diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ea3fd9a3a..ecd9cb6a5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,7 +45,6 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy - - Through Certimate - go install - Other validations: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 7f6793167..33f7be155 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -24,7 +24,6 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy - - Through Certimate - go install - Other validations: diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index b319bc287..9e9fe3c03 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 @@ -40,23 +34,11 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy - - Through Certimate - go install - Other validations: required: true - - type: dropdown - id: profile - attributes: - label: Who are you? - options: - - A customer of this DNS provider - - An employee of this DNS provider - - Other (please explain) - validations: - required: true - - type: input id: provider-link attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 795320a8d..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4f9d444fc..c0bbbfbdc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,11 +17,15 @@ jobs: steps: - - uses: actions/checkout@v6 + # https://github.com/marketplace/actions/checkout + - name: Check out code + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-go@v6 + # https://github.com/marketplace/actions/setup-go-environment + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/go-cross.yml b/.github/workflows/go-cross.yml index 9dee85035..30ec652a2 100644 --- a/.github/workflows/go-cross.yml +++ b/.github/workflows/go-cross.yml @@ -20,8 +20,13 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v6 - - uses: actions/setup-go@v6 + # https://github.com/marketplace/actions/checkout + - name: Checkout code + uses: actions/checkout@v4 + + # https://github.com/marketplace/actions/setup-go-environment + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 33ca106cc..91977bc28 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.10 + GOLANGCI_LINT_VERSION: v2.6.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI @@ -21,36 +21,43 @@ jobs: steps: - - uses: actions/checkout@v6 + # https://github.com/marketplace/actions/checkout + - name: Check out code + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-go@v6 + # https://github.com/marketplace/actions/setup-go-environment + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - name: Check and get dependencies run: | - go mod tidy --diff + go mod tidy + git diff --exit-code go.mod + git diff --exit-code go.sum - name: Generate and Check generated elements run: | make generate-dns git diff --exit-code - - uses: golangci/golangci-lint-action@v9 - with: - version: ${{ env.GOLANGCI_LINT_VERSION }} - install-only: true + # https://golangci-lint.run/usage/install#other-ci + - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} + golangci-lint --version - name: Install Pebble - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.9.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.7.0 - name: Install challtestsrv - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.9.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.7.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/.github/workflows/release.yml b/.github/workflows/release.yml index 6a0d3b703..a83c85909 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,11 +42,13 @@ jobs: docker-images: true swap-storage: false - - uses: actions/checkout@v6 + - name: Check out code + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-go@v6 + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} @@ -67,10 +69,9 @@ jobs: # https://goreleaser.com/ci/actions/ - name: Run GoReleaser - id: goreleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.13.0 + version: v2.12.3 args: release -p 1 --clean --timeout=90m env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} @@ -79,9 +80,7 @@ jobs: - uses: actions/attest-build-provenance@v3 with: - subject-checksums: ./dist/lego_${{ fromJSON(steps.goreleaser.outputs.metadata).version }}_checksums.txt - github-token: ${{ secrets.GH_TOKEN_REPO }} + subject-checksums: ./dist/lego_*_checksums.txt - uses: actions/attest-build-provenance@v3 with: subject-checksums: ./dist/digests.txt - github-token: ${{ secrets.GH_TOKEN_REPO }} diff --git a/.golangci.yml b/.golangci.yml index b6ab51ccc..a6f0c4bfa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -180,12 +180,6 @@ linters: text: Error return value of `fmt.Fprintln` is not checked linters: - errcheck - - 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: @@ -222,7 +216,11 @@ linters: text: load is a global variable linters: - gochecknoglobals - - path: providers/(dns|http)/([\d\w]+/)*[\d\w]+_test.go + - path: providers/dns/([\d\w]+/)*[\d\w]+_test.go + text: envTest is a global variable + linters: + - gochecknoglobals + - path: providers/http/([\d\w]+/)*[\d\w]+_test.go text: envTest is a global variable linters: - gochecknoglobals @@ -230,10 +228,6 @@ linters: text: testCases is a global variable linters: - gochecknoglobals - - path: providers/dns/namecheap/transport.go - text: (envProxyOnce|envProxyFuncValue) is a global variable - linters: - - gochecknoglobals - path: providers/dns/acmedns/mock_test.go text: egTestAccount is a global variable linters: @@ -269,10 +263,6 @@ linters: text: cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high linters: - gocyclo - - path: providers/dns/manual/manual.go - text: 'SA1019: dns01.DNSProviderManual is deprecated' - linters: - - staticcheck # Those elements have been replaced by non-exposed structures. - path: providers/dns/linode/linode_test.go text: 'SA1019: linodego\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated' diff --git a/.goreleaser.yml b/.goreleaser.yml index c358f8a38..073997209 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -90,9 +90,8 @@ dockers_v2: - linux/arm/v7 tags: - 'latest' - - 'v{{ .Major }}' - - 'v{{ .Major }}.{{ .Minor }}' - '{{ .Tag }}' + - 'v{{ .Major }}.{{ .Minor }}' labels: # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys 'org.opencontainers.image.title': '{{.ProjectName}}' diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73f70f3..ccfd912d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,129 +1,11 @@ # Changelog -lego is an independent, free, open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ +lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). -## v4.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 -- Tag: [v4.31.0](https://github.com/go-acme/lego/releases/tag/v4.31.0) - -### Added - -- **[dnsprovider]** Add DNS provider for ISPConfig -- **[dnsprovider]** Add DNS Provider for ISPConfig (DDNS Module) -- **[dnsprovider]** Add DNS provider for Alwaysdata -- **[dnsprovider]** Add DNS provider for JDCloud -- **[dnsprovider]** Add DNS provider for 35.com/三五互联 -- **[dnsprovider]** f5xc: add an option to configure the domain of the server - -### Changed - -- **[lib]** feat: improve ACME error types -- **[dnsprovider,cname]** namedotcom: follow CNAME - -### Fixed - -- **[dnsprovider]** hetzner: fix compatibility with _FILE suffix -- **[dnsprovider]** gandiv5: fix API Key header - -## v4.30.1 - -- Release date: 2025-12-16 -- Tag: [v4.30.1](https://github.com/go-acme/lego/releases/tag/v4.30.1) - -Due to an error related to `aliyun/credentials-go`, some artifacts of the v4.30.0 release have not been published. - -This release contains the same things as v4.30.0. - -## v4.30.0 - -- Release date: 2025-12-16 -- Tag: [v4.30.0](https://github.com/go-acme/lego/releases/tag/v4.30.0) - -### Added - -- **[dnsprovider]** Add DNS provider for Ionos Cloud -- **[dnsprovider]** Add DNS provider for Virtualname -- **[dnsprovider]** Add DNS Provider for Neodigit -- **[dnsprovider]** Add DNS provider for Syse.no -- **[dnsprovider]** Add DNS provider for Gravity -- **[dnsprovider]** Add DNS provider for hosting.nl - -### Changed - -- **[cli]** feat: remove email requirement - -### Fixed - -- **[dnsprovider]** autodns: use the right response structure - -## v4.29.0 - -- Release date: 2025-11-29 -- Tag: [v4.29.0](https://github.com/go-acme/lego/releases/tag/v4.29.0) - -### Added - -- **[dnsprovider]** Add DNS provider for United-Domains -- **[dnsprovider]** Add DNS provider for Gigahost.no -- **[dnsprovider]** Add DNS provider for EdgeCenter -- **[dnsprovider]** Add DNS provider for AlibabaCloud ESA -- **[dnsprovider]** edgeone: add zones mapping -- **[dnsprovider]** namecheap: add experimental proxy support - -### Changed - -- **[dnsprovider]** gandiv5: update base API URL - -### Fixed - -- **[dnsprovider]** hetzner: use int64 for IDs -- **[dnsprovider]** baiducloud: pagination and TTL -- **[dnsprovider]** inwx: fix API breaking changes with record IDs - -## v4.28.1 - -- Release date: 2025-11-06 -- Tag: [v4.28.1](https://github.com/go-acme/lego/releases/tag/v4.28.1) - -### Fixed - -- **[cli]** fix: skip nil response - ## v4.28.0 - Release date: 2025-10-31 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05e4fa994..a0005cff8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ To ensure a great and easy experience for everyone, please review the few guidel - If both of the above do not apply, create a new issue and include as much information as possible. Bug reports should include all information a person could need to reproduce your problem without the need to -follow up for more information. If possible, provide detailed steps for us to reproduce it, the expected behavior and the actual behavior. +follow up for more information. If possible, provide detailed steps for us to reproduce it, the expected behaviour and the actual behaviour. ## Feature proposals and requests @@ -20,26 +20,31 @@ It is up to you to make a strong point about your proposal and convince us of th ## Pull requests -Create an issue and wait for a maintainer to approve it BEFORE opening a pull request. - Patches, new features and improvements are a great way to help the project. Please keep them focused on one thing and do not include unrelated commits. -All pull requests that alter the behavior of the program, -add new behavior or somehow alter code in a non-trivial way should **always** include tests. +All pull requests which alter the behaviour of the program, add new behaviour or somehow alter code in a non-trivial way should **always** include tests. -**IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of the [MIT License](LICENSE). +If you want to contribute a significant pull request (with a non-trivial workload for you) please **ask first**. We do not want you to spend +a lot of time on something the project's developers might not want to merge into the project. + +**IMPORTANT**: By submitting a patch, you agree to allow the project +owners to license your work under the terms of the [MIT License](LICENSE). ### How to create a pull request Requirements: -- `go` v1.24+ +- `go` v1.15+ - environment variable: `GO111MODULE=on` First, you have to install [GoLang](https://golang.org/doc/install) and [golangci-lint](https://github.com/golangci/golangci-lint#install). ```bash +# Create the root folder +mkdir -p $GOPATH/src/github.com/go-acme +cd $GOPATH/src/github.com/go-acme + # clone your fork git clone git@github.com:YOUR_USERNAME/lego.git cd lego @@ -51,12 +56,14 @@ git fetch upstream ```bash # Create your branch -git switch -c my-feature +git checkout -b my-feature ## Create your code ## ``` ```bash +# Format +make fmt # Linters make checks # Tests diff --git a/README.md b/README.md index e90e94962..1a8480b24 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) @@ -24,7 +24,7 @@ So if you think that lego is worth it, please consider [donating](https://donate - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [180 DNS providers](https://go-acme.github.io/lego/dns) +- Comes with about [170 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates @@ -56,63 +56,51 @@ Documentation is hosted live at https://go-acme.github.io/lego/. Detailed documentation is available [here](https://go-acme.github.io/lego/dns). -If your DNS provider is not supported, please open an [issue](https://github.com/go-acme/lego/issues/new?assignees=&labels=enhancement%2C+new-provider&template=new_dns_provider.yml). - - - - - - + - - - + - + - + - - + - + - + - + - - - @@ -121,48 +109,38 @@ If your DNS provider is not supported, please open an [issue](https://github.com - - + - + - - - - - + - - - + - - - + - @@ -182,124 +160,114 @@ If your DNS provider is not supported, please open an [issue](https://github.com - - - - - - - + - + - + - + - + - - + - + - + - + - + - + - + - + - - - + - + - - - + - + - + - + +
35.com/三五互联 Active24 Akamai EdgeDNS Alibaba Cloud DNS
AlibabaCloud ESA all-inklAlwaysdataAmazon Lightsail
Amazon Lightsail Amazon Route 53 Anexia CloudDNSANS SafeDNSArtFiles
ArvanCloud
Aurora DNS Autodns Axelname
Azion
Azure (deprecated) Azure DNS Baidu Cloud
Beget.com
Binary Lane Bindman Bluecat
Bluecat v2 BookMyName
Brandit (deprecated) Bunny
Checkdomain Civo
Cloud.ru CloudDNS
Cloudflare ClouDNS
CloudXNS (Deprecated) ConoHa v2
ConoHa v3 Constellix
Core-Networks CPanel/WHM
CzechiaDDnss (DynDNS Service) Derak Cloud deSEC.io
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 IPEpikEuroDNS
ExcedoEpik Exoscale External program F5 XC
freemyip.comFusionLayer NameSurfer G-Core Gandi
Gandi Live DNS (v5)Gigahost.no
Glesys Go Daddy
Google Cloud Google DomainsGravityHetzner
Hetzner Hosting.deHosting.nl Hostinger Hosttech
INWX
IonosIonos Cloud IPv64ISPConfig 3
ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated)JD Cloud Joker
Joohoi's ACME-DNS KeyHelpLeaseweb 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.NETNeodigit
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
StackpathSyse TechnitiumTencent Cloud DNS
Tencent Cloud DNS Tencent EdgeOne Timeweb CloudTodayNIC/时代互联 TransIP
UKFast SafeDNS UltradnsUnited-Domains Variomedia VegaDNS
Vercel Versio.[nl|eu|uk] VinylDNSVirtualname
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/sender.go b/acme/api/internal/sender/sender.go index d8859edf4..d5db5d410 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -120,46 +120,39 @@ func (d *Doer) formatUserAgent() string { } func checkError(req *http.Request, resp *http.Response) error { - if resp.StatusCode < http.StatusBadRequest { - return nil - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err) - } - - var errorDetails *acme.ProblemDetails - - err = json.Unmarshal(body, &errorDetails) - if err != nil { - return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body)) - } - - errorDetails.Method = req.Method - errorDetails.URL = req.URL.String() - - if errorDetails.HTTPStatus == 0 { - errorDetails.HTTPStatus = resp.StatusCode - } - - // Check for errors we handle specifically - switch { - case errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr: - return &acme.NonceError{ProblemDetails: errorDetails} - - case errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr: - return &acme.AlreadyReplacedError{ProblemDetails: errorDetails} - - case errorDetails.HTTPStatus == http.StatusTooManyRequests && errorDetails.Type == acme.RateLimitedErr: - return &acme.RateLimitedError{ - ProblemDetails: errorDetails, - RetryAfter: resp.Header.Get("Retry-After"), + if resp.StatusCode >= http.StatusBadRequest { + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err) + } + + var errorDetails *acme.ProblemDetails + + err = json.Unmarshal(body, &errorDetails) + if err != nil { + return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body)) + } + + errorDetails.Method = req.Method + errorDetails.URL = req.URL.String() + + if errorDetails.HTTPStatus == 0 { + errorDetails.HTTPStatus = resp.StatusCode + } + + // Check for errors we handle specifically + if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr { + return &acme.NonceError{ProblemDetails: errorDetails} + } + + if errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr { + return &acme.AlreadyReplacedError{ProblemDetails: errorDetails} } - default: return errorDetails } + + return nil } type httpsOnly struct { diff --git a/acme/api/internal/sender/sender_test.go b/acme/api/internal/sender/sender_test.go index 73701ab11..1f25c6d26 100644 --- a/acme/api/internal/sender/sender_test.go +++ b/acme/api/internal/sender/sender_test.go @@ -1,14 +1,11 @@ package sender import ( - "bytes" - "io" "net/http" "net/http/httptest" "strings" "testing" - "github.com/go-acme/lego/v4/acme" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -81,70 +78,3 @@ func TestDo_failWithHTTP(t *testing.T) { _, err := sender.Post(server.URL, strings.NewReader("data"), "text/plain", nil) require.ErrorContains(t, err, "HTTPS is required: http://") } - -func Test_checkError(t *testing.T) { - testCases := []struct { - desc string - resp *http.Response - assert func(t *testing.T, err error) - }{ - { - desc: "default", - resp: &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:example","detail":"message","status":404}`)), - }, - assert: errorAs[*acme.ProblemDetails], - }, - { - desc: "badNonce", - resp: &http.Response{ - StatusCode: http.StatusBadRequest, - Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:badNonce","detail":"message","status":400}`)), - }, - assert: errorAs[*acme.NonceError], - }, - { - desc: "alreadyReplaced", - resp: &http.Response{ - StatusCode: http.StatusConflict, - Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:alreadyReplaced","detail":"message","status":409}`)), - }, - assert: errorAs[*acme.AlreadyReplacedError], - }, - { - desc: "rateLimited", - resp: &http.Response{ - StatusCode: http.StatusConflict, - Header: http.Header{ - "Retry-After": []string{"1"}, - }, - Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:rateLimited","detail":"message","status":429}`)), - }, - assert: errorAs[*acme.RateLimitedError], - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://example.com", nil) - - err := checkError(req, test.resp) - require.Error(t, err) - - pb := &acme.ProblemDetails{} - assert.ErrorAs(t, err, &pb) - - test.assert(t, err) - }) - } -} - -func errorAs[T error](t *testing.T, err error) { - t.Helper() - - var zero T - assert.ErrorAs(t, err, &zero) -} diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 51a1b4770..11f6edf99 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.32.0" + ourUserAgent = "xenolf-acme/4.28.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/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..161a47c38 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -10,7 +10,6 @@ const ( errNS = "urn:ietf:params:acme:error:" BadNonceErr = errNS + "badNonce" AlreadyReplacedErr = errNS + "alreadyReplaced" - RateLimitedErr = errNS + "rateLimited" ) // ProblemDetails the problem details object. @@ -29,18 +28,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 != "" { @@ -64,30 +63,9 @@ type NonceError struct { *ProblemDetails } -func (e *NonceError) Unwrap() error { - return e.ProblemDetails -} - // AlreadyReplacedError represents the error which is returned -// if the Server rejects the request because the identified certificate has already been marked as replaced. +// If the Server rejects the request because the identified certificate has already been marked as replaced. // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 type AlreadyReplacedError struct { *ProblemDetails } - -func (e *AlreadyReplacedError) Unwrap() error { - return e.ProblemDetails -} - -// RateLimitedError represents the error which is returned -// if the server rejects the request because the client has exceeded the rate limit. -// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.6 -type RateLimitedError struct { - *ProblemDetails - - RetryAfter string -} - -func (e *RateLimitedError) Unwrap() error { - return e.ProblemDetails -} 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/dns01/dns_challenge_manual.go b/challenge/dns01/dns_challenge_manual.go index 3821fc157..c00d64041 100644 --- a/challenge/dns01/dns_challenge_manual.go +++ b/challenge/dns01/dns_challenge_manual.go @@ -12,14 +12,9 @@ const ( ) // DNSProviderManual is an implementation of the ChallengeProvider interface. -// TODO(ldez): move this to providers/dns/manual -// -// Deprecated: Use the manual.DNSProvider instead. type DNSProviderManual struct{} // NewDNSProviderManual returns a DNSProviderManual instance. -// -// Deprecated: Use the manual.NewDNSProvider instead. func NewDNSProviderManual() (*DNSProviderManual, error) { return &DNSProviderManual{}, nil } diff --git a/providers/dns/manual/manual_test.go b/challenge/dns01/dns_challenge_manual_test.go similarity index 73% rename from providers/dns/manual/manual_test.go rename to challenge/dns01/dns_challenge_manual_test.go index 7badd4b8b..c183822bb 100644 --- a/providers/dns/manual/manual_test.go +++ b/challenge/dns01/dns_challenge_manual_test.go @@ -1,14 +1,22 @@ -package manual +package dns01 import ( "io" "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" + "github.com/miekg/dns" "github.com/stretchr/testify/require" ) func TestDNSProviderManual(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). + Query("_acme-challenge.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t)) + backupStdin := os.Stdin defer func() { os.Stdin = backupStdin }() @@ -44,7 +52,7 @@ func TestDNSProviderManual(t *testing.T) { os.Stdin = file - manualProvider, err := NewDNSProvider() + manualProvider, err := NewDNSProviderManual() require.NoError(t, err) err = manualProvider.Present("example.com", "", "") 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/accounts_storage.go b/cmd/accounts_storage.go index 01db2faf8..1dbdfb84b 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -16,8 +16,6 @@ import ( "github.com/urfave/cli/v2" ) -const userIDPlaceholder = "noemail@example.com" - const ( baseAccountsRootFolderName = "accounts" baseKeysFolderName = "keys" @@ -34,7 +32,7 @@ const ( // // rootUserPath: // -// ./.lego/accounts/localhost_14000/foo@example.com/ +// ./.lego/accounts/localhost_14000/hubert@hubert.com/ // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) // │ └── root accounts directory @@ -42,7 +40,7 @@ const ( // // keysPath: // -// ./.lego/accounts/localhost_14000/foo@example.com/keys/ +// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/ // │ │ │ │ └── root keys directory // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) @@ -51,7 +49,7 @@ const ( // // accountFilePath: // -// ./.lego/accounts/localhost_14000/foo@example.com/account.json +// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json // │ │ │ │ └── account file // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) @@ -59,7 +57,6 @@ const ( // └── "path" option type AccountsStorage struct { userID string - email string rootPath string rootUserPath string keysPath string @@ -69,13 +66,8 @@ type AccountsStorage struct { // NewAccountsStorage Creates a new AccountsStorage. func NewAccountsStorage(ctx *cli.Context) *AccountsStorage { - // TODO: move to account struct? - email := ctx.String(flgEmail) - - userID := email - if userID == "" { - userID = userIDPlaceholder - } + // TODO: move to account struct? Currently MUST pass email. + email := getEmail(ctx) serverURL, err := url.Parse(ctx.String(flgServer)) if err != nil { @@ -85,11 +77,10 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage { rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName) serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host) accountsPath := filepath.Join(rootPath, serverPath) - rootUserPath := filepath.Join(accountsPath, userID) + rootUserPath := filepath.Join(accountsPath, email) return &AccountsStorage{ - userID: userID, - email: email, + userID: email, rootPath: rootPath, rootUserPath: rootUserPath, keysPath: filepath.Join(rootUserPath, baseKeysFolderName), @@ -121,10 +112,6 @@ func (s *AccountsStorage) GetUserID() string { return s.userID } -func (s *AccountsStorage) GetEmail() string { - return s.email -} - func (s *AccountsStorage) Save(account *Account) error { jsonBytes, err := json.MarshalIndent(account, "", "\t") if err != nil { @@ -137,14 +124,14 @@ func (s *AccountsStorage) Save(account *Account) error { func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { fileBytes, err := os.ReadFile(s.accountFilePath) if err != nil { - log.Fatalf("Could not load file for account %s: %v", s.GetUserID(), err) + log.Fatalf("Could not load file for account %s: %v", s.userID, err) } var account Account err = json.Unmarshal(fileBytes, &account) if err != nil { - log.Fatalf("Could not parse file for account %s: %v", s.GetUserID(), err) + log.Fatalf("Could not parse file for account %s: %v", s.userID, err) } account.key = privateKey @@ -152,14 +139,14 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { if account.Registration == nil || account.Registration.Body.Status == "" { reg, err := tryRecoverRegistration(s.ctx, privateKey) if err != nil { - log.Fatalf("Could not load account for %s. Registration is nil: %#v", s.GetUserID(), err) + log.Fatalf("Could not load account for %s. Registration is nil: %#v", s.userID, err) } account.Registration = reg err = s.Save(&account) if err != nil { - log.Fatalf("Could not save account for %s. Registration is nil: %#v", s.GetUserID(), err) + log.Fatalf("Could not save account for %s. Registration is nil: %#v", s.userID, err) } } @@ -167,15 +154,15 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { } func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.PrivateKey { - accKeyPath := filepath.Join(s.keysPath, s.GetUserID()+".key") + accKeyPath := filepath.Join(s.keysPath, s.userID+".key") if _, err := os.Stat(accKeyPath); os.IsNotExist(err) { - log.Printf("No key found for account %s. Generating a %s key.", s.GetUserID(), keyType) + log.Printf("No key found for account %s. Generating a %s key.", s.userID, keyType) s.createKeysFolder() privateKey, err := generatePrivateKey(accKeyPath, keyType) if err != nil { - log.Fatalf("Could not generate RSA private account key for account %s: %v", s.GetUserID(), err) + log.Fatalf("Could not generate RSA private account key for account %s: %v", s.userID, err) } log.Printf("Saved key to %s", accKeyPath) @@ -193,7 +180,7 @@ func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.Priva func (s *AccountsStorage) createKeysFolder() { if err := createNonExistingFolder(s.keysPath); err != nil { - log.Fatalf("Could not check/create directory for account %s: %v", s.GetUserID(), err) + log.Fatalf("Could not check/create directory for account %s: %v", s.userID, err) } } diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 53cd12c3c..864b85977 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" @@ -37,7 +36,7 @@ func createList() *cli.Command { // fake email, needed by NewAccountsStorage &cli.StringFlag{ Name: flgEmail, - Value: "", + Value: "unknown", Hidden: true, }, }, @@ -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_renew.go b/cmd/cmd_renew.go index 4b41ebc78..99bc5ebbd 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -144,9 +144,7 @@ func renew(ctx *cli.Context) error { bundle := !ctx.Bool(flgNoBundle) - meta := map[string]string{ - hookEnvAccountEmail: account.Email, - } + meta := map[string]string{hookEnvAccountEmail: account.Email} // CSR if ctx.IsSet(flgCSR) { diff --git a/cmd/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..8eed28947 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.32.0+dev-detach" +const defaultVersion = "v4.28.0+dev-release" var version = "" diff --git a/cmd/setup.go b/cmd/setup.go index 6d15adad3..4d17f2e27 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -40,7 +40,7 @@ func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, if accountsStorage.ExistsAccountFilePath() { account = accountsStorage.LoadAccount(privateKey) } else { - account = &Account{Email: accountsStorage.GetEmail(), key: privateKey} + account = &Account{Email: accountsStorage.GetUserID(), key: privateKey} } return account, keyType @@ -118,6 +118,15 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType { return "" } +func getEmail(ctx *cli.Context) string { + email := ctx.String(flgEmail) + if email == "" { + log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail) + } + + return email +} + func getUserAgent(ctx *cli.Context) string { return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version)) } @@ -171,10 +180,6 @@ func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, erro return rt, err } - if resp == nil { - return rt, nil - } - if resp.StatusCode/100 == 2 { return rt, nil } diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index f73f3920b..e21f37e63 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -12,14 +12,12 @@ import ( func allDNSCodes() string { providers := []string{ + "manual", "acme-dns", "active24", "alidns", - "aliesa", "allinkl", - "alwaysdata", "anexia", - "artfiles", "arvancloud", "auroradns", "autodns", @@ -32,7 +30,6 @@ func allDNSCodes() string { "binarylane", "bindman", "bluecat", - "bluecatv2", "bookmyname", "brandit", "bunny", @@ -43,20 +40,16 @@ func allDNSCodes() string { "cloudns", "cloudru", "cloudxns", - "com35", "conoha", "conohav3", "constellix", "corenetworks", "cpanel", - "czechia", - "ddnss", "derak", "desec", "designate", "digitalocean", "directadmin", - "dnsexit", "dnshomede", "dnsimple", "dnsmadeeasy", @@ -69,13 +62,10 @@ func allDNSCodes() string { "dyndnsfree", "dynu", "easydns", - "edgecenter", "edgedns", "edgeone", "efficientip", "epik", - "eurodns", - "excedo", "exec", "exoscale", "f5xc", @@ -84,15 +74,12 @@ func allDNSCodes() string { "gandiv5", "gcloud", "gcore", - "gigahostno", "glesys", "godaddy", "googledomains", - "gravity", "hetzner", "hostingde", "hostinger", - "hostingnl", "hosttech", "httpnet", "httpreq", @@ -107,15 +94,10 @@ func allDNSCodes() string { "internetbs", "inwx", "ionos", - "ionoscloud", "ipv64", - "ispconfig", - "ispconfigddns", "iwantmyname", - "jdcloud", "joker", "keyhelp", - "leaseweb", "liara", "lightsail", "limacity", @@ -125,7 +107,6 @@ func allDNSCodes() string { "luadns", "mailinabox", "manageengine", - "manual", "metaname", "metaregistrar", "mijnhost", @@ -136,9 +117,7 @@ func allDNSCodes() string { "namecheap", "namedotcom", "namesilo", - "namesurfer", "nearlyfreespeech", - "neodigit", "netcup", "netlify", "nicmanager", @@ -174,20 +153,16 @@ func allDNSCodes() string { "sonic", "spaceship", "stackpath", - "syse", "technitium", "tencentcloud", "timewebcloud", - "todaynic", "transip", "ultradns", - "uniteddomains", "variomedia", "vegadns", "vercel", "versio", "vinyldns", - "virtualname", "vkcloud", "volcengine", "vscale", @@ -269,38 +244,13 @@ 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() ew.writeln(`More information: https://go-acme.github.io/lego/dns/alidns`) - case "aliesa": - // generated from: providers/dns/aliesa/aliesa.toml - ew.writeln(`Configuration for AlibabaCloud ESA.`) - ew.writeln(`Code: 'aliesa'`) - ew.writeln(`Since: 'v4.29.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "ALIESA_ACCESS_KEY": Access key ID`) - ew.writeln(` - "ALIESA_RAM_ROLE": Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)`) - ew.writeln(` - "ALIESA_SECRET_KEY": Access Key secret`) - ew.writeln(` - "ALIESA_SECURITY_TOKEN": STS Security Token (optional)`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ALIESA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "ALIESA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "ALIESA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "ALIESA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/aliesa`) - case "allinkl": // generated from: providers/dns/allinkl/allinkl.toml ew.writeln(`Configuration for all-inkl.`) @@ -321,27 +271,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/allinkl`) - case "alwaysdata": - // generated from: providers/dns/alwaysdata/alwaysdata.toml - ew.writeln(`Configuration for Alwaysdata.`) - ew.writeln(`Code: 'alwaysdata'`) - ew.writeln(`Since: 'v4.31.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "ALWAYSDATA_API_KEY": API Key`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ALWAYSDATA_ACCOUNT": Account name`) - ew.writeln(` - "ALWAYSDATA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "ALWAYSDATA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "ALWAYSDATA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "ALWAYSDATA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/alwaysdata`) - case "anexia": // generated from: providers/dns/anexia/anexia.toml ew.writeln(`Configuration for Anexia CloudDNS.`) @@ -363,27 +292,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.`) @@ -563,7 +471,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "BAIDUCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "BAIDUCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "BAIDUCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) + ew.writeln(` - "BAIDUCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/baiducloud`) @@ -653,31 +561,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.`) @@ -896,27 +779,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudxns`) - case "com35": - // generated from: providers/dns/com35/com35.toml - ew.writeln(`Configuration for 35.com/三五互联.`) - ew.writeln(`Code: 'com35'`) - ew.writeln(`Since: 'v4.31.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "COM35_PASSWORD": API password`) - ew.writeln(` - "COM35_USERNAME": Username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "COM35_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "COM35_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) - ew.writeln(` - "COM35_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) - ew.writeln(` - "COM35_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/com35`) - case "conoha": // generated from: providers/dns/conoha/conoha.toml ew.writeln(`Configuration for ConoHa v2.`) @@ -1029,47 +891,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 +1006,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.`) @@ -1451,26 +1252,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/easydns`) - case "edgecenter": - // generated from: providers/dns/edgecenter/edgecenter.toml - ew.writeln(`Configuration for EdgeCenter.`) - ew.writeln(`Code: 'edgecenter'`) - ew.writeln(`Since: 'v4.29.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "EDGECENTER_PERMANENT_API_TOKEN": Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/)`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "EDGECENTER_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) - ew.writeln(` - "EDGECENTER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) - ew.writeln(` - "EDGECENTER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 360)`) - ew.writeln(` - "EDGECENTER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgecenter`) - case "edgedns": // generated from: providers/dns/edgedns/edgedns.toml ew.writeln(`Configuration for Akamai EdgeDNS.`) @@ -1515,7 +1296,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "EDGEONE_REGION": Region`) ew.writeln(` - "EDGEONE_SESSION_TOKEN": Access Key token`) ew.writeln(` - "EDGEONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) - ew.writeln(` - "EDGEONE_ZONES_MAPPING": Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgeone`) @@ -1564,48 +1344,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.`) @@ -1655,7 +1393,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "F5XC_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "F5XC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "F5XC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "F5XC_SERVER": Server domain (Default: console.ves.volterra.io)`) ew.writeln(` - "F5XC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() @@ -1768,28 +1505,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcore`) - case "gigahostno": - // generated from: providers/dns/gigahostno/gigahostno.toml - ew.writeln(`Configuration for Gigahost.no.`) - ew.writeln(`Code: 'gigahostno'`) - ew.writeln(`Since: 'v4.29.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "GIGAHOSTNO_PASSWORD": Password`) - ew.writeln(` - "GIGAHOSTNO_USERNAME": Username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GIGAHOSTNO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "GIGAHOSTNO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "GIGAHOSTNO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "GIGAHOSTNO_SECRET": TOTP secret`) - ew.writeln(` - "GIGAHOSTNO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/gigahostno`) - case "glesys": // generated from: providers/dns/glesys/glesys.toml ew.writeln(`Configuration for Glesys.`) @@ -1851,28 +1566,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/googledomains`) - case "gravity": - // generated from: providers/dns/gravity/gravity.toml - ew.writeln(`Configuration for Gravity.`) - ew.writeln(`Code: 'gravity'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "GRAVITY_PASSWORD": Password`) - ew.writeln(` - "GRAVITY_SERVER_URL": URL of the server`) - ew.writeln(` - "GRAVITY_USERNAME": Username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GRAVITY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "GRAVITY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "GRAVITY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "GRAVITY_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 1)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/gravity`) - case "hetzner": // generated from: providers/dns/hetzner/hetzner.toml ew.writeln(`Configuration for Hetzner.`) @@ -1934,26 +1627,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostinger`) - case "hostingnl": - // generated from: providers/dns/hostingnl/hostingnl.toml - ew.writeln(`Configuration for Hosting.nl.`) - ew.writeln(`Code: 'hostingnl'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "HOSTINGNL_API_KEY": The API key`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HOSTINGNL_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) - ew.writeln(` - "HOSTINGNL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "HOSTINGNL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) - ew.writeln(` - "HOSTINGNL_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostingnl`) - case "hosttech": // generated from: providers/dns/hosttech/hosttech.toml ew.writeln(`Configuration for Hosttech.`) @@ -2253,26 +1926,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ionos`) - case "ionoscloud": - // generated from: providers/dns/ionoscloud/ionoscloud.toml - ew.writeln(`Configuration for Ionos Cloud.`) - ew.writeln(`Code: 'ionoscloud'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "IONOSCLOUD_API_TOKEN": API token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "IONOSCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "IONOSCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "IONOSCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) - ew.writeln(` - "IONOSCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/ionoscloud`) - case "ipv64": // generated from: providers/dns/ipv64/ipv64.toml ew.writeln(`Configuration for IPv64.`) @@ -2292,50 +1945,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ipv64`) - case "ispconfig": - // generated from: providers/dns/ispconfig/ispconfig.toml - ew.writeln(`Configuration for ISPConfig 3.`) - ew.writeln(`Code: 'ispconfig'`) - ew.writeln(`Since: 'v4.31.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "ISPCONFIG_PASSWORD": Password`) - ew.writeln(` - "ISPCONFIG_SERVER_URL": Server URL`) - ew.writeln(` - "ISPCONFIG_USERNAME": Username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ISPCONFIG_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "ISPCONFIG_INSECURE_SKIP_VERIFY": Whether to verify the API certificate`) - ew.writeln(` - "ISPCONFIG_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "ISPCONFIG_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "ISPCONFIG_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/ispconfig`) - - case "ispconfigddns": - // generated from: providers/dns/ispconfigddns/ispconfigddns.toml - ew.writeln(`Configuration for ISPConfig 3 - Dynamic DNS (DDNS) Module.`) - ew.writeln(`Code: 'ispconfigddns'`) - ew.writeln(`Since: 'v4.31.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "ISPCONFIG_DDNS_SERVER_URL": API server URL (ex: https://panel.example.com:8080)`) - ew.writeln(` - "ISPCONFIG_DDNS_TOKEN": DDNS API token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ISPCONFIG_DDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "ISPCONFIG_DDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "ISPCONFIG_DDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "ISPCONFIG_DDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/ispconfigddns`) - case "iwantmyname": // generated from: providers/dns/iwantmyname/iwantmyname.toml ew.writeln(`Configuration for iwantmyname (Deprecated).`) @@ -2357,28 +1966,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iwantmyname`) - case "jdcloud": - // generated from: providers/dns/jdcloud/jdcloud.toml - ew.writeln(`Configuration for JD Cloud.`) - ew.writeln(`Code: 'jdcloud'`) - ew.writeln(`Since: 'v4.31.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "JDCLOUD_ACCESS_KEY_ID": Access key ID`) - ew.writeln(` - "JDCLOUD_ACCESS_KEY_SECRET": Access key secret`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "JDCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "JDCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "JDCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "JDCLOUD_REGION_ID": Region ID (Default: cn-north-1)`) - ew.writeln(` - "JDCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/jdcloud`) - case "joker": // generated from: providers/dns/joker/joker.toml ew.writeln(`Configuration for Joker.`) @@ -2424,26 +2011,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 +2026,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() @@ -2634,16 +2200,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/manageengine`) - case "manual": - // generated from: providers/dns/manual/manual.toml - ew.writeln(`Configuration for Manual.`) - ew.writeln(`Code: 'manual'`) - ew.writeln(`Since: 'v0.3.0'`) - ew.writeln() - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/manual`) - case "metaname": // generated from: providers/dns/metaname/metaname.toml ew.writeln(`Configuration for Metaname.`) @@ -2852,30 +2408,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.`) @@ -2898,26 +2430,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nearlyfreespeech`) - case "neodigit": - // generated from: providers/dns/neodigit/neodigit.toml - ew.writeln(`Configuration for Neodigit.`) - ew.writeln(`Code: 'neodigit'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "NEODIGIT_TOKEN": API token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NEODIGIT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "NEODIGIT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) - ew.writeln(` - "NEODIGIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) - ew.writeln(` - "NEODIGIT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/neodigit`) - case "netcup": // generated from: providers/dns/netcup/netcup.toml ew.writeln(`Configuration for Netcup.`) @@ -3439,7 +2951,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() @@ -3697,26 +3209,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/stackpath`) - case "syse": - // generated from: providers/dns/syse/syse.toml - ew.writeln(`Configuration for Syse.`) - ew.writeln(`Code: 'syse'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "SYSE_CREDENTIALS": Comma-separated list of 'zone:password' credential pairs`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SYSE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "SYSE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) - ew.writeln(` - "SYSE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) - ew.writeln(` - "SYSE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/syse`) - case "technitium": // generated from: providers/dns/technitium/technitium.toml ew.writeln(`Configuration for Technitium.`) @@ -3780,27 +3272,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.`) @@ -3843,26 +3314,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ultradns`) - case "uniteddomains": - // generated from: providers/dns/uniteddomains/uniteddomains.toml - ew.writeln(`Configuration for United-Domains.`) - ew.writeln(`Code: 'uniteddomains'`) - ew.writeln(`Since: 'v4.29.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "UNITEDDOMAINS_API_KEY": API key '.' https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "UNITEDDOMAINS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "UNITEDDOMAINS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "UNITEDDOMAINS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 900)`) - ew.writeln(` - "UNITEDDOMAINS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/uniteddomains`) - case "variomedia": // generated from: providers/dns/variomedia/variomedia.toml ew.writeln(`Configuration for Variomedia.`) @@ -3972,26 +3423,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vinyldns`) - case "virtualname": - // generated from: providers/dns/virtualname/virtualname.toml - ew.writeln(`Configuration for Virtualname.`) - ew.writeln(`Code: 'virtualname'`) - ew.writeln(`Since: 'v4.30.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "VIRTUALNAME_TOKEN": API token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VIRTUALNAME_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "VIRTUALNAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) - ew.writeln(` - "VIRTUALNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) - ew.writeln(` - "VIRTUALNAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/virtualname`) - case "vkcloud": // generated from: providers/dns/vkcloud/vkcloud.toml ew.writeln(`Configuration for VK Cloud.`) @@ -4307,6 +3738,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/zonomi`) + case "manual": + ew.writeln(`Solving the DNS-01 challenge using CLI prompt.`) default: return fmt.Errorf("%q is not yet supported", name) } diff --git a/docs/content/_index.md b/docs/content/_index.md index 95e411afc..229435e7d 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -7,16 +7,6 @@ chapter: false Let's Encrypt client and ACME library written in Go. -{{% notice important %}} -lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ - -This project is not owned by a company. I'm not an employee of a company. - -I don't have gifted domains/accounts from DNS companies. - -I've been maintaining it for about 10 years. -{{% /notice %}} - ## Features - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) @@ -24,7 +14,7 @@ I've been maintaining it for about 10 years. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [180 DNS providers]({{% ref "dns" %}}) +- Comes with about [150 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates diff --git a/docs/content/dns/_index.md b/docs/content/dns/_index.md index 2b6f0489c..7ccfeb53d 100644 --- a/docs/content/dns/_index.md +++ b/docs/content/dns/_index.md @@ -5,16 +5,6 @@ draft: false weight: 3 --- -{{% notice important %}} -lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ - -This project is not owned by a company. I'm not an employee of a company. - -I don't have gifted domains/accounts from DNS companies. - -I've been maintaining it for about 10 years. -{{% /notice %}} - ## Configuration and Credentials Credentials and DNS configuration for DNS providers must be passed through environment variables. diff --git a/providers/dns/manual/manual.toml b/docs/content/dns/manual.md similarity index 76% rename from providers/dns/manual/manual.toml rename to docs/content/dns/manual.md index fc47a8fae..3f9cf0a8e 100644 --- a/providers/dns/manual/manual.toml +++ b/docs/content/dns/manual.md @@ -1,19 +1,24 @@ -Name = "Manual" -Description = '''Solving the DNS-01 challenge using CLI prompt.''' -Code = "manual" -Since = "v0.3.0" +--- +title: "Manual" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: manual +dnsprovider: + since: v0.3.0 + code: manual + url: +--- -Example = ''' -lego --dns manual -d '*.example.com' -d example.com run -''' +Solving the DNS-01 challenge using CLI prompt. + + -Additional = ''' ## Example To start using the CLI prompt "provider", start lego with `--dns manual`: ```console -$ lego --dns manual -d example.com run +$ lego --email "you@example.com" --domains="example.com" --dns "manual" run ``` What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions: @@ -31,13 +36,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 @@ -65,5 +70,3 @@ _acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5 ``` As mentioned, you can now remove the TXT record again. - -''' diff --git a/docs/content/dns/zz_gen_acme-dns.md b/docs/content/dns/zz_gen_acme-dns.md index 5564dba1b..cb3d24016 100644 --- a/docs/content/dns/zz_gen_acme-dns.md +++ b/docs/content/dns/zz_gen_acme-dns.md @@ -28,13 +28,13 @@ Here is an example bash command using the Joohoi's ACME-DNS provider: ```bash ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ -lego --dns "acme-dns" -d '*.example.com' -d example.com run +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run # or ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ -lego --dns "acme-dns" -d '*.example.com' -d example.com run +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_active24.md b/docs/content/dns/zz_gen_active24.md index 6ec5c467a..cadc6660c 100644 --- a/docs/content/dns/zz_gen_active24.md +++ b/docs/content/dns/zz_gen_active24.md @@ -28,7 +28,7 @@ Here is an example bash command using the Active24 provider: ```bash ACTIVE24_API_KEY="xxx" \ ACTIVE24_SECRET="yyy" \ -lego --dns active24 -d '*.example.com' -d example.com run +lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_alidns.md b/docs/content/dns/zz_gen_alidns.md index 4ded782ab..7a7a36e8a 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -28,13 +28,13 @@ Here is an example bash command using the Alibaba Cloud DNS provider: ```bash # Setup using instance RAM role ALICLOUD_RAM_ROLE=lego \ -lego --dns alidns -d '*.example.com' -d example.com run +lego --email you@example.com --dns alidns -d '*.example.com' -d example.com run # Or, using credentials ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALICLOUD_SECRET_KEY=your-secret-key \ ALICLOUD_SECURITY_TOKEN=your-sts-token \ -lego --dns alidns - -d '*.example.com' -d example.com run +lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com run ``` @@ -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_aliesa.md b/docs/content/dns/zz_gen_aliesa.md deleted file mode 100644 index af28f9a4e..000000000 --- a/docs/content/dns/zz_gen_aliesa.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: "AlibabaCloud ESA" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: aliesa -dnsprovider: - since: "v4.29.0" - code: "aliesa" - url: "https://www.alibabacloud.com/en/product/esa" ---- - - - - - - -Configuration for [AlibabaCloud ESA](https://www.alibabacloud.com/en/product/esa). - - - - -- Code: `aliesa` -- Since: v4.29.0 - - -Here is an example bash command using the AlibabaCloud ESA provider: - -```bash -# Setup using instance RAM role -ALIESA_RAM_ROLE=lego \ -lego --dns aliesa -d '*.example.com' -d example.com run - -# Or, using credentials -ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ -ALIESA_SECRET_KEY=your-secret-key \ -ALIESA_SECURITY_TOKEN=your-sts-token \ -lego --dns aliesa - -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `ALIESA_ACCESS_KEY` | Access key ID | -| `ALIESA_RAM_ROLE` | Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance) | -| `ALIESA_SECRET_KEY` | Access Key secret | -| `ALIESA_SECURITY_TOKEN` | STS Security Token (optional) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `ALIESA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `ALIESA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `ALIESA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `ALIESA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records) -- [Go client](https://github.com/alibabacloud-go/esa-20240910) - - - - diff --git a/docs/content/dns/zz_gen_allinkl.md b/docs/content/dns/zz_gen_allinkl.md index 2db6ae2c5..2415c812f 100644 --- a/docs/content/dns/zz_gen_allinkl.md +++ b/docs/content/dns/zz_gen_allinkl.md @@ -28,7 +28,7 @@ Here is an example bash command using the all-inkl provider: ```bash ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --dns allinkl -d '*.example.com' -d example.com run +lego --email you@example.com --dns allinkl -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_alwaysdata.md b/docs/content/dns/zz_gen_alwaysdata.md deleted file mode 100644 index 6ec332d16..000000000 --- a/docs/content/dns/zz_gen_alwaysdata.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Alwaysdata" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: alwaysdata -dnsprovider: - since: "v4.31.0" - code: "alwaysdata" - url: "https://alwaysdata.com/" ---- - - - - - - -Configuration for [Alwaysdata](https://alwaysdata.com/). - - - - -- Code: `alwaysdata` -- Since: v4.31.0 - - -Here is an example bash command using the Alwaysdata provider: - -```bash -ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns alwaysdata -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `ALWAYSDATA_API_KEY` | API Key | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `ALWAYSDATA_ACCOUNT` | Account name | -| `ALWAYSDATA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `ALWAYSDATA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `ALWAYSDATA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `ALWAYSDATA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://help.alwaysdata.com/en/api/resources/) - - - - diff --git a/docs/content/dns/zz_gen_anexia.md b/docs/content/dns/zz_gen_anexia.md index e12ec7cfd..4256d957c 100644 --- a/docs/content/dns/zz_gen_anexia.md +++ b/docs/content/dns/zz_gen_anexia.md @@ -27,7 +27,7 @@ Here is an example bash command using the Anexia CloudDNS provider: ```bash ANEXIA_TOKEN=xxx \ -lego --dns anexia -d '*.example.com' -d example.com run +lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run ``` 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_arvancloud.md b/docs/content/dns/zz_gen_arvancloud.md index 96d495f71..b9fa1af8d 100644 --- a/docs/content/dns/zz_gen_arvancloud.md +++ b/docs/content/dns/zz_gen_arvancloud.md @@ -27,7 +27,7 @@ Here is an example bash command using the ArvanCloud provider: ```bash ARVANCLOUD_API_KEY="Apikey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \ -lego --dns arvancloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns arvancloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_auroradns.md b/docs/content/dns/zz_gen_auroradns.md index d608c85bb..9fffe34bc 100644 --- a/docs/content/dns/zz_gen_auroradns.md +++ b/docs/content/dns/zz_gen_auroradns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Aurora DNS provider: ```bash AURORA_API_KEY=xxxxx \ AURORA_SECRET=yyyyyy \ -lego --dns auroradns -d '*.example.com' -d example.com run +lego --email you@example.com --dns auroradns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_autodns.md b/docs/content/dns/zz_gen_autodns.md index f1f25e916..73f41b980 100644 --- a/docs/content/dns/zz_gen_autodns.md +++ b/docs/content/dns/zz_gen_autodns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Autodns provider: ```bash AUTODNS_API_USER=username \ AUTODNS_API_PASSWORD=supersecretpassword \ -lego --dns autodns -d '*.example.com' -d example.com run +lego --email you@example.com --dns autodns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_axelname.md b/docs/content/dns/zz_gen_axelname.md index 91476e521..b1bb3e166 100644 --- a/docs/content/dns/zz_gen_axelname.md +++ b/docs/content/dns/zz_gen_axelname.md @@ -28,7 +28,7 @@ Here is an example bash command using the Axelname provider: ```bash AXELNAME_NICKNAME="yyy" \ AXELNAME_TOKEN="xxx" \ -lego --dns axelname -d '*.example.com' -d example.com run +lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_azion.md b/docs/content/dns/zz_gen_azion.md index c5ca33552..af2a281b0 100644 --- a/docs/content/dns/zz_gen_azion.md +++ b/docs/content/dns/zz_gen_azion.md @@ -27,7 +27,7 @@ Here is an example bash command using the Azion provider: ```bash AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns azion -d '*.example.com' -d example.com run +lego --email you@example.com --dns azion -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_azuredns.md b/docs/content/dns/zz_gen_azuredns.md index 3b2586711..85feaae88 100644 --- a/docs/content/dns/zz_gen_azuredns.md +++ b/docs/content/dns/zz_gen_azuredns.md @@ -31,32 +31,32 @@ Here is an example bash command using the Azure DNS provider: AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_SECRET= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using client certificate AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_CERTIFICATE_PATH= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Azure CLI az login \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure VM) AZURE_TENANT_ID= \ AZURE_RESOURCE_GROUP= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure Arc) AZURE_TENANT_ID= \ IMDS_ENDPOINT=http://localhost:40342 \ IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_baiducloud.md b/docs/content/dns/zz_gen_baiducloud.md index 59a2f9a2d..11a71c1ab 100644 --- a/docs/content/dns/zz_gen_baiducloud.md +++ b/docs/content/dns/zz_gen_baiducloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Baidu Cloud provider: ```bash BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ -lego --dns baiducloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run ``` @@ -51,7 +51,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `BAIDUCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `BAIDUCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `BAIDUCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | +| `BAIDUCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/docs/content/dns/zz_gen_beget.md b/docs/content/dns/zz_gen_beget.md index 3f03a2ac5..ae1d16a7c 100644 --- a/docs/content/dns/zz_gen_beget.md +++ b/docs/content/dns/zz_gen_beget.md @@ -28,7 +28,7 @@ Here is an example bash command using the Beget.com provider: ```bash BEGET_USERNAME=xxxxxx \ BEGET_PASSWORD=yyyyyy \ -lego --dns beget -d '*.example.com' -d example.com run +lego --email you@example.com --dns beget -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_binarylane.md b/docs/content/dns/zz_gen_binarylane.md index eebf3c54e..4d65bb0bc 100644 --- a/docs/content/dns/zz_gen_binarylane.md +++ b/docs/content/dns/zz_gen_binarylane.md @@ -27,7 +27,7 @@ Here is an example bash command using the Binary Lane provider: ```bash BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns binarylane -d '*.example.com' -d example.com run +lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bindman.md b/docs/content/dns/zz_gen_bindman.md index fcceb8962..e12f25b7a 100644 --- a/docs/content/dns/zz_gen_bindman.md +++ b/docs/content/dns/zz_gen_bindman.md @@ -27,7 +27,7 @@ Here is an example bash command using the Bindman provider: ```bash BINDMAN_MANAGER_ADDRESS= \ -lego --dns bindman -d '*.example.com' -d example.com run +lego --email you@example.com --dns bindman -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bluecat.md b/docs/content/dns/zz_gen_bluecat.md index 2d9eb5b48..ee45c7f8b 100644 --- a/docs/content/dns/zz_gen_bluecat.md +++ b/docs/content/dns/zz_gen_bluecat.md @@ -32,7 +32,7 @@ BLUECAT_USER_NAME=myusername \ BLUECAT_CONFIG_NAME=myconfig \ BLUECAT_SERVER_URL=https://bam.example.com \ BLUECAT_TTL=30 \ -lego --dns bluecat -d '*.example.com' -d example.com run +lego --email you@example.com --dns bluecat -d '*.example.com' -d example.com run ``` 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_bookmyname.md b/docs/content/dns/zz_gen_bookmyname.md index cb7e1d3a1..3f5d1f2c3 100644 --- a/docs/content/dns/zz_gen_bookmyname.md +++ b/docs/content/dns/zz_gen_bookmyname.md @@ -28,7 +28,7 @@ Here is an example bash command using the BookMyName provider: ```bash BOOKMYNAME_USERNAME="xxx" \ BOOKMYNAME_PASSWORD="yyy" \ -lego --dns bookmyname -d '*.example.com' -d example.com run +lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_brandit.md b/docs/content/dns/zz_gen_brandit.md index fdb538684..5d1f91214 100644 --- a/docs/content/dns/zz_gen_brandit.md +++ b/docs/content/dns/zz_gen_brandit.md @@ -31,7 +31,7 @@ Here is an example bash command using the Brandit (deprecated) provider: ```bash BRANDIT_API_KEY=xxxxxxxxxxxxxxxxxxxxx \ BRANDIT_API_USERNAME=yyyyyyyyyyyyyyyyyyyy \ -lego --dns brandit -d '*.example.com' -d example.com run +lego --email you@example.com --dns brandit -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bunny.md b/docs/content/dns/zz_gen_bunny.md index 63c30782a..884c61aea 100644 --- a/docs/content/dns/zz_gen_bunny.md +++ b/docs/content/dns/zz_gen_bunny.md @@ -27,7 +27,7 @@ Here is an example bash command using the Bunny provider: ```bash BUNNY_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --dns bunny -d '*.example.com' -d example.com run +lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_checkdomain.md b/docs/content/dns/zz_gen_checkdomain.md index e0275f6c9..516d87880 100644 --- a/docs/content/dns/zz_gen_checkdomain.md +++ b/docs/content/dns/zz_gen_checkdomain.md @@ -27,7 +27,7 @@ Here is an example bash command using the Checkdomain provider: ```bash CHECKDOMAIN_TOKEN=yoursecrettoken \ -lego --dns checkdomain -d '*.example.com' -d example.com run +lego --email you@example.com --dns checkdomain -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_civo.md b/docs/content/dns/zz_gen_civo.md index 61303b539..a2cffe27c 100644 --- a/docs/content/dns/zz_gen_civo.md +++ b/docs/content/dns/zz_gen_civo.md @@ -27,7 +27,7 @@ Here is an example bash command using the Civo provider: ```bash CIVO_TOKEN=xxxxxx \ -lego --dns civo -d '*.example.com' -d example.com run +lego --email you@example.com --dns civo -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_clouddns.md b/docs/content/dns/zz_gen_clouddns.md index d10d1d6a1..27a254873 100644 --- a/docs/content/dns/zz_gen_clouddns.md +++ b/docs/content/dns/zz_gen_clouddns.md @@ -29,7 +29,7 @@ Here is an example bash command using the CloudDNS provider: CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \ CLOUDDNS_EMAIL=you@example.com \ CLOUDDNS_PASSWORD=b9841238feb177a84330f \ -lego --dns clouddns -d '*.example.com' -d example.com run +lego --email you@example.com --dns clouddns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudflare.md b/docs/content/dns/zz_gen_cloudflare.md index f3390a5fd..0fd1d440e 100644 --- a/docs/content/dns/zz_gen_cloudflare.md +++ b/docs/content/dns/zz_gen_cloudflare.md @@ -28,12 +28,12 @@ Here is an example bash command using the Cloudflare provider: ```bash CLOUDFLARE_EMAIL=you@example.com \ CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --dns cloudflare -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run # or CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns cloudflare -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudns.md b/docs/content/dns/zz_gen_cloudns.md index 26bd838f2..01d4b7815 100644 --- a/docs/content/dns/zz_gen_cloudns.md +++ b/docs/content/dns/zz_gen_cloudns.md @@ -28,7 +28,7 @@ Here is an example bash command using the ClouDNS provider: ```bash CLOUDNS_AUTH_ID=xxxx \ CLOUDNS_AUTH_PASSWORD=yyyy \ -lego --dns cloudns -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudru.md b/docs/content/dns/zz_gen_cloudru.md index 6dc3b0030..52190b031 100644 --- a/docs/content/dns/zz_gen_cloudru.md +++ b/docs/content/dns/zz_gen_cloudru.md @@ -29,7 +29,7 @@ Here is an example bash command using the Cloud.ru provider: CLOUDRU_SERVICE_INSTANCE_ID=ppp \ CLOUDRU_KEY_ID=xxx \ CLOUDRU_SECRET=yyy \ -lego --dns cloudru -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudxns.md b/docs/content/dns/zz_gen_cloudxns.md index b26e5ddb5..0b290b693 100644 --- a/docs/content/dns/zz_gen_cloudxns.md +++ b/docs/content/dns/zz_gen_cloudxns.md @@ -28,7 +28,7 @@ Here is an example bash command using the CloudXNS (Deprecated) provider: ```bash CLOUDXNS_API_KEY=xxxx \ CLOUDXNS_SECRET_KEY=yyyy \ -lego --dns cloudxns -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudxns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_com35.md b/docs/content/dns/zz_gen_com35.md deleted file mode 100644 index e2552e57c..000000000 --- a/docs/content/dns/zz_gen_com35.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "35.com/三五互联" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: com35 -dnsprovider: - since: "v4.31.0" - code: "com35" - url: "https://www.35.cn/" ---- - - - - - - -Configuration for [35.com/三五互联](https://www.35.cn/). - - - - -- Code: `com35` -- Since: v4.31.0 - - -Here is an example bash command using the 35.com/三五互联 provider: - -```bash -COM35_USERNAME="xxx" \ -COM35_PASSWORD="yyy" \ -lego --dns com35 -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `COM35_PASSWORD` | API password | -| `COM35_USERNAME` | Username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `COM35_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `COM35_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | -| `COM35_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | -| `COM35_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://api.35.cn/CustomerCenter/doc/domain_v2.html) - - - - diff --git a/docs/content/dns/zz_gen_conoha.md b/docs/content/dns/zz_gen_conoha.md index 08a979b31..4d5f84660 100644 --- a/docs/content/dns/zz_gen_conoha.md +++ b/docs/content/dns/zz_gen_conoha.md @@ -29,7 +29,7 @@ Here is an example bash command using the ConoHa v2 provider: CONOHA_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHA_API_USERNAME=xxxx \ CONOHA_API_PASSWORD=yyyy \ -lego --dns conoha -d '*.example.com' -d example.com run +lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_conohav3.md b/docs/content/dns/zz_gen_conohav3.md index e473f9434..208f2f91b 100644 --- a/docs/content/dns/zz_gen_conohav3.md +++ b/docs/content/dns/zz_gen_conohav3.md @@ -29,7 +29,7 @@ Here is an example bash command using the ConoHa v3 provider: CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHAV3_API_USER_ID=xxxx \ CONOHAV3_API_PASSWORD=yyyy \ -lego --dns conohav3 -d '*.example.com' -d example.com run +lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_constellix.md b/docs/content/dns/zz_gen_constellix.md index d4ce02bac..23628e001 100644 --- a/docs/content/dns/zz_gen_constellix.md +++ b/docs/content/dns/zz_gen_constellix.md @@ -28,7 +28,7 @@ Here is an example bash command using the Constellix provider: ```bash CONSTELLIX_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ CONSTELLIX_SECRET_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --dns constellix -d '*.example.com' -d example.com run +lego --email you@example.com --dns constellix -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_corenetworks.md b/docs/content/dns/zz_gen_corenetworks.md index 05468b1a3..dc756647e 100644 --- a/docs/content/dns/zz_gen_corenetworks.md +++ b/docs/content/dns/zz_gen_corenetworks.md @@ -28,7 +28,7 @@ Here is an example bash command using the Core-Networks provider: ```bash CORENETWORKS_LOGIN="xxxx" \ CORENETWORKS_PASSWORD="yyyy" \ -lego --dns corenetworks -d '*.example.com' -d example.com run +lego --email you@example.com --dns corenetworks -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cpanel.md b/docs/content/dns/zz_gen_cpanel.md index e5c0cc047..48cb229e7 100644 --- a/docs/content/dns/zz_gen_cpanel.md +++ b/docs/content/dns/zz_gen_cpanel.md @@ -31,7 +31,7 @@ Here is an example bash command using the CPanel/WHM provider: CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2083" \ -lego --dns cpanel -d '*.example.com' -d example.com run +lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ## WHM @@ -39,7 +39,7 @@ CPANEL_MODE=whm \ CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2087" \ -lego --dns cpanel -d '*.example.com' -d example.com run +lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ``` 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_derak.md b/docs/content/dns/zz_gen_derak.md index c5c8c7bc6..fedbf4683 100644 --- a/docs/content/dns/zz_gen_derak.md +++ b/docs/content/dns/zz_gen_derak.md @@ -27,7 +27,7 @@ Here is an example bash command using the Derak Cloud provider: ```bash DERAK_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns derak -d '*.example.com' -d example.com run +lego --email you@example.com --dns derak -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_desec.md b/docs/content/dns/zz_gen_desec.md index 4dbc713d6..977a00e06 100644 --- a/docs/content/dns/zz_gen_desec.md +++ b/docs/content/dns/zz_gen_desec.md @@ -27,7 +27,7 @@ Here is an example bash command using the deSEC.io provider: ```bash DESEC_TOKEN=x-xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns desec -d '*.example.com' -d example.com run +lego --email you@example.com --dns desec -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_designate.md b/docs/content/dns/zz_gen_designate.md index 9703f094d..74cd04920 100644 --- a/docs/content/dns/zz_gen_designate.md +++ b/docs/content/dns/zz_gen_designate.md @@ -28,7 +28,7 @@ Here is an example bash command using the Designate DNSaaS for Openstack provide ```bash # With a `clouds.yaml` OS_CLOUD=my_openstack \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run # or @@ -37,7 +37,7 @@ OS_REGION_NAME=RegionOne \ OS_PROJECT_ID=23d4522a987d4ab529f722a007c27846 OS_USERNAME=myuser \ OS_PASSWORD=passw0rd \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run # or @@ -46,7 +46,7 @@ OS_REGION_NAME=RegionOne \ OS_AUTH_TYPE=v3applicationcredential \ OS_APPLICATION_CREDENTIAL_ID=imn74uq0or7dyzz20dwo1ytls4me8dry \ OS_APPLICATION_CREDENTIAL_SECRET=68FuSPSdQqkFQYH5X1OoriEIJOwyLtQ8QSqXZOc9XxFK1A9tzZT6He2PfPw0OMja \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_digitalocean.md b/docs/content/dns/zz_gen_digitalocean.md index 4dc43886d..24307cfb0 100644 --- a/docs/content/dns/zz_gen_digitalocean.md +++ b/docs/content/dns/zz_gen_digitalocean.md @@ -27,7 +27,7 @@ Here is an example bash command using the Digital Ocean provider: ```bash DO_AUTH_TOKEN=xxxxxx \ -lego --dns digitalocean -d '*.example.com' -d example.com run +lego --email you@example.com --dns digitalocean -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_directadmin.md b/docs/content/dns/zz_gen_directadmin.md index 1d03dcc4e..006cb87d6 100644 --- a/docs/content/dns/zz_gen_directadmin.md +++ b/docs/content/dns/zz_gen_directadmin.md @@ -29,7 +29,7 @@ Here is an example bash command using the DirectAdmin provider: DIRECTADMIN_API_URL="http://example.com:2222" \ DIRECTADMIN_USERNAME=xxxx \ DIRECTADMIN_PASSWORD=yyy \ -lego --dns directadmin -d '*.example.com' -d example.com run +lego --email you@example.com --dns directadmin -d '*.example.com' -d example.com run ``` 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_dnshomede.md b/docs/content/dns/zz_gen_dnshomede.md index ca7f83523..b865578e6 100644 --- a/docs/content/dns/zz_gen_dnshomede.md +++ b/docs/content/dns/zz_gen_dnshomede.md @@ -27,10 +27,10 @@ Here is an example bash command using the dnsHome.de provider: ```bash DNSHOMEDE_CREDENTIALS=example.org:password \ -lego --dns dnshomede -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnshomede -d '*.example.com' -d example.com run DNSHOMEDE_CREDENTIALS=my.example.org:password1,demo.example.org:password2 \ -lego --dns dnshomede -d my.example.org -d demo.example.org +lego --email you@example.com --dns dnshomede -d my.example.org -d demo.example.org ``` diff --git a/docs/content/dns/zz_gen_dnsimple.md b/docs/content/dns/zz_gen_dnsimple.md index 7799ece88..d73122273 100644 --- a/docs/content/dns/zz_gen_dnsimple.md +++ b/docs/content/dns/zz_gen_dnsimple.md @@ -27,7 +27,7 @@ Here is an example bash command using the DNSimple provider: ```bash DNSIMPLE_OAUTH_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns dnsimple -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnsimple -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dnsmadeeasy.md b/docs/content/dns/zz_gen_dnsmadeeasy.md index e7f260889..572676fbd 100644 --- a/docs/content/dns/zz_gen_dnsmadeeasy.md +++ b/docs/content/dns/zz_gen_dnsmadeeasy.md @@ -28,7 +28,7 @@ Here is an example bash command using the DNS Made Easy provider: ```bash DNSMADEEASY_API_KEY=xxxxxx \ DNSMADEEASY_API_SECRET=yyyyy \ -lego --dns dnsmadeeasy -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnsmadeeasy -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dnspod.md b/docs/content/dns/zz_gen_dnspod.md index 86112a5ce..b9e906052 100644 --- a/docs/content/dns/zz_gen_dnspod.md +++ b/docs/content/dns/zz_gen_dnspod.md @@ -27,7 +27,7 @@ Here is an example bash command using the DNSPod (deprecated) provider: ```bash DNSPOD_API_KEY=xxxxxx \ -lego --dns dnspod -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnspod -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dode.md b/docs/content/dns/zz_gen_dode.md index 28eebe5fa..153650406 100644 --- a/docs/content/dns/zz_gen_dode.md +++ b/docs/content/dns/zz_gen_dode.md @@ -27,7 +27,7 @@ Here is an example bash command using the Domain Offensive (do.de) provider: ```bash DODE_TOKEN=xxxxxx \ -lego --dns dode -d '*.example.com' -d example.com run +lego --email you@example.com --dns dode -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_domeneshop.md b/docs/content/dns/zz_gen_domeneshop.md index 0530ab365..a519cfbef 100644 --- a/docs/content/dns/zz_gen_domeneshop.md +++ b/docs/content/dns/zz_gen_domeneshop.md @@ -28,7 +28,7 @@ Here is an example bash command using the Domeneshop provider: ```bash DOMENESHOP_API_TOKEN= \ DOMENESHOP_API_SECRET= \ -lego --dns domeneshop -d '*.example.com' -d example.com run +lego --email example@example.com --dns domeneshop -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dreamhost.md b/docs/content/dns/zz_gen_dreamhost.md index b9d273099..e713b8ad2 100644 --- a/docs/content/dns/zz_gen_dreamhost.md +++ b/docs/content/dns/zz_gen_dreamhost.md @@ -27,7 +27,7 @@ Here is an example bash command using the DreamHost provider: ```bash DREAMHOST_API_KEY="YOURAPIKEY" \ -lego --dns dreamhost -d '*.example.com' -d example.com run +lego --email you@example.com --dns dreamhost -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_duckdns.md b/docs/content/dns/zz_gen_duckdns.md index 8b60780d2..1290b82fd 100644 --- a/docs/content/dns/zz_gen_duckdns.md +++ b/docs/content/dns/zz_gen_duckdns.md @@ -27,7 +27,7 @@ Here is an example bash command using the Duck DNS provider: ```bash DUCKDNS_TOKEN=xxxxxx \ -lego --dns duckdns -d '*.example.com' -d example.com run +lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dyn.md b/docs/content/dns/zz_gen_dyn.md index e31a90e45..f241ea930 100644 --- a/docs/content/dns/zz_gen_dyn.md +++ b/docs/content/dns/zz_gen_dyn.md @@ -29,7 +29,7 @@ Here is an example bash command using the Dyn provider: DYN_CUSTOMER_NAME=xxxxxx \ DYN_USER_NAME=yyyyy \ DYN_PASSWORD=zzzz \ -lego --dns dyn -d '*.example.com' -d example.com run +lego --email you@example.com --dns dyn -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dyndnsfree.md b/docs/content/dns/zz_gen_dyndnsfree.md index ea549b4e2..6f4cf46ff 100644 --- a/docs/content/dns/zz_gen_dyndnsfree.md +++ b/docs/content/dns/zz_gen_dyndnsfree.md @@ -28,7 +28,7 @@ Here is an example bash command using the DynDnsFree.de provider: ```bash DYNDNSFREE_USERNAME="xxx" \ DYNDNSFREE_PASSWORD="yyy" \ -lego --dns dyndnsfree -d '*.example.com' -d example.com run +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dynu.md b/docs/content/dns/zz_gen_dynu.md index a1f3e762e..4db76456f 100644 --- a/docs/content/dns/zz_gen_dynu.md +++ b/docs/content/dns/zz_gen_dynu.md @@ -27,7 +27,7 @@ Here is an example bash command using the Dynu provider: ```bash DYNU_API_KEY=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns dynu -d '*.example.com' -d example.com run +lego --email you@example.com --dns dynu -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_easydns.md b/docs/content/dns/zz_gen_easydns.md index 12f69e09c..196e6ab7c 100644 --- a/docs/content/dns/zz_gen_easydns.md +++ b/docs/content/dns/zz_gen_easydns.md @@ -28,7 +28,7 @@ Here is an example bash command using the EasyDNS provider: ```bash EASYDNS_TOKEN=xxx \ EASYDNS_KEY=yyy \ -lego --dns easydns -d '*.example.com' -d example.com run +lego --email you@example.com --dns easydns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_edgecenter.md b/docs/content/dns/zz_gen_edgecenter.md deleted file mode 100644 index 1fd9fe5fa..000000000 --- a/docs/content/dns/zz_gen_edgecenter.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "EdgeCenter" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: edgecenter -dnsprovider: - since: "v4.29.0" - code: "edgecenter" - url: "https://edgecenter.ru/dns" ---- - - - - - - -Configuration for [EdgeCenter](https://edgecenter.ru/dns). - - - - -- Code: `edgecenter` -- Since: v4.29.0 - - -Here is an example bash command using the EdgeCenter provider: - -```bash -EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ -lego --dns edgecenter -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `EDGECENTER_PERMANENT_API_TOKEN` | Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `EDGECENTER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | -| `EDGECENTER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | -| `EDGECENTER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 360) | -| `EDGECENTER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://apidocs.edgecenter.ru/dns) - - - - diff --git a/docs/content/dns/zz_gen_edgedns.md b/docs/content/dns/zz_gen_edgedns.md index 31b191168..21d819d2c 100644 --- a/docs/content/dns/zz_gen_edgedns.md +++ b/docs/content/dns/zz_gen_edgedns.md @@ -30,7 +30,7 @@ AKAMAI_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG= \ AKAMAI_CLIENT_TOKEN=akab-mnbvcxzlkjhgfdsapoiuytrewq1234567 \ AKAMAI_HOST=akab-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.luna.akamaiapis.net \ AKAMAI_ACCESS_TOKEN=akab-1234567890qwerty-asdfghjklzxcvtnu \ -lego --dns edgedns -d '*.example.com' -d example.com run +lego --email you@example.com --dns edgedns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_edgeone.md b/docs/content/dns/zz_gen_edgeone.md index ba5de5ba2..b7b5b1eec 100644 --- a/docs/content/dns/zz_gen_edgeone.md +++ b/docs/content/dns/zz_gen_edgeone.md @@ -28,7 +28,7 @@ Here is an example bash command using the Tencent EdgeOne provider: ```bash EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ EDGEONE_SECRET_KEY=your-secret-key \ -lego --dns edgeone -d '*.example.com' -d example.com run +lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run ``` @@ -55,7 +55,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `EDGEONE_REGION` | Region | | `EDGEONE_SESSION_TOKEN` | Access Key token | | `EDGEONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | -| `EDGEONE_ZONES_MAPPING` | Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2') | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/docs/content/dns/zz_gen_efficientip.md b/docs/content/dns/zz_gen_efficientip.md index acca3ebb7..7c151e67a 100644 --- a/docs/content/dns/zz_gen_efficientip.md +++ b/docs/content/dns/zz_gen_efficientip.md @@ -30,7 +30,7 @@ EFFICIENTIP_USERNAME="user" \ EFFICIENTIP_PASSWORD="secret" \ EFFICIENTIP_HOSTNAME="ipam.example.org" \ EFFICIENTIP_DNS_NAME="dns.smart" \ -lego --dns efficientip -d '*.example.com' -d example.com run +lego --email you@example.com --dns efficientip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_epik.md b/docs/content/dns/zz_gen_epik.md index a7fc029d3..50f66e8da 100644 --- a/docs/content/dns/zz_gen_epik.md +++ b/docs/content/dns/zz_gen_epik.md @@ -27,7 +27,7 @@ Here is an example bash command using the Epik provider: ```bash EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns epik -d '*.example.com' -d example.com run +lego --email you@example.com --dns epik -d '*.example.com' -d example.com run ``` 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_exec.md b/docs/content/dns/zz_gen_exec.md index ad2e6906e..fb2b17e3d 100644 --- a/docs/content/dns/zz_gen_exec.md +++ b/docs/content/dns/zz_gen_exec.md @@ -26,7 +26,7 @@ Here is an example bash command using the External program provider: ```bash EXEC_PATH=/the/path/to/myscript.sh \ -lego --dns exec -d '*.example.com' -d example.com run +lego --email you@example.com --dns exec -d '*.example.com' -d example.com run ``` @@ -61,7 +61,7 @@ For example, requesting a certificate for the domain 'my.example.org' can be ach ```bash EXEC_PATH=./update-dns.sh \ -lego --dns exec --d my.example.org run +lego --email you@example.com --dns exec --d my.example.org run ``` It will then call the program './update-dns.sh' with like this: @@ -81,7 +81,7 @@ If you want to use the raw domain, token, and keyAuth values with your program, ```bash EXEC_MODE=RAW \ EXEC_PATH=./update-dns.sh \ -lego --dns exec -d my.example.org run +lego --email you@example.com --dns exec -d my.example.org run ``` It will then call the program `./update-dns.sh` like this: diff --git a/docs/content/dns/zz_gen_exoscale.md b/docs/content/dns/zz_gen_exoscale.md index e599d6487..5392ff573 100644 --- a/docs/content/dns/zz_gen_exoscale.md +++ b/docs/content/dns/zz_gen_exoscale.md @@ -28,7 +28,7 @@ Here is an example bash command using the Exoscale provider: ```bash EXOSCALE_API_KEY=abcdefghijklmnopqrstuvwx \ EXOSCALE_API_SECRET=xxxxxxx \ -lego --dns exoscale -d '*.example.com' -d example.com run +lego --email you@example.com --dns exoscale -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_f5xc.md b/docs/content/dns/zz_gen_f5xc.md index 0fd8fe58a..c8a664a00 100644 --- a/docs/content/dns/zz_gen_f5xc.md +++ b/docs/content/dns/zz_gen_f5xc.md @@ -29,7 +29,7 @@ Here is an example bash command using the F5 XC provider: F5XC_API_TOKEN="xxx" \ F5XC_TENANT_NAME="yyy" \ F5XC_GROUP_NAME="zzz" \ -lego --dns f5xc -d '*.example.com' -d example.com run +lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run ``` @@ -54,7 +54,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `F5XC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `F5XC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `F5XC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `F5XC_SERVER` | Server domain (Default: console.ves.volterra.io) | | `F5XC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_freemyip.md b/docs/content/dns/zz_gen_freemyip.md index 215f8eb84..d89e17c27 100644 --- a/docs/content/dns/zz_gen_freemyip.md +++ b/docs/content/dns/zz_gen_freemyip.md @@ -27,7 +27,7 @@ Here is an example bash command using the freemyip.com provider: ```bash FREEMYIP_TOKEN=xxxxxx \ -lego --dns freemyip -d '*.example.com' -d example.com run +lego --email you@example.com --dns freemyip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gandi.md b/docs/content/dns/zz_gen_gandi.md index b02d97819..961ed6873 100644 --- a/docs/content/dns/zz_gen_gandi.md +++ b/docs/content/dns/zz_gen_gandi.md @@ -27,7 +27,7 @@ Here is an example bash command using the Gandi provider: ```bash GANDI_API_KEY=abcdefghijklmnopqrstuvwx \ -lego --dns gandi -d '*.example.com' -d example.com run +lego --email you@example.com --dns gandi -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gandiv5.md b/docs/content/dns/zz_gen_gandiv5.md index 78824abbe..773bd3b08 100644 --- a/docs/content/dns/zz_gen_gandiv5.md +++ b/docs/content/dns/zz_gen_gandiv5.md @@ -27,7 +27,7 @@ Here is an example bash command using the Gandi Live DNS (v5) provider: ```bash GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \ -lego --dns gandiv5 -d '*.example.com' -d example.com run +lego --email you@example.com --dns gandiv5 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gcloud.md b/docs/content/dns/zz_gen_gcloud.md index 64acc1d1e..ff228a1c8 100644 --- a/docs/content/dns/zz_gen_gcloud.md +++ b/docs/content/dns/zz_gen_gcloud.md @@ -29,18 +29,18 @@ Here is an example bash command using the Google Cloud provider: # Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run # Using default credentials with impersonation GCE_PROJECT="gc-project-id" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run # Using service account key with impersonation GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gcore.md b/docs/content/dns/zz_gen_gcore.md index 21a7ee9b1..f2a17c3fb 100644 --- a/docs/content/dns/zz_gen_gcore.md +++ b/docs/content/dns/zz_gen_gcore.md @@ -27,7 +27,7 @@ Here is an example bash command using the G-Core provider: ```bash GCORE_PERMANENT_API_TOKEN=xxxxx \ -lego --dns gcore -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcore -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gigahostno.md b/docs/content/dns/zz_gen_gigahostno.md deleted file mode 100644 index a59ffc401..000000000 --- a/docs/content/dns/zz_gen_gigahostno.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: "Gigahost.no" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: gigahostno -dnsprovider: - since: "v4.29.0" - code: "gigahostno" - url: "https://gigahost.no/" ---- - - - - - - -Configuration for [Gigahost.no](https://gigahost.no/). - - - - -- Code: `gigahostno` -- Since: v4.29.0 - - -Here is an example bash command using the Gigahost.no provider: - -```bash -GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ -GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns gigahostno -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `GIGAHOSTNO_PASSWORD` | Password | -| `GIGAHOSTNO_USERNAME` | Username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `GIGAHOSTNO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `GIGAHOSTNO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `GIGAHOSTNO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `GIGAHOSTNO_SECRET` | TOTP secret | -| `GIGAHOSTNO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://gigahost.no/api-dokumentasjon) - - - - diff --git a/docs/content/dns/zz_gen_glesys.md b/docs/content/dns/zz_gen_glesys.md index 2d2608330..ff43cfe9a 100644 --- a/docs/content/dns/zz_gen_glesys.md +++ b/docs/content/dns/zz_gen_glesys.md @@ -28,7 +28,7 @@ Here is an example bash command using the Glesys provider: ```bash GLESYS_API_USER=xxxxx \ GLESYS_API_KEY=yyyyy \ -lego --dns glesys -d '*.example.com' -d example.com run +lego --email you@example.com --dns glesys -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_godaddy.md b/docs/content/dns/zz_gen_godaddy.md index bc51cd69b..c5392a878 100644 --- a/docs/content/dns/zz_gen_godaddy.md +++ b/docs/content/dns/zz_gen_godaddy.md @@ -28,7 +28,7 @@ Here is an example bash command using the Go Daddy provider: ```bash GODADDY_API_KEY=xxxxxxxx \ GODADDY_API_SECRET=yyyyyyyy \ -lego --dns godaddy -d '*.example.com' -d example.com run +lego --email you@example.com --dns godaddy -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_googledomains.md b/docs/content/dns/zz_gen_googledomains.md index 2421184c0..c6f6d0577 100644 --- a/docs/content/dns/zz_gen_googledomains.md +++ b/docs/content/dns/zz_gen_googledomains.md @@ -27,7 +27,7 @@ Here is an example bash command using the Google Domains provider: ```bash GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns googledomains -d '*.example.com' -d example.com run +lego --email you@example.com --dns googledomains -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gravity.md b/docs/content/dns/zz_gen_gravity.md deleted file mode 100644 index 654ad8424..000000000 --- a/docs/content/dns/zz_gen_gravity.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Gravity" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: gravity -dnsprovider: - since: "v4.30.0" - code: "gravity" - url: "https://gravity.beryju.io/" ---- - - - - - - -Configuration for [Gravity](https://gravity.beryju.io/). - - - - -- Code: `gravity` -- Since: v4.30.0 - - -Here is an example bash command using the Gravity provider: - -```bash -GRAVITY_SERVER_URL="https://example.org:1234" \ -GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ -GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns gravity -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `GRAVITY_PASSWORD` | Password | -| `GRAVITY_SERVER_URL` | URL of the server | -| `GRAVITY_USERNAME` | Username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `GRAVITY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `GRAVITY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `GRAVITY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `GRAVITY_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 1) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://gravity.beryju.io/docs/api/reference/) - - - - diff --git a/docs/content/dns/zz_gen_hetzner.md b/docs/content/dns/zz_gen_hetzner.md index 4e81bd4d9..5778a64ce 100644 --- a/docs/content/dns/zz_gen_hetzner.md +++ b/docs/content/dns/zz_gen_hetzner.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hetzner provider: ```bash HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hetzner -d '*.example.com' -d example.com run +lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostingde.md b/docs/content/dns/zz_gen_hostingde.md index 4a66fe0f1..cc86116e1 100644 --- a/docs/content/dns/zz_gen_hostingde.md +++ b/docs/content/dns/zz_gen_hostingde.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hosting.de provider: ```bash HOSTINGDE_API_KEY=xxxxxxxx \ -lego --dns hostingde -d '*.example.com' -d example.com run +lego --email you@example.com --dns hostingde -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostinger.md b/docs/content/dns/zz_gen_hostinger.md index c05b3f003..193455f63 100644 --- a/docs/content/dns/zz_gen_hostinger.md +++ b/docs/content/dns/zz_gen_hostinger.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hostinger provider: ```bash HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostinger -d '*.example.com' -d example.com run +lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostingnl.md b/docs/content/dns/zz_gen_hostingnl.md deleted file mode 100644 index 09cb69b47..000000000 --- a/docs/content/dns/zz_gen_hostingnl.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Hosting.nl" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: hostingnl -dnsprovider: - since: "v4.30.0" - code: "hostingnl" - url: "https://hosting.nl" ---- - - - - - - -Configuration for [Hosting.nl](https://hosting.nl). - - - - -- Code: `hostingnl` -- Since: v4.30.0 - - -Here is an example bash command using the Hosting.nl provider: - -```bash -HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostingnl -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `HOSTINGNL_API_KEY` | The API key | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `HOSTINGNL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | -| `HOSTINGNL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `HOSTINGNL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | -| `HOSTINGNL_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://api.hosting.nl/api/documentation) - - - - diff --git a/docs/content/dns/zz_gen_hosttech.md b/docs/content/dns/zz_gen_hosttech.md index 9435cc562..4f9f117ba 100644 --- a/docs/content/dns/zz_gen_hosttech.md +++ b/docs/content/dns/zz_gen_hosttech.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hosttech provider: ```bash HOSTTECH_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns hosttech -d '*.example.com' -d example.com run +lego --email you@example.com --dns hosttech -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_httpnet.md b/docs/content/dns/zz_gen_httpnet.md index 862909697..06883b3f8 100644 --- a/docs/content/dns/zz_gen_httpnet.md +++ b/docs/content/dns/zz_gen_httpnet.md @@ -27,7 +27,7 @@ Here is an example bash command using the http.net provider: ```bash HTTPNET_API_KEY=xxxxxxxx \ -lego --dns httpnet -d '*.example.com' -d example.com run +lego --email you@example.com --dns httpnet -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_httpreq.md b/docs/content/dns/zz_gen_httpreq.md index 7f6a8d576..9c6476802 100644 --- a/docs/content/dns/zz_gen_httpreq.md +++ b/docs/content/dns/zz_gen_httpreq.md @@ -27,7 +27,7 @@ Here is an example bash command using the HTTP request provider: ```bash HTTPREQ_ENDPOINT=http://my.server.com:9090 \ -lego --dns httpreq -d '*.example.com' -d example.com run +lego --email you@example.com --dns httpreq -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_huaweicloud.md b/docs/content/dns/zz_gen_huaweicloud.md index 46d121265..9a37a8878 100644 --- a/docs/content/dns/zz_gen_huaweicloud.md +++ b/docs/content/dns/zz_gen_huaweicloud.md @@ -29,7 +29,7 @@ Here is an example bash command using the Huawei Cloud provider: HUAWEICLOUD_ACCESS_KEY_ID=your-access-key-id \ HUAWEICLOUD_SECRET_ACCESS_KEY=your-secret-access-key \ HUAWEICLOUD_REGION=cn-south-1 \ -lego --dns huaweicloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns huaweicloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hurricane.md b/docs/content/dns/zz_gen_hurricane.md index 0c195d19c..da78630d4 100644 --- a/docs/content/dns/zz_gen_hurricane.md +++ b/docs/content/dns/zz_gen_hurricane.md @@ -27,10 +27,10 @@ Here is an example bash command using the Hurricane Electric DNS provider: ```bash HURRICANE_TOKENS=example.org:token \ -lego --dns hurricane -d '*.example.com' -d example.com run +lego --email you@example.com --dns hurricane -d '*.example.com' -d example.com run HURRICANE_TOKENS=my.example.org:token1,demo.example.org:token2 \ -lego --dns hurricane -d my.example.org -d demo.example.org +lego --email you@example.com --dns hurricane -d my.example.org -d demo.example.org ``` diff --git a/docs/content/dns/zz_gen_hyperone.md b/docs/content/dns/zz_gen_hyperone.md index bc496f7bc..83dfdb111 100644 --- a/docs/content/dns/zz_gen_hyperone.md +++ b/docs/content/dns/zz_gen_hyperone.md @@ -26,7 +26,7 @@ Configuration for [HyperOne](https://www.hyperone.com). Here is an example bash command using the HyperOne provider: ```bash -lego --dns hyperone -d '*.example.com' -d example.com run +lego --email you@example.com --dns hyperone -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ibmcloud.md b/docs/content/dns/zz_gen_ibmcloud.md index c5a48d2ad..94997b703 100644 --- a/docs/content/dns/zz_gen_ibmcloud.md +++ b/docs/content/dns/zz_gen_ibmcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the IBM Cloud (SoftLayer) provider: ```bash SOFTLAYER_USERNAME=xxxxx \ SOFTLAYER_API_KEY=yyyyy \ -lego --dns ibmcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_iij.md b/docs/content/dns/zz_gen_iij.md index c7acfe3a0..8c73f58a5 100644 --- a/docs/content/dns/zz_gen_iij.md +++ b/docs/content/dns/zz_gen_iij.md @@ -29,7 +29,7 @@ Here is an example bash command using the Internet Initiative Japan provider: IIJ_API_ACCESS_KEY=xxxxxxxx \ IIJ_API_SECRET_KEY=yyyyyy \ IIJ_DO_SERVICE_CODE=zzzzzz \ -lego --dns iij -d '*.example.com' -d example.com run +lego --email you@example.com --dns iij -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_iijdpf.md b/docs/content/dns/zz_gen_iijdpf.md index 12e126f49..7c694fc32 100644 --- a/docs/content/dns/zz_gen_iijdpf.md +++ b/docs/content/dns/zz_gen_iijdpf.md @@ -28,7 +28,7 @@ Here is an example bash command using the IIJ DNS Platform Service provider: ```bash IIJ_DPF_API_TOKEN=xxxxxxxx \ IIJ_DPF_DPM_SERVICE_CODE=yyyyyy \ -lego --dns iijdpf -d '*.example.com' -d example.com run +lego --email you@example.com --dns iijdpf -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_infoblox.md b/docs/content/dns/zz_gen_infoblox.md index 74b80b2d1..2d07628f3 100644 --- a/docs/content/dns/zz_gen_infoblox.md +++ b/docs/content/dns/zz_gen_infoblox.md @@ -29,7 +29,7 @@ Here is an example bash command using the Infoblox provider: INFOBLOX_USERNAME=api-user-529 \ INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ INFOBLOX_HOST=infoblox.example.org -lego --dns infoblox -d '*.example.com' -d example.com run +lego --email you@example.com --dns infoblox -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_infomaniak.md b/docs/content/dns/zz_gen_infomaniak.md index 7254241b1..be02d8ee8 100644 --- a/docs/content/dns/zz_gen_infomaniak.md +++ b/docs/content/dns/zz_gen_infomaniak.md @@ -27,7 +27,7 @@ Here is an example bash command using the Infomaniak provider: ```bash INFOMANIAK_ACCESS_TOKEN=1234567898765432 \ -lego --dns infomaniak -d '*.example.com' -d example.com run +lego --email you@example.com --dns infomaniak -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_internetbs.md b/docs/content/dns/zz_gen_internetbs.md index f0d9df3c1..e98fbf4b9 100644 --- a/docs/content/dns/zz_gen_internetbs.md +++ b/docs/content/dns/zz_gen_internetbs.md @@ -28,7 +28,7 @@ Here is an example bash command using the Internet.bs provider: ```bash INTERNET_BS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ INTERNET_BS_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --dns internetbs -d '*.example.com' -d example.com run +lego --email you@example.com --dns internetbs -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_inwx.md b/docs/content/dns/zz_gen_inwx.md index 3e7d999e9..a46ff061e 100644 --- a/docs/content/dns/zz_gen_inwx.md +++ b/docs/content/dns/zz_gen_inwx.md @@ -28,13 +28,13 @@ Here is an example bash command using the INWX provider: ```bash INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ -lego --dns inwx -d '*.example.com' -d example.com run +lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run # 2FA INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ INWX_SHARED_SECRET=zzzzzzzzzz \ -lego --dns inwx -d '*.example.com' -d example.com run +lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ionos.md b/docs/content/dns/zz_gen_ionos.md index 78bd3ffb1..60a2ede03 100644 --- a/docs/content/dns/zz_gen_ionos.md +++ b/docs/content/dns/zz_gen_ionos.md @@ -27,7 +27,7 @@ Here is an example bash command using the Ionos provider: ```bash IONOS_API_KEY=xxxxxxxx \ -lego --dns ionos -d '*.example.com' -d example.com run +lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ionoscloud.md b/docs/content/dns/zz_gen_ionoscloud.md deleted file mode 100644 index 6007670a7..000000000 --- a/docs/content/dns/zz_gen_ionoscloud.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Ionos Cloud" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: ionoscloud -dnsprovider: - since: "v4.30.0" - code: "ionoscloud" - url: "https://cloud.ionos.de/network/cloud-dns" ---- - - - - - - -Configuration for [Ionos Cloud](https://cloud.ionos.de/network/cloud-dns). - - - - -- Code: `ionoscloud` -- Since: v4.30.0 - - -Here is an example bash command using the Ionos Cloud provider: - -```bash -IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns ionoscloud -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `IONOSCLOUD_API_TOKEN` | API token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `IONOSCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `IONOSCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `IONOSCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | -| `IONOSCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://api.ionos.com/docs/dns/v1/) - - - - diff --git a/docs/content/dns/zz_gen_ipv64.md b/docs/content/dns/zz_gen_ipv64.md index 00a0292a6..21327caaf 100644 --- a/docs/content/dns/zz_gen_ipv64.md +++ b/docs/content/dns/zz_gen_ipv64.md @@ -27,7 +27,7 @@ Here is an example bash command using the IPv64 provider: ```bash IPV64_API_KEY=xxxxxx \ -lego --dns ipv64 -d '*.example.com' -d example.com run +lego --email you@example.com --dns ipv64 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ispconfig.md b/docs/content/dns/zz_gen_ispconfig.md deleted file mode 100644 index e56f1f0b1..000000000 --- a/docs/content/dns/zz_gen_ispconfig.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: "ISPConfig 3" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: ispconfig -dnsprovider: - since: "v4.31.0" - code: "ispconfig" - url: "https://www.ispconfig.org/" ---- - - - - - - -Configuration for [ISPConfig 3](https://www.ispconfig.org/). - - - - -- Code: `ispconfig` -- Since: v4.31.0 - - -Here is an example bash command using the ISPConfig 3 provider: - -```bash -ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ -ISPCONFIG_USERNAME="xxx" \ -ISPCONFIG_PASSWORD="yyy" \ -lego --dns ispconfig -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `ISPCONFIG_PASSWORD` | Password | -| `ISPCONFIG_SERVER_URL` | Server URL | -| `ISPCONFIG_USERNAME` | Username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `ISPCONFIG_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `ISPCONFIG_INSECURE_SKIP_VERIFY` | Whether to verify the API certificate | -| `ISPCONFIG_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `ISPCONFIG_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `ISPCONFIG_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/index.html) - - - - diff --git a/docs/content/dns/zz_gen_ispconfigddns.md b/docs/content/dns/zz_gen_ispconfigddns.md deleted file mode 100644 index 3d1dd83c3..000000000 --- a/docs/content/dns/zz_gen_ispconfigddns.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: "ISPConfig 3 - Dynamic DNS (DDNS) Module" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: ispconfigddns -dnsprovider: - since: "v4.31.0" - code: "ispconfigddns" - url: "https://www.ispconfig.org/" ---- - - - - - - -Configuration for [ISPConfig 3 - Dynamic DNS (DDNS) Module](https://www.ispconfig.org/). - - - - -- Code: `ispconfigddns` -- Since: v4.31.0 - - -Here is an example bash command using the ISPConfig 3 - Dynamic DNS (DDNS) Module provider: - -```bash -ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ -ISPCONFIG_DDNS_TOKEN=xxxxxx \ -lego --dns ispconfigddns -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `ISPCONFIG_DDNS_SERVER_URL` | API server URL (ex: https://panel.example.com:8080) | -| `ISPCONFIG_DDNS_TOKEN` | DDNS API token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `ISPCONFIG_DDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `ISPCONFIG_DDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `ISPCONFIG_DDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `ISPCONFIG_DDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - -ISPConfig DNS provider supports leveraging the [ISPConfig 3 Dynamic DNS (DDNS) Module](https://github.com/mhofer117/ispconfig-ddns-module). - -Requires the DDNS module described at https://www.ispconfig.org/ispconfig/download/ - -See https://www.howtoforge.com/community/threads/ispconfig-3-danymic-dns-ddns-module.87967/ for additional details. - - - -## More information - -- [API documentation](https://github.com/mhofer117/ispconfig-ddns-module/tree/master/lib/updater) - - - - diff --git a/docs/content/dns/zz_gen_iwantmyname.md b/docs/content/dns/zz_gen_iwantmyname.md index 4638e1379..cbdb29cb3 100644 --- a/docs/content/dns/zz_gen_iwantmyname.md +++ b/docs/content/dns/zz_gen_iwantmyname.md @@ -30,7 +30,7 @@ Here is an example bash command using the iwantmyname (Deprecated) provider: ```bash IWANTMYNAME_USERNAME=xxxxxxxx \ IWANTMYNAME_PASSWORD=xxxxxxxx \ -lego --dns iwantmyname -d '*.example.com' -d example.com run +lego --email you@example.com --dns iwantmyname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_jdcloud.md b/docs/content/dns/zz_gen_jdcloud.md deleted file mode 100644 index a37cc3520..000000000 --- a/docs/content/dns/zz_gen_jdcloud.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "JD Cloud" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: jdcloud -dnsprovider: - since: "v4.31.0" - code: "jdcloud" - url: "https://www.jdcloud.com/" ---- - - - - - - -Configuration for [JD Cloud](https://www.jdcloud.com/). - - - - -- Code: `jdcloud` -- Since: v4.31.0 - - -Here is an example bash command using the JD Cloud provider: - -```bash -JDCLOUD_ACCESS_KEY_ID="xxx" \ -JDCLOUD_ACCESS_KEY_SECRET="yyy" \ -lego --dns jdcloud -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `JDCLOUD_ACCESS_KEY_ID` | Access key ID | -| `JDCLOUD_ACCESS_KEY_SECRET` | Access key secret | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `JDCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `JDCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `JDCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `JDCLOUD_REGION_ID` | Region ID (Default: cn-north-1) | -| `JDCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://docs.jdcloud.com/cn/jd-cloud-dns/api/overview) -- [Go client](https://github.com/jdcloud-api/jdcloud-sdk-go) - - - - diff --git a/docs/content/dns/zz_gen_joker.md b/docs/content/dns/zz_gen_joker.md index a5ecd47de..c8d55b2f7 100644 --- a/docs/content/dns/zz_gen_joker.md +++ b/docs/content/dns/zz_gen_joker.md @@ -30,17 +30,17 @@ Here is an example bash command using the Joker provider: JOKER_API_MODE=SVC \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run # DMAPI JOKER_API_MODE=DMAPI \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run ## or JOKER_API_MODE=DMAPI \ JOKER_API_KEY= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_keyhelp.md b/docs/content/dns/zz_gen_keyhelp.md index e39d3ce82..2886a0a8e 100644 --- a/docs/content/dns/zz_gen_keyhelp.md +++ b/docs/content/dns/zz_gen_keyhelp.md @@ -28,7 +28,7 @@ Here is an example bash command using the KeyHelp provider: ```bash KEYHELP_BASE_URL="https://keyhelp.example.com" \ KEYHELP_API_KEY="xxx" \ -lego --dns keyhelp -d '*.example.com' -d example.com run +lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run ``` 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..2c3d59ae0 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -27,7 +27,7 @@ Here is an example bash command using the Liara provider: ```bash LIARA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns liara -d '*.example.com' -d example.com run +lego --email you@example.com --dns liara -d '*.example.com' -d example.com run ``` @@ -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_limacity.md b/docs/content/dns/zz_gen_limacity.md index 29bc6e0a7..2a01814e5 100644 --- a/docs/content/dns/zz_gen_limacity.md +++ b/docs/content/dns/zz_gen_limacity.md @@ -27,7 +27,7 @@ Here is an example bash command using the Lima-City provider: ```bash LIMACITY_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns limacity -d '*.example.com' -d example.com run +lego --email you@example.com --dns limacity -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_linode.md b/docs/content/dns/zz_gen_linode.md index e41ba7cd9..8c8487541 100644 --- a/docs/content/dns/zz_gen_linode.md +++ b/docs/content/dns/zz_gen_linode.md @@ -27,7 +27,7 @@ Here is an example bash command using the Linode (v4) provider: ```bash LINODE_TOKEN=xxxxx \ -lego --dns linode -d '*.example.com' -d example.com run +lego --email you@example.com --dns linode -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_liquidweb.md b/docs/content/dns/zz_gen_liquidweb.md index bd2ce63b6..9d8fe8c9c 100644 --- a/docs/content/dns/zz_gen_liquidweb.md +++ b/docs/content/dns/zz_gen_liquidweb.md @@ -28,7 +28,7 @@ Here is an example bash command using the Liquid Web provider: ```bash LWAPI_USERNAME=someuser \ LWAPI_PASSWORD=somepass \ -lego --dns liquidweb -d '*.example.com' -d example.com run +lego --email you@example.com --dns liquidweb -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_loopia.md b/docs/content/dns/zz_gen_loopia.md index bb3120c00..3951de8e1 100644 --- a/docs/content/dns/zz_gen_loopia.md +++ b/docs/content/dns/zz_gen_loopia.md @@ -28,7 +28,7 @@ Here is an example bash command using the Loopia provider: ```bash LOOPIA_API_USER=xxxxxxxx \ LOOPIA_API_PASSWORD=yyyyyyyy \ -lego --dns loopia -d '*.example.com' -d example.com run +lego --email you@example.com --dns loopia -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_luadns.md b/docs/content/dns/zz_gen_luadns.md index 8bf718ba3..c987cc9bf 100644 --- a/docs/content/dns/zz_gen_luadns.md +++ b/docs/content/dns/zz_gen_luadns.md @@ -28,7 +28,7 @@ Here is an example bash command using the LuaDNS provider: ```bash LUADNS_API_USERNAME=youremail \ LUADNS_API_TOKEN=xxxxxxxx \ -lego --dns luadns -d '*.example.com' -d example.com run +lego --email you@example.com --dns luadns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mailinabox.md b/docs/content/dns/zz_gen_mailinabox.md index 62a6bdb57..3ffed1cc7 100644 --- a/docs/content/dns/zz_gen_mailinabox.md +++ b/docs/content/dns/zz_gen_mailinabox.md @@ -29,7 +29,7 @@ Here is an example bash command using the Mail-in-a-Box provider: MAILINABOX_EMAIL=user@example.com \ MAILINABOX_PASSWORD=yyyy \ MAILINABOX_BASE_URL=https://box.example.com \ -lego --dns mailinabox -d '*.example.com' -d example.com run +lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_manageengine.md b/docs/content/dns/zz_gen_manageengine.md index a39db8208..32b3a3aeb 100644 --- a/docs/content/dns/zz_gen_manageengine.md +++ b/docs/content/dns/zz_gen_manageengine.md @@ -28,7 +28,7 @@ Here is an example bash command using the ManageEngine CloudDNS provider: ```bash MANAGEENGINE_CLIENT_ID="xxx" \ MANAGEENGINE_CLIENT_SECRET="yyy" \ -lego --dns manageengine -d '*.example.com' -d example.com run +lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_manual.md b/docs/content/dns/zz_gen_manual.md deleted file mode 100644 index 832ccaf58..000000000 --- a/docs/content/dns/zz_gen_manual.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: "Manual" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: manual -dnsprovider: - since: "v0.3.0" - code: "manual" - url: "" ---- - - - - - -Solving the DNS-01 challenge using CLI prompt. - - - - -- Code: `manual` -- Since: v0.3.0 - - -Here is an example bash command using the Manual provider: - -```bash -lego --dns manual -d '*.example.com' -d example.com run -``` - - - - -## Example - -To start using the CLI prompt "provider", start lego with `--dns manual`: - -```console -$ lego --dns manual -d example.com run -``` - -What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions: - -```txt -No key found for account you@example.com. Generating a P256 key. -Saved key to ./.lego/accounts/acme-v02.api.letsencrypt.org/you@example.com/keys/you@example.com.key -Please review the TOS at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf -Do you accept the TOS? Y/n -``` - -If you accept the linked Terms of Service, hit `Enter`. - -```txt -[INFO] acme: Registering account for you@example.com -!!!! HEADS UP !!!! - -Your account credentials have been saved in your -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. -[INFO] [example.com] acme: Obtaining bundled SAN certificate -[INFO] [example.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/2345678901 -[INFO] [example.com] acme: Could not find solver for: tls-alpn-01 -[INFO] [example.com] acme: Could not find solver for: http-01 -[INFO] [example.com] acme: use dns-01 solver -[INFO] [example.com] acme: Preparing to solve DNS-01 -lego: Please create the following TXT record in your example.com. zone: -_acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5q2JQ" -lego: Press 'Enter' when you are done -``` - -Do as instructed, and create the TXT records, and hit `Enter`. - -```txt -[INFO] [example.com] acme: Trying to solve DNS-01 -[INFO] [example.com] acme: Checking DNS record propagation using [192.168.8.1:53] -[INFO] Wait for propagation [timeout: 1m0s, interval: 2s] -[INFO] [example.com] acme: Waiting for DNS record propagation. -[INFO] [example.com] The server validated our request -[INFO] [example.com] acme: Cleaning DNS-01 challenge -lego: You can now remove this TXT record from your example.com. zone: -_acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5q2JQ" -[INFO] [example.com] acme: Validations succeeded; requesting certificates -[INFO] [example.com] Server responded with a certificate. -``` - -As mentioned, you can now remove the TXT record again. - - - - - - - - diff --git a/docs/content/dns/zz_gen_metaname.md b/docs/content/dns/zz_gen_metaname.md index 156cf15eb..a90d0170b 100644 --- a/docs/content/dns/zz_gen_metaname.md +++ b/docs/content/dns/zz_gen_metaname.md @@ -28,7 +28,7 @@ Here is an example bash command using the Metaname provider: ```bash METANAME_ACCOUNT_REFERENCE=xxxx \ METANAME_API_KEY=yyyyyyy \ -lego --dns metaname -d '*.example.com' -d example.com run +lego --email you@example.com --dns metaname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_metaregistrar.md b/docs/content/dns/zz_gen_metaregistrar.md index 22de046e2..63cc2bebc 100644 --- a/docs/content/dns/zz_gen_metaregistrar.md +++ b/docs/content/dns/zz_gen_metaregistrar.md @@ -27,7 +27,7 @@ Here is an example bash command using the Metaregistrar provider: ```bash METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns metaregistrar -d '*.example.com' -d example.com run +lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mijnhost.md b/docs/content/dns/zz_gen_mijnhost.md index 3d8f71aff..42abc6558 100644 --- a/docs/content/dns/zz_gen_mijnhost.md +++ b/docs/content/dns/zz_gen_mijnhost.md @@ -27,7 +27,7 @@ Here is an example bash command using the mijn.host provider: ```bash MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns mijnhost -d '*.example.com' -d example.com run +lego --email you@example.com --dns mijnhost -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mittwald.md b/docs/content/dns/zz_gen_mittwald.md index 7714ef54f..943397ee9 100644 --- a/docs/content/dns/zz_gen_mittwald.md +++ b/docs/content/dns/zz_gen_mittwald.md @@ -27,7 +27,7 @@ Here is an example bash command using the Mittwald provider: ```bash MITTWALD_TOKEN=my-token \ -lego --dns mittwald -d '*.example.com' -d example.com run +lego --email you@example.com --dns mittwald -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_myaddr.md b/docs/content/dns/zz_gen_myaddr.md index 4a52a058b..277a0bf06 100644 --- a/docs/content/dns/zz_gen_myaddr.md +++ b/docs/content/dns/zz_gen_myaddr.md @@ -27,7 +27,7 @@ Here is an example bash command using the myaddr.{tools,dev,io} provider: ```bash MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ -lego --dns myaddr -d '*.example.com' -d example.com run +lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mydnsjp.md b/docs/content/dns/zz_gen_mydnsjp.md index 0a49404bb..5b29266db 100644 --- a/docs/content/dns/zz_gen_mydnsjp.md +++ b/docs/content/dns/zz_gen_mydnsjp.md @@ -28,7 +28,7 @@ Here is an example bash command using the MyDNS.jp provider: ```bash MYDNSJP_MASTER_ID=xxxxx \ MYDNSJP_PASSWORD=xxxxx \ -lego --dns mydnsjp -d '*.example.com' -d example.com run +lego --email you@example.com --dns mydnsjp -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mythicbeasts.md b/docs/content/dns/zz_gen_mythicbeasts.md index 70e38d249..37feebf8c 100644 --- a/docs/content/dns/zz_gen_mythicbeasts.md +++ b/docs/content/dns/zz_gen_mythicbeasts.md @@ -28,7 +28,7 @@ Here is an example bash command using the MythicBeasts provider: ```bash MYTHICBEASTS_USERNAME=myuser \ MYTHICBEASTS_PASSWORD=mypass \ -lego --dns mythicbeasts -d '*.example.com' -d example.com run +lego --email you@example.com --dns mythicbeasts -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namecheap.md b/docs/content/dns/zz_gen_namecheap.md index 9d7143d84..706651660 100644 --- a/docs/content/dns/zz_gen_namecheap.md +++ b/docs/content/dns/zz_gen_namecheap.md @@ -33,7 +33,7 @@ Here is an example bash command using the Namecheap provider: ```bash NAMECHEAP_API_USER=user \ NAMECHEAP_API_KEY=key \ -lego --dns namecheap -d '*.example.com' -d example.com run +lego --email you@example.com --dns namecheap -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namedotcom.md b/docs/content/dns/zz_gen_namedotcom.md index 2860ff0ae..36a423faa 100644 --- a/docs/content/dns/zz_gen_namedotcom.md +++ b/docs/content/dns/zz_gen_namedotcom.md @@ -28,7 +28,7 @@ Here is an example bash command using the Name.com provider: ```bash NAMECOM_USERNAME=foo.bar \ NAMECOM_API_TOKEN=a379a6f6eeafb9a55e378c118034e2751e682fab \ -lego --dns namedotcom -d '*.example.com' -d example.com run +lego --email you@example.com --dns namedotcom -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namesilo.md b/docs/content/dns/zz_gen_namesilo.md index 207a1603f..397a1a3ca 100644 --- a/docs/content/dns/zz_gen_namesilo.md +++ b/docs/content/dns/zz_gen_namesilo.md @@ -27,7 +27,7 @@ Here is an example bash command using the Namesilo provider: ```bash NAMESILO_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --dns namesilo -d '*.example.com' -d example.com run +lego --email you@example.com --dns namesilo -d '*.example.com' -d example.com run ``` 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_nearlyfreespeech.md b/docs/content/dns/zz_gen_nearlyfreespeech.md index 31402d2d2..86f6152f9 100644 --- a/docs/content/dns/zz_gen_nearlyfreespeech.md +++ b/docs/content/dns/zz_gen_nearlyfreespeech.md @@ -28,7 +28,7 @@ Here is an example bash command using the NearlyFreeSpeech.NET provider: ```bash NEARLYFREESPEECH_API_KEY=xxxxxx \ NEARLYFREESPEECH_LOGIN=xxxx \ -lego --dns nearlyfreespeech -d '*.example.com' -d example.com run +lego --email you@example.com --dns nearlyfreespeech -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_neodigit.md b/docs/content/dns/zz_gen_neodigit.md deleted file mode 100644 index aefeef4bf..000000000 --- a/docs/content/dns/zz_gen_neodigit.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Neodigit" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: neodigit -dnsprovider: - since: "v4.30.0" - code: "neodigit" - url: "https://www.neodigit.net" ---- - - - - - - -Configuration for [Neodigit](https://www.neodigit.net). - - - - -- Code: `neodigit` -- Since: v4.30.0 - - -Here is an example bash command using the Neodigit provider: - -```bash -NEODIGIT_TOKEN=xxxxxx \ -lego --dns neodigit -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `NEODIGIT_TOKEN` | API token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `NEODIGIT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `NEODIGIT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | -| `NEODIGIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | -| `NEODIGIT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://developers.neodigit.net/#dns) - - - - diff --git a/docs/content/dns/zz_gen_netcup.md b/docs/content/dns/zz_gen_netcup.md index 29def3285..337baf59d 100644 --- a/docs/content/dns/zz_gen_netcup.md +++ b/docs/content/dns/zz_gen_netcup.md @@ -29,7 +29,7 @@ Here is an example bash command using the Netcup provider: NETCUP_CUSTOMER_NUMBER=xxxx \ NETCUP_API_KEY=yyyy \ NETCUP_API_PASSWORD=zzzz \ -lego --dns netcup -d '*.example.com' -d example.com run +lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_netlify.md b/docs/content/dns/zz_gen_netlify.md index 76651d9ef..b08f650f0 100644 --- a/docs/content/dns/zz_gen_netlify.md +++ b/docs/content/dns/zz_gen_netlify.md @@ -27,7 +27,7 @@ Here is an example bash command using the Netlify provider: ```bash NETLIFY_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns netlify -d '*.example.com' -d example.com run +lego --email you@example.com --dns netlify -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nicmanager.md b/docs/content/dns/zz_gen_nicmanager.md index a29d72120..0b6e1b2cb 100644 --- a/docs/content/dns/zz_gen_nicmanager.md +++ b/docs/content/dns/zz_gen_nicmanager.md @@ -34,7 +34,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --dns nicmanager -d '*.example.com' -d example.com run +lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run ## Login using account name + username @@ -45,7 +45,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --dns nicmanager -d '*.example.com' -d example.com run +lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nicru.md b/docs/content/dns/zz_gen_nicru.md index 3ac8d99cf..d55477a32 100644 --- a/docs/content/dns/zz_gen_nicru.md +++ b/docs/content/dns/zz_gen_nicru.md @@ -30,7 +30,7 @@ NICRU_USER="" \ NICRU_PASSWORD="" \ NICRU_SERVICE_ID="" \ NICRU_SECRET="" \ -lego --dns nicru -d '*.example.com' -d example.com run +lego --dns nicru --domains "*.example.com" --email you@example.com run ``` diff --git a/docs/content/dns/zz_gen_nifcloud.md b/docs/content/dns/zz_gen_nifcloud.md index 66f38223b..9b9929ce2 100644 --- a/docs/content/dns/zz_gen_nifcloud.md +++ b/docs/content/dns/zz_gen_nifcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the NIFCloud provider: ```bash NIFCLOUD_ACCESS_KEY_ID=xxxx \ NIFCLOUD_SECRET_ACCESS_KEY=yyyy \ -lego --dns nifcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns nifcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_njalla.md b/docs/content/dns/zz_gen_njalla.md index 9a312df8b..cf268041c 100644 --- a/docs/content/dns/zz_gen_njalla.md +++ b/docs/content/dns/zz_gen_njalla.md @@ -27,7 +27,7 @@ Here is an example bash command using the Njalla provider: ```bash NJALLA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns njalla -d '*.example.com' -d example.com run +lego --email you@example.com --dns njalla -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nodion.md b/docs/content/dns/zz_gen_nodion.md index 8d61eb834..c11759e8e 100644 --- a/docs/content/dns/zz_gen_nodion.md +++ b/docs/content/dns/zz_gen_nodion.md @@ -27,7 +27,7 @@ Here is an example bash command using the Nodion provider: ```bash NODION_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns nodion -d '*.example.com' -d example.com run +lego --email you@example.com --dns nodion -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ns1.md b/docs/content/dns/zz_gen_ns1.md index b2262169d..547a51c1c 100644 --- a/docs/content/dns/zz_gen_ns1.md +++ b/docs/content/dns/zz_gen_ns1.md @@ -27,7 +27,7 @@ Here is an example bash command using the NS1 provider: ```bash NS1_API_KEY=xxxx \ -lego --dns ns1 -d '*.example.com' -d example.com run +lego --email you@example.com --dns ns1 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_octenium.md b/docs/content/dns/zz_gen_octenium.md index f25da4f44..874c4e780 100644 --- a/docs/content/dns/zz_gen_octenium.md +++ b/docs/content/dns/zz_gen_octenium.md @@ -27,7 +27,7 @@ Here is an example bash command using the Octenium provider: ```bash OCTENIUM_API_KEY="xxx" \ -lego --dns octenium -d '*.example.com' -d example.com run +lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index b7192f380..c43c24b21 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -34,13 +34,13 @@ OCI_USER_OCID="ocid1.user.oc1..secret" \ OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --dns oraclecloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run # Using Instance Principal authentication (when running on OCI compute instances): # https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm OCI_AUTH_TYPE="instance_principal" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --dns oraclecloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index 9da69c694..4f3679fa2 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -30,7 +30,7 @@ OTC_DOMAIN_NAME=domain_name \ OTC_USER_NAME=user_name \ OTC_PASSWORD=password \ OTC_PROJECT_NAME=project_name \ -lego --dns otc -d '*.example.com' -d example.com run +lego --email you@example.com --dns otc -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ovh.md b/docs/content/dns/zz_gen_ovh.md index aaafded85..7abc01b92 100644 --- a/docs/content/dns/zz_gen_ovh.md +++ b/docs/content/dns/zz_gen_ovh.md @@ -32,20 +32,20 @@ OVH_APPLICATION_KEY=1234567898765432 \ OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \ OVH_CONSUMER_KEY=256vfsd347245sdfg \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run # Or Access Token: OVH_ACCESS_TOKEN=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run # Or OAuth2: OVH_CLIENT_ID=yyy \ OVH_CLIENT_SECRET=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_pdns.md b/docs/content/dns/zz_gen_pdns.md index 7c2a8c663..34a22cf84 100644 --- a/docs/content/dns/zz_gen_pdns.md +++ b/docs/content/dns/zz_gen_pdns.md @@ -28,7 +28,7 @@ Here is an example bash command using the PowerDNS provider: ```bash PDNS_API_URL=http://pdns-server:80/ \ PDNS_API_KEY=xxxx \ -lego --dns pdns -d '*.example.com' -d example.com run +lego --email you@example.com --dns pdns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_plesk.md b/docs/content/dns/zz_gen_plesk.md index 73ec9a55d..b18b2656a 100644 --- a/docs/content/dns/zz_gen_plesk.md +++ b/docs/content/dns/zz_gen_plesk.md @@ -29,7 +29,7 @@ Here is an example bash command using the plesk.com provider: PLESK_SERVER_BASE_URL="https://plesk.myserver.com:8443" \ PLESK_USERNAME=xxxxxx \ PLESK_PASSWORD=yyyyyy \ -lego --dns plesk -d '*.example.com' -d example.com run +lego --email you@example.com --dns plesk -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_porkbun.md b/docs/content/dns/zz_gen_porkbun.md index f54e6f688..9fd230d0d 100644 --- a/docs/content/dns/zz_gen_porkbun.md +++ b/docs/content/dns/zz_gen_porkbun.md @@ -28,7 +28,7 @@ Here is an example bash command using the Porkbun provider: ```bash PORKBUN_SECRET_API_KEY=xxxxxx \ PORKBUN_API_KEY=yyyyyy \ -lego --dns porkbun -d '*.example.com' -d example.com run +lego --email you@example.com --dns porkbun -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rackspace.md b/docs/content/dns/zz_gen_rackspace.md index b9a2ab710..6dcf6b2b2 100644 --- a/docs/content/dns/zz_gen_rackspace.md +++ b/docs/content/dns/zz_gen_rackspace.md @@ -28,7 +28,7 @@ Here is an example bash command using the Rackspace provider: ```bash RACKSPACE_USER=xxxx \ RACKSPACE_API_KEY=yyyy \ -lego --dns rackspace -d '*.example.com' -d example.com run +lego --email you@example.com --dns rackspace -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rainyun.md b/docs/content/dns/zz_gen_rainyun.md index 680eb845a..74ced9f54 100644 --- a/docs/content/dns/zz_gen_rainyun.md +++ b/docs/content/dns/zz_gen_rainyun.md @@ -27,7 +27,7 @@ Here is an example bash command using the Rain Yun/雨云 provider: ```bash RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns rainyun -d '*.example.com' -d example.com run +lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rcodezero.md b/docs/content/dns/zz_gen_rcodezero.md index a544df420..98eaea9ca 100644 --- a/docs/content/dns/zz_gen_rcodezero.md +++ b/docs/content/dns/zz_gen_rcodezero.md @@ -27,7 +27,7 @@ Here is an example bash command using the RcodeZero provider: ```bash RCODEZERO_API_TOKEN= \ -lego --dns rcodezero -d '*.example.com' -d example.com run +lego --email you@example.com --dns rcodezero -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_regfish.md b/docs/content/dns/zz_gen_regfish.md index 357ce0764..149338e5e 100644 --- a/docs/content/dns/zz_gen_regfish.md +++ b/docs/content/dns/zz_gen_regfish.md @@ -27,7 +27,7 @@ Here is an example bash command using the Regfish provider: ```bash REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns regfish -d '*.example.com' -d example.com run +lego --email you@example.com --dns regfish -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_regru.md b/docs/content/dns/zz_gen_regru.md index eaf163a13..1d0e0053d 100644 --- a/docs/content/dns/zz_gen_regru.md +++ b/docs/content/dns/zz_gen_regru.md @@ -28,7 +28,7 @@ Here is an example bash command using the reg.ru provider: ```bash REGRU_USERNAME=xxxxxx \ REGRU_PASSWORD=yyyyyy \ -lego --dns regru -d '*.example.com' -d example.com run +lego --email you@example.com --dns regru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rfc2136.md b/docs/content/dns/zz_gen_rfc2136.md index 1b1d43dd5..ffdbc4b54 100644 --- a/docs/content/dns/zz_gen_rfc2136.md +++ b/docs/content/dns/zz_gen_rfc2136.md @@ -30,7 +30,7 @@ RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ -lego --dns rfc2136 -d '*.example.com' -d example.com run +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ## --- @@ -38,7 +38,7 @@ keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_FILE="$keyfile" \ -lego --dns rfc2136 -d '*.example.com' -d example.com run +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rimuhosting.md b/docs/content/dns/zz_gen_rimuhosting.md index acb829e93..2a703dec7 100644 --- a/docs/content/dns/zz_gen_rimuhosting.md +++ b/docs/content/dns/zz_gen_rimuhosting.md @@ -27,7 +27,7 @@ Here is an example bash command using the RimuHosting provider: ```bash RIMUHOSTING_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns rimuhosting -d '*.example.com' -d example.com run +lego --email you@example.com --dns rimuhosting -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_route53.md b/docs/content/dns/zz_gen_route53.md index 59e489d6a..a0967a57e 100644 --- a/docs/content/dns/zz_gen_route53.md +++ b/docs/content/dns/zz_gen_route53.md @@ -30,7 +30,7 @@ AWS_ACCESS_KEY_ID=your_key_id \ AWS_SECRET_ACCESS_KEY=your_secret_access_key \ AWS_REGION=aws-region \ AWS_HOSTED_ZONE_ID=your_hosted_zone_id \ -lego --dns route53 -d '*.example.com' -d example.com run +lego --email you@example.com --dns route53 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_safedns.md b/docs/content/dns/zz_gen_safedns.md index 4c20fca6a..2a9e179f5 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,11 +23,11 @@ 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 \ -lego --dns safedns -d '*.example.com' -d example.com run +lego --email you@example.com --dns safedns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_sakuracloud.md b/docs/content/dns/zz_gen_sakuracloud.md index b43f83ef4..e08e73e70 100644 --- a/docs/content/dns/zz_gen_sakuracloud.md +++ b/docs/content/dns/zz_gen_sakuracloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Sakura Cloud provider: ```bash SAKURACLOUD_ACCESS_TOKEN=xxxxx \ SAKURACLOUD_ACCESS_TOKEN_SECRET=yyyyy \ -lego --dns sakuracloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns sakuracloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_scaleway.md b/docs/content/dns/zz_gen_scaleway.md index 4033a9bd6..2f6af9d8a 100644 --- a/docs/content/dns/zz_gen_scaleway.md +++ b/docs/content/dns/zz_gen_scaleway.md @@ -27,7 +27,7 @@ Here is an example bash command using the Scaleway provider: ```bash SCW_SECRET_KEY=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \ -lego --dns scaleway -d '*.example.com' -d example.com run +lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selectel.md b/docs/content/dns/zz_gen_selectel.md index d994d6633..33dc859bb 100644 --- a/docs/content/dns/zz_gen_selectel.md +++ b/docs/content/dns/zz_gen_selectel.md @@ -27,7 +27,7 @@ Here is an example bash command using the Selectel provider: ```bash SELECTEL_API_TOKEN=xxxxx \ -lego --dns selectel -d '*.example.com' -d example.com run +lego --email you@example.com --dns selectel -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selectelv2.md b/docs/content/dns/zz_gen_selectelv2.md index 0873d810c..933ca201f 100644 --- a/docs/content/dns/zz_gen_selectelv2.md +++ b/docs/content/dns/zz_gen_selectelv2.md @@ -30,7 +30,7 @@ SELECTELV2_USERNAME=trex \ SELECTELV2_PASSWORD=xxxxx \ SELECTELV2_ACCOUNT_ID=1234567 \ SELECTELV2_PROJECT_ID=111a11111aaa11aa1a11aaa11111aa1a \ -lego --dns selectelv2 -d '*.example.com' -d example.com run +lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selfhostde.md b/docs/content/dns/zz_gen_selfhostde.md index 363f782e0..12df0c10d 100644 --- a/docs/content/dns/zz_gen_selfhostde.md +++ b/docs/content/dns/zz_gen_selfhostde.md @@ -29,7 +29,7 @@ Here is an example bash command using the SelfHost.(de|eu) provider: SELFHOSTDE_USERNAME=xxx \ SELFHOSTDE_PASSWORD=yyy \ SELFHOSTDE_RECORDS_MAPPING=my.example.com:123 \ -lego --dns selfhostde -d '*.example.com' -d example.com run +lego --email you@example.com --dns selfhostde -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_servercow.md b/docs/content/dns/zz_gen_servercow.md index 7d00a6306..3851325d1 100644 --- a/docs/content/dns/zz_gen_servercow.md +++ b/docs/content/dns/zz_gen_servercow.md @@ -28,7 +28,7 @@ Here is an example bash command using the Servercow provider: ```bash SERVERCOW_USERNAME=xxxxxxxx \ SERVERCOW_PASSWORD=xxxxxxxx \ -lego --dns servercow -d '*.example.com' -d example.com run +lego --email you@example.com --dns servercow -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_shellrent.md b/docs/content/dns/zz_gen_shellrent.md index cbbc172e2..6c1365b7e 100644 --- a/docs/content/dns/zz_gen_shellrent.md +++ b/docs/content/dns/zz_gen_shellrent.md @@ -28,7 +28,7 @@ Here is an example bash command using the Shellrent provider: ```bash SHELLRENT_USERNAME=xxxx \ SHELLRENT_TOKEN=yyyy \ -lego --dns shellrent -d '*.example.com' -d example.com run +lego --email you@example.com --dns shellrent -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_simply.md b/docs/content/dns/zz_gen_simply.md index edfa14380..32df66f05 100644 --- a/docs/content/dns/zz_gen_simply.md +++ b/docs/content/dns/zz_gen_simply.md @@ -28,7 +28,7 @@ Here is an example bash command using the Simply.com provider: ```bash SIMPLY_ACCOUNT_NAME=xxxxxx \ SIMPLY_API_KEY=yyyyyy \ -lego --dns simply -d '*.example.com' -d example.com run +lego --email you@example.com --dns simply -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_sonic.md b/docs/content/dns/zz_gen_sonic.md index 20729bc1a..f56a23151 100644 --- a/docs/content/dns/zz_gen_sonic.md +++ b/docs/content/dns/zz_gen_sonic.md @@ -28,7 +28,7 @@ Here is an example bash command using the Sonic provider: ```bash SONIC_USER_ID=12345 \ SONIC_API_KEY=4d6fbf2f9ab0fa11697470918d37625851fc0c51 \ -lego --dns sonic -d '*.example.com' -d example.com run +lego --email you@example.com --dns sonic -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_spaceship.md b/docs/content/dns/zz_gen_spaceship.md index 9f3b51e43..4594fe217 100644 --- a/docs/content/dns/zz_gen_spaceship.md +++ b/docs/content/dns/zz_gen_spaceship.md @@ -28,7 +28,7 @@ Here is an example bash command using the Spaceship provider: ```bash SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns spaceship -d '*.example.com' -d example.com run +lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_stackpath.md b/docs/content/dns/zz_gen_stackpath.md index b881176f4..ce0a02eac 100644 --- a/docs/content/dns/zz_gen_stackpath.md +++ b/docs/content/dns/zz_gen_stackpath.md @@ -29,7 +29,7 @@ Here is an example bash command using the Stackpath provider: STACKPATH_CLIENT_ID=xxxxx \ STACKPATH_CLIENT_SECRET=yyyyy \ STACKPATH_STACK_ID=zzzzz \ -lego --dns stackpath -d '*.example.com' -d example.com run +lego --email you@example.com --dns stackpath -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_syse.md b/docs/content/dns/zz_gen_syse.md deleted file mode 100644 index a1a952bc5..000000000 --- a/docs/content/dns/zz_gen_syse.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: "Syse" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: syse -dnsprovider: - since: "v4.30.0" - code: "syse" - url: "https://www.syse.no/" ---- - - - - - - -Configuration for [Syse](https://www.syse.no/). - - - - -- Code: `syse` -- Since: v4.30.0 - - -Here is an example bash command using the Syse provider: - -```bash -SYSE_CREDENTIALS=example.com:password \ -lego --dns syse -d '*.example.com' -d example.com run - -SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ -lego --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `SYSE_CREDENTIALS` | Comma-separated list of `zone:password` credential pairs | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `SYSE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `SYSE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | -| `SYSE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | -| `SYSE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://www.syse.no/api/dns) - - - - diff --git a/docs/content/dns/zz_gen_technitium.md b/docs/content/dns/zz_gen_technitium.md index ff7f2e6ed..80f7c6a1f 100644 --- a/docs/content/dns/zz_gen_technitium.md +++ b/docs/content/dns/zz_gen_technitium.md @@ -28,7 +28,7 @@ Here is an example bash command using the Technitium provider: ```bash TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \ TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns technitium -d '*.example.com' -d example.com run +lego --email you@example.com --dns technitium -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_tencentcloud.md b/docs/content/dns/zz_gen_tencentcloud.md index 178ffcf43..ef1e6cdf8 100644 --- a/docs/content/dns/zz_gen_tencentcloud.md +++ b/docs/content/dns/zz_gen_tencentcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Tencent Cloud DNS provider: ```bash TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \ TENCENTCLOUD_SECRET_KEY=your-secret-key \ -lego --dns tencentcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns tencentcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_timewebcloud.md b/docs/content/dns/zz_gen_timewebcloud.md index 83d5b831b..af218ddce 100644 --- a/docs/content/dns/zz_gen_timewebcloud.md +++ b/docs/content/dns/zz_gen_timewebcloud.md @@ -27,7 +27,7 @@ Here is an example bash command using the Timeweb Cloud provider: ```bash TIMEWEBCLOUD_AUTH_TOKEN=xxxxxx \ -lego --dns timewebcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns timewebcloud -d '*.example.com' -d example.com run ``` 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/content/dns/zz_gen_transip.md b/docs/content/dns/zz_gen_transip.md index a66a25879..769fbc734 100644 --- a/docs/content/dns/zz_gen_transip.md +++ b/docs/content/dns/zz_gen_transip.md @@ -28,7 +28,7 @@ Here is an example bash command using the TransIP provider: ```bash TRANSIP_ACCOUNT_NAME = "Account name" \ TRANSIP_PRIVATE_KEY_PATH = "transip.key" \ -lego --dns transip -d '*.example.com' -d example.com run +lego --email you@example.com --dns transip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ultradns.md b/docs/content/dns/zz_gen_ultradns.md index d6d89c77b..8e0fa9b20 100644 --- a/docs/content/dns/zz_gen_ultradns.md +++ b/docs/content/dns/zz_gen_ultradns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Ultradns provider: ```bash ULTRADNS_USERNAME=username \ ULTRADNS_PASSWORD=password \ -lego --dns ultradns -d '*.example.com' -d example.com run +lego --email you@example.com --dns ultradns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_uniteddomains.md b/docs/content/dns/zz_gen_uniteddomains.md deleted file mode 100644 index e837644d5..000000000 --- a/docs/content/dns/zz_gen_uniteddomains.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "United-Domains" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: uniteddomains -dnsprovider: - since: "v4.29.0" - code: "uniteddomains" - url: "https://www.united-domains.de/" ---- - - - - - - -Configuration for [United-Domains](https://www.united-domains.de/). - - - - -- Code: `uniteddomains` -- Since: v4.29.0 - - -Here is an example bash command using the United-Domains provider: - -```bash -UNITEDDOMAINS_API_KEY=xxxxxxxx \ -lego --dns uniteddomains -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `UNITEDDOMAINS_API_KEY` | API key `.` https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/ | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `UNITEDDOMAINS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `UNITEDDOMAINS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `UNITEDDOMAINS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 900) | -| `UNITEDDOMAINS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://www.united-domains.de/dns-apidoc/) - - - - diff --git a/docs/content/dns/zz_gen_variomedia.md b/docs/content/dns/zz_gen_variomedia.md index f9771c867..282ec9da3 100644 --- a/docs/content/dns/zz_gen_variomedia.md +++ b/docs/content/dns/zz_gen_variomedia.md @@ -27,7 +27,7 @@ Here is an example bash command using the Variomedia provider: ```bash VARIOMEDIA_API_TOKEN=xxxx \ -lego --dns variomedia -d '*.example.com' -d example.com run +lego --email you@example.com --dns variomedia -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vercel.md b/docs/content/dns/zz_gen_vercel.md index 71f2eeed5..d9e24eee3 100644 --- a/docs/content/dns/zz_gen_vercel.md +++ b/docs/content/dns/zz_gen_vercel.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vercel provider: ```bash VERCEL_API_TOKEN=xxxxxx \ -lego --dns vercel -d '*.example.com' -d example.com run +lego --email you@example.com --dns vercel -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_versio.md b/docs/content/dns/zz_gen_versio.md index 5d2cc0118..0e2edfa1e 100644 --- a/docs/content/dns/zz_gen_versio.md +++ b/docs/content/dns/zz_gen_versio.md @@ -28,7 +28,7 @@ Here is an example bash command using the Versio.[nl|eu|uk] provider: ```bash VERSIO_USERNAME= \ VERSIO_PASSWORD= \ -lego --dns versio -d '*.example.com' -d example.com run +lego --email you@example.com --dns versio -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vinyldns.md b/docs/content/dns/zz_gen_vinyldns.md index 3280d6f0a..666bc39c4 100644 --- a/docs/content/dns/zz_gen_vinyldns.md +++ b/docs/content/dns/zz_gen_vinyldns.md @@ -29,7 +29,7 @@ Here is an example bash command using the VinylDNS provider: VINYLDNS_ACCESS_KEY=xxxxxx \ VINYLDNS_SECRET_KEY=yyyyy \ VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \ -lego --dns vinyldns -d '*.example.com' -d example.com run +lego --email you@example.com --dns vinyldns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_virtualname.md b/docs/content/dns/zz_gen_virtualname.md deleted file mode 100644 index a00e5105f..000000000 --- a/docs/content/dns/zz_gen_virtualname.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Virtualname" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: virtualname -dnsprovider: - since: "v4.30.0" - code: "virtualname" - url: "https://www.virtualname.es/" ---- - - - - - - -Configuration for [Virtualname](https://www.virtualname.es/). - - - - -- Code: `virtualname` -- Since: v4.30.0 - - -Here is an example bash command using the Virtualname provider: - -```bash -VIRTUALNAME_TOKEN=xxxxxx \ -lego --dns virtualname -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `VIRTUALNAME_TOKEN` | API token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `VIRTUALNAME_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `VIRTUALNAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | -| `VIRTUALNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | -| `VIRTUALNAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://developers.virtualname.net/#dns) - - - - diff --git a/docs/content/dns/zz_gen_vkcloud.md b/docs/content/dns/zz_gen_vkcloud.md index 76fd557a5..eede62cf5 100644 --- a/docs/content/dns/zz_gen_vkcloud.md +++ b/docs/content/dns/zz_gen_vkcloud.md @@ -29,7 +29,7 @@ Here is an example bash command using the VK Cloud provider: VK_CLOUD_PROJECT_ID="" \ VK_CLOUD_USERNAME="" \ VK_CLOUD_PASSWORD="" \ -lego --dns vkcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns vkcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_volcengine.md b/docs/content/dns/zz_gen_volcengine.md index 587ce1e74..9d3c92d0d 100644 --- a/docs/content/dns/zz_gen_volcengine.md +++ b/docs/content/dns/zz_gen_volcengine.md @@ -28,7 +28,7 @@ Here is an example bash command using the Volcano Engine/火山引擎 provider: ```bash VOLC_ACCESSKEY=xxx \ VOLC_SECRETKEY=yyy \ -lego --dns volcengine -d '*.example.com' -d example.com run +lego --email you@example.com --dns volcengine -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vscale.md b/docs/content/dns/zz_gen_vscale.md index c33e2f7b5..660542d61 100644 --- a/docs/content/dns/zz_gen_vscale.md +++ b/docs/content/dns/zz_gen_vscale.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vscale provider: ```bash VSCALE_API_TOKEN=xxxxx \ -lego --dns vscale -d '*.example.com' -d example.com run +lego --email you@example.com --dns vscale -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vultr.md b/docs/content/dns/zz_gen_vultr.md index 4160fbcf3..a3807c1a1 100644 --- a/docs/content/dns/zz_gen_vultr.md +++ b/docs/content/dns/zz_gen_vultr.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vultr provider: ```bash VULTR_API_KEY=xxxxx \ -lego --dns vultr -d '*.example.com' -d example.com run +lego --email you@example.com --dns vultr -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_webnames.md b/docs/content/dns/zz_gen_webnames.md index cad02c287..4945775a5 100644 --- a/docs/content/dns/zz_gen_webnames.md +++ b/docs/content/dns/zz_gen_webnames.md @@ -27,7 +27,7 @@ Here is an example bash command using the webnames.ru provider: ```bash WEBNAMESRU_API_KEY=xxxxxx \ -lego --dns webnamesru -d '*.example.com' -d example.com run +lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_webnamesca.md b/docs/content/dns/zz_gen_webnamesca.md index 4a7d3794f..41a33cb82 100644 --- a/docs/content/dns/zz_gen_webnamesca.md +++ b/docs/content/dns/zz_gen_webnamesca.md @@ -28,7 +28,7 @@ Here is an example bash command using the webnames.ca provider: ```bash WEBNAMESCA_API_USER="xxx" \ WEBNAMESCA_API_KEY="yyy" \ -lego --dns webnamesca -d '*.example.com' -d example.com run +lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_websupport.md b/docs/content/dns/zz_gen_websupport.md index 67ae394d7..5fe44a860 100644 --- a/docs/content/dns/zz_gen_websupport.md +++ b/docs/content/dns/zz_gen_websupport.md @@ -28,7 +28,7 @@ Here is an example bash command using the Websupport provider: ```bash WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns websupport -d '*.example.com' -d example.com run +lego --email you@example.com --dns websupport -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_wedos.md b/docs/content/dns/zz_gen_wedos.md index 16139f4d4..8fe6ba00d 100644 --- a/docs/content/dns/zz_gen_wedos.md +++ b/docs/content/dns/zz_gen_wedos.md @@ -28,7 +28,7 @@ Here is an example bash command using the WEDOS provider: ```bash WEDOS_USERNAME=xxxxxxxx \ WEDOS_WAPI_PASSWORD=xxxxxxxx \ -lego --dns wedos -d '*.example.com' -d example.com run +lego --email you@example.com --dns wedos -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_westcn.md b/docs/content/dns/zz_gen_westcn.md index a5523b955..434e5b601 100644 --- a/docs/content/dns/zz_gen_westcn.md +++ b/docs/content/dns/zz_gen_westcn.md @@ -28,7 +28,7 @@ Here is an example bash command using the West.cn/西部数码 provider: ```bash WESTCN_USERNAME="xxx" \ WESTCN_PASSWORD="yyy" \ -lego --dns westcn -d '*.example.com' -d example.com run +lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandex.md b/docs/content/dns/zz_gen_yandex.md index 4a1cf1f99..6100c02fe 100644 --- a/docs/content/dns/zz_gen_yandex.md +++ b/docs/content/dns/zz_gen_yandex.md @@ -27,7 +27,7 @@ Here is an example bash command using the Yandex PDD provider: ```bash YANDEX_PDD_TOKEN= \ -lego --dns yandex -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandex -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandex360.md b/docs/content/dns/zz_gen_yandex360.md index d831fdfc2..66b90e049 100644 --- a/docs/content/dns/zz_gen_yandex360.md +++ b/docs/content/dns/zz_gen_yandex360.md @@ -28,7 +28,7 @@ Here is an example bash command using the Yandex 360 provider: ```bash YANDEX360_OAUTH_TOKEN= \ YANDEX360_ORG_ID= \ -lego --dns yandex360 -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandex360 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandexcloud.md b/docs/content/dns/zz_gen_yandexcloud.md index 0564e93d2..f5aeba09d 100644 --- a/docs/content/dns/zz_gen_yandexcloud.md +++ b/docs/content/dns/zz_gen_yandexcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Yandex Cloud provider: ```bash YANDEX_CLOUD_IAM_TOKEN= \ YANDEX_CLOUD_FOLDER_ID= \ -lego --dns yandexcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run # --- @@ -41,7 +41,7 @@ YANDEX_CLOUD_IAM_TOKEN=$(echo '{ \ "private_key": "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----" \ }' | base64) \ YANDEX_CLOUD_FOLDER_ID= \ -lego --dns yandexcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zoneedit.md b/docs/content/dns/zz_gen_zoneedit.md index c7f88b3fe..e259a2a04 100644 --- a/docs/content/dns/zz_gen_zoneedit.md +++ b/docs/content/dns/zz_gen_zoneedit.md @@ -28,7 +28,7 @@ Here is an example bash command using the ZoneEdit provider: ```bash ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns zoneedit -d '*.example.com' -d example.com run +lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zoneee.md b/docs/content/dns/zz_gen_zoneee.md index 65678a3dc..cfc6be692 100644 --- a/docs/content/dns/zz_gen_zoneee.md +++ b/docs/content/dns/zz_gen_zoneee.md @@ -28,7 +28,7 @@ Here is an example bash command using the Zone.ee provider: ```bash ZONEEE_API_USER=xxxxx \ ZONEEE_API_KEY=yyyyy \ -lego --dns zoneee -d '*.example.com' -d example.com run +lego --email you@example.com --dns zoneee -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zonomi.md b/docs/content/dns/zz_gen_zonomi.md index fd8757f82..1e90a7285 100644 --- a/docs/content/dns/zz_gen_zonomi.md +++ b/docs/content/dns/zz_gen_zonomi.md @@ -27,7 +27,7 @@ Here is an example bash command using the Zonomi provider: ```bash ZONOMI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns zonomi -d '*.example.com' -d example.com run +lego --email you@example.com --dns zonomi -d '*.example.com' -d example.com run ``` diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 139143b17..80cbcaac0 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, 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, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/docs/hugo.toml b/docs/hugo.toml index fe076a306..b17206d43 100644 --- a/docs/hugo.toml +++ b/docs/hugo.toml @@ -15,7 +15,6 @@ title = "Lego" custom_css = ["css/theme-custom.css"] disableLandingPageButton = true hideAuthorEmail = true - hideAuthorName = true # Author of the site, will be used in meta information [params.author] diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 9dd9ab0d6..509b57bb1 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -58,6 +58,7 @@ func TestChallengeDNS_Run(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( + "-m", "hubert@hubert.com", "--accept-tos", "--dns", "exec", "--dns.resolvers", ":8053", diff --git a/e2e/fixtures/certs/localhost/cert.pem b/e2e/fixtures/certs/localhost/cert.pem index d81d29e70..2866a2b48 100644 --- a/e2e/fixtures/certs/localhost/cert.pem +++ b/e2e/fixtures/certs/localhost/cert.pem @@ -1,20 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDMDCCAhigAwIBAgIILDt8c2fMw2IwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgNTM0NWU2MB4XDTI1MDkwMzIzNDAwNVoXDTI3MTAw -MzIzNDAwNVowFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAmxTFtw113RK70H9pQmdKs9AxhFmnQ6BdDtp3jOZlWlUO -0BltMXOUML5905etgtCbcC6RdKRtgSAiDfgx3VWiFMJH++4gUtnaB9SN8GhNSPBp -FfSa2JhWPo9HQNUsAZqlGTV4SzcGRqtWvdZxUiOfQ2TcvyXIqsaD19ivvqI1NhT6 -bl3tredTZlzLLM6Wvkw6hfyHrJAPQP8LOlCIeDM4YIce6Gstv6qo9iCD4wJiY4u9 -5HVL7RK8t8JpZAb7VR+dPhbHEvVpjwuYd5Q05OZ280gFyrhbrKLbqst104GOQT4k -QMJGWxGONyTX6np0Dx6O5jU7dvYvjVVawbJwGuaL6wIDAQABo3oweDAOBgNVHQ8B -Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNV -HSMEGDAWgBSu8RGpErgYUoYnQuwCq+/ggTiEjDAiBgNVHREEGzAZgglsb2NhbGhv -c3SCBnBlYmJsZYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAAB0gkekXCNOwqWmY -vQ2lLJ8Zk2WzQ9B+VOC27IgxEEuskZyCpyXAbJB9sCGQWZhAARyaI4SPRGGagcug -d1SwDWdPGeSJzF3aDnXDYoP9Zw2KqiqVZTngeoiw8Yn0F8PNriANwRLybouX7mMc -4V7T5+2k4SUs7pFH4KO0a0XBCcjXDjdKuBljftRTXCHzJzfRtmieCCuZlpnp5sHx -hKa/uxKGyyZB+4Y3MrzsiQSCBOr9G4TH9RofmNcawl+tsVe08zLV/XVhrbakKEs7 -Y7MGHSj3BkPFF32NObc0znqWzTaUD9hU+rXWGANM4sXd4dagdnxfrb7i0WYhcUFj -9Try8Q== +MIIDGzCCAgOgAwIBAgIIbEfayDFsBtwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMDcx +MjA2MTk0MjEwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCbFMW3DXXdErvQf2lCZ0qz0DGEWadDoF0O2neM5mVa +VQ7QGW0xc5Qwvn3Tl62C0JtwLpF0pG2BICIN+DHdVaIUwkf77iBS2doH1I3waE1I +8GkV9JrYmFY+j0dA1SwBmqUZNXhLNwZGq1a91nFSI59DZNy/JciqxoPX2K++ojU2 +FPpuXe2t51NmXMsszpa+TDqF/IeskA9A/ws6UIh4Mzhghx7oay2/qqj2IIPjAmJj +i73kdUvtEry3wmlkBvtVH50+FscS9WmPC5h3lDTk5nbzSAXKuFusotuqy3XTgY5B +PiRAwkZbEY43JNfqenQPHo7mNTt29i+NVVrBsnAa5ovrAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T +AQH/BAIwADAiBgNVHREEGzAZgglsb2NhbGhvc3SCBnBlYmJsZYcEfwAAATANBgkq +hkiG9w0BAQsFAAOCAQEAYIkXff8H28KS0KyLHtbbSOGU4sujHHVwiVXSATACsNAE +D0Qa8hdtTQ6AUqA6/n8/u1tk0O4rPE/cTpsM3IJFX9S3rZMRsguBP7BSr1Lq/XAB +7JP/CNHt+Z9aKCKcg11wIX9/B9F7pyKM3TdKgOpqXGV6TMuLjg5PlYWI/07lVGFW +/mSJDRs8bSCFmbRtEqc4lpwlrpz+kTTnX6G7JDLfLWYw/xXVqwFfdengcDTHCc8K +wtgGq/Gu6vcoBxIO3jaca+OIkMfxxXmGrcNdseuUCa3RMZ8Qy03DqGu6Y6XQyK4B +W8zIG6H9SVKkAznM2yfYhW8v2ktcaZ95/OBHY97ZIw== -----END CERTIFICATE----- diff --git a/e2e/fixtures/certs/pebble.minica.pem b/e2e/fixtures/certs/pebble.minica.pem index 5578b5b55..a69a4c419 100644 --- a/e2e/fixtures/certs/pebble.minica.pem +++ b/e2e/fixtures/certs/pebble.minica.pem @@ -1,20 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDPzCCAiegAwIBAgIIU0Xm9UFdQxUwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgNTM0NWU2MCAXDTI1MDkwMzIzNDAwNVoYDzIxMjUw -OTAzMjM0MDA1WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA1MzQ1ZTYwggEi +MIIDCTCCAfGgAwIBAgIIJOLbes8sTr4wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMTcx +MjA2MTk0MjEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyNGUyZGIwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5WgZNoVJandj43kkLyU50vzCZ alozvdRo3OFiKoDtmqKPNWRNO2hC9AUNxTDJco51Yc42u/WV3fPbbhSznTiOOVtn Ajm6iq4I5nZYltGGZetGDOQWr78y2gWY+SG078MuOO2hyDIiKtVc3xiXYA+8Hluu 9F8KbqSS1h55yxZ9b87eKR+B0zu2ahzBCIHKmKWgc6N13l7aDxxY3D6uq8gtJRU0 toumyLbdzGcupVvjbjDP11nl07RESDWBLG1/g3ktJvqIa4BWgU2HMh4rND6y8OD3 Hy3H8MY6CElL+MOCbFJjWqhtOxeFyZZV9q3kYnk9CAuQJKMEGuN4GU6tzhW1AgMB -AAGjezB5MA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggrBgEFBQcDATASBgNV -HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSu8RGpErgYUoYnQuwCq+/ggTiEjDAf -BgNVHSMEGDAWgBSu8RGpErgYUoYnQuwCq+/ggTiEjDANBgkqhkiG9w0BAQsFAAOC -AQEAXDVYov1+f6EL7S41LhYQkEX/GyNNzsEvqxE9U0+3Iri5JfkcNOiA9O9L6Z+Y -bqcsXV93s3vi4r4WSWuc//wHyJYrVe5+tK4nlFpbJOvfBUtnoBDyKNxXzZCxFJVh -f9uc8UejRfQMFbDbhWY/x83y9BDufJHHq32OjCIN7gp2UR8rnfYvlz7Zg4qkJBsn -DG4dwd+pRTCFWJOVIG0JoNhK3ZmE7oJ1N4H38XkZ31NPcMksKxpsLLIS9+mosZtg -4olL7tMPJklx5ZaeMFaKRDq4Gdxkbw4+O4vRgNm3Z8AXWKknOdfgdpqLUPPhRcP4 -v1lhy71EhBuXXwRQJry0lTdF+w== +AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAF85v +d40HK1ouDAtWeO1PbnWfGEmC5Xa478s9ddOd9Clvp2McYzNlAFfM7kdcj6xeiNhF +WPIfaGAi/QdURSL/6C1KsVDqlFBlTs9zYfh2g0UXGvJtj1maeih7zxFLvet+fqll +xseM4P9EVJaQxwuK/F78YBt0tCNfivC6JNZMgxKF59h0FBpH70ytUSHXdz7FKwix +Mfn3qEb9BXSk0Q3prNV5sOV3vgjEtB4THfDxSz9z3+DepVnW3vbbqwEbkXdk3j82 +2muVldgOUgTwK8eT+XdofVdntzU/kzygSAtAQwLJfn51fS1GvEcYGBc1bDryIqmF +p9BI7gVKtWSZYegicA== -----END CERTIFICATE----- diff --git a/e2e/readme.md b/e2e/readme.md index 171170507..7a2367c9b 100644 --- a/e2e/readme.md +++ b/e2e/readme.md @@ -2,8 +2,8 @@ - Install [Pebble](https://github.com/letsencrypt/pebble): ```bash -go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.9.0 -go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.9.0 +go install github.com/letsencrypt/pebble/v2/cmd/pebble@main +go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@main ``` - Launch tests: diff --git a/go.mod b/go.mod index b8e88428e..3dc72c1ff 100644 --- a/go.mod +++ b/go.mod @@ -5,68 +5,65 @@ 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/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 github.com/Azure/go-autorest/autorest v0.11.30 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 github.com/Azure/go-autorest/autorest/to v0.4.1 - github.com/BurntSushi/toml v1.6.0 + github.com/BurntSushi/toml v1.5.0 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.15 - github.com/alibabacloud-go/tea v1.4.0 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 + github.com/alibabacloud-go/tea v1.3.13 github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.41.1 - github.com/aws/aws-sdk-go-v2/config v1.32.8 - github.com/aws/aws-sdk-go-v2/credentials v1.19.8 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 - github.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 - github.com/aziontech/azionapi-go-sdk v0.144.0 - github.com/baidubce/bce-sdk-go v0.9.260 + github.com/aws/aws-sdk-go-v2 v1.39.4 + github.com/aws/aws-sdk-go-v2/config v1.31.15 + github.com/aws/aws-sdk-go-v2/credentials v1.18.19 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 + github.com/aziontech/azionapi-go-sdk v0.143.0 + github.com/baidubce/bce-sdk-go v0.9.250 github.com/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/jdcloud-sdk-go v1.64.0 - github.com/go-acme/tencentclouddnspod v1.3.24 - github.com/go-acme/tencentedgdeone v1.3.38 + github.com/exoscale/egoscale/v3 v3.1.27 + github.com/go-acme/alidns-20150109/v4 v4.6.1 + github.com/go-acme/tencentclouddnspod v1.1.10 + github.com/go-acme/tencentedgdeone v1.1.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 + github.com/google/go-querystring v1.1.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 - github.com/hashicorp/go-version v1.8.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187 + github.com/hashicorp/go-version v1.7.0 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.10.1 - github.com/linode/linodego v1.65.0 + github.com/linode/linodego v1.60.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.72 + github.com/miekg/dns v1.1.68 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 - github.com/nrdcg/auroradns v1.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 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 - github.com/nrdcg/goinwx v0.12.0 + github.com/nrdcg/goinwx v0.11.0 github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 github.com/nrdcg/porkbun v0.4.0 github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 @@ -75,35 +72,35 @@ require ( github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 github.com/regfish/regfish-dnsapi-go v0.1.1 github.com/sacloud/api-client-go v0.3.3 - github.com/sacloud/iaas-api-go v1.23.1 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 + github.com/sacloud/iaas-api-go v1.20.0 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 - github.com/vinyldns/go-vinyldns v0.9.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/vinyldns/go-vinyldns v0.9.16 + github.com/volcengine/volc-sdk-golang v1.0.224 + github.com/vultr/govultr/v3 v3.24.0 + github.com/yandex-cloud/go-genproto v0.34.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.16 + github.com/yandex-cloud/go-sdk/v2 v2.24.0 + golang.org/x/crypto v0.43.0 + golang.org/x/net v0.46.0 + golang.org/x/oauth2 v0.32.0 + golang.org/x/text v0.30.0 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.254.0 + gopkg.in/ns1/ns1-go.v2 v2.15.1 gopkg.in/yaml.v2 v2.4.0 - software.sslmate.com/src/go-pkcs12 v0.7.0 + software.sslmate.com/src/go-pkcs12 v0.6.0 ) require ( - cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect @@ -113,29 +110,27 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.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/aws/protocol/eventstream v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect - github.com/aws/smithy-go v1.24.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect + github.com/aws/smithy-go v1.23.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - 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 @@ -153,16 +148,16 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect - github.com/go-resty/resty/v2 v2.17.1 // indirect + github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/goccy/go-yaml v1.9.8 // indirect github.com/gofrs/flock v0.13.0 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect 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/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -186,11 +181,12 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sacloud/go-http v0.1.9 // indirect - github.com/sacloud/packages-go v0.0.12 // indirect + github.com/sacloud/packages-go v0.0.11 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -204,26 +200,24 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.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.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.32.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/mod v0.28.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.org/x/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/grpc v1.78.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/ini.v1 v1.67.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -retract v4.30.0 // Problem related to misuse of sycalls by aliyun/credentials-go diff --git a/go.sum b/go.sum index f5b87c9fe..a8d61029d 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.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -42,10 +42,10 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -85,11 +85,11 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= -github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -121,10 +121,9 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12/go.mod h1:f2wDpbM7hK9SvLIH09zSKVU1TsyemUNOqErMscMMl7c= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= -github.com/alibabacloud-go/darabonba-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= @@ -145,9 +144,9 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea v1.3.12/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= -github.com/alibabacloud-go/tea v1.4.0 h1:MSKhu/kWLPX7mplWMngki8nNt+CyUZ+kfkzaR5VpMhA= -github.com/alibabacloud-go/tea v1.4.0/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= @@ -171,54 +170,52 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= -github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.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 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= +github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= +github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.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.11 h1:bKgSxk1TW//00PGQqYmrq83c+2myGidEclp+t9pPqVI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11/go.mod h1:vrPYCQ6rFHL8jzQA8ppu3gWX18zxjLIDGTeqDxkBmSI= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.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/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 h1:DGFpGybmutVsCuF6vSuLZ25Vh55E3VmsnJmFfjeBx4M= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2/go.mod h1:hm/wU1HDvXCFEDzOLorQnZZ/CVvPXvWEmHMSmqgQRuA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 h1:weapBOuuFIBEQ9OX/NVW3tFQCvSutyjZYk/ga5jDLPo= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11/go.mod h1:3C1gN4FmIVLwYSh8etngUS+f1viY6nLCDVtZmrFbDy0= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 h1:pr1dQ9vamhAf2mYOgiRRC/w9Ht4POFhy6+xXw7hOqwY= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2/go.mod h1:A4Ch93K7Wam4Qe0Wl0XbPgcgoL5KIJtFIe7wHw6OPWE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 h1:JbCUlVDEjmhpvpIgXP9QN+/jW61WWWj99cGmxMC49hM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.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/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= +github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= +github.com/baidubce/bce-sdk-go v0.9.250 h1:fnvV5clsNCAP6pCauj0eNaUnoLVmjQGnco7rcMqp984= +github.com/baidubce/bce-sdk-go v0.9.250/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/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 +238,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= @@ -289,8 +284,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.33 h1:5Lk/pwZ+K0sjNu9obS0VYPfhZQffRkvvO0BpdPoir4o= -github.com/exoscale/egoscale/v3 v3.1.33/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= +github.com/exoscale/egoscale/v3 v3.1.27 h1:vKdWZG8QFDc7rY7lCfcuudO+ovyp5psYjFwKVqmkhCE= +github.com/exoscale/egoscale/v3 v3.1.27/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -317,16 +312,12 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/alidns-20150109/v4 v4.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/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/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= +github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= +github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= +github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= +github.com/go-acme/tencentedgdeone v1.1.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= @@ -363,15 +354,15 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= -github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/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= @@ -380,8 +371,6 @@ github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXK github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -448,9 +437,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= -github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -471,12 +459,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.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.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.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -525,8 +513,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -541,8 +529,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.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.173 h1:Y4ixGadyrK9xHw6Z+cyiiME3SBXepEcUoiT+B8C5FoQ= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -616,8 +604,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.60.0 h1:SgsebJFRCi+lSmYy+C40wmKZeJllGGm+W12Qw4+yVdI= +github.com/linode/linodego v1.60.0/go.mod h1:1+Bt0oTz5rBnDOJbGhccxn7LYVytXTIIfAy7QYmijDs= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -653,8 +641,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.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -695,8 +683,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= @@ -707,18 +695,18 @@ github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= -github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4= -github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0= +github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= +github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= github.com/nrdcg/mailinabox v0.3.0 h1:PHkC1elKXKAjEvdx2HHFMgcEGZFqudAl7aU3L2JDhM4= github.com/nrdcg/mailinabox v0.3.0/go.mod h1:1eFIGcM4lI+AfFOUpbs548SFGz1ZWoMOGbECBmkghw4= github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.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.103.0 h1:GPwwX9GFIBjV4u1M3Cr8eKCP6drW01IsfQSDIz6SUk8= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 h1:MjHla6lf1jpjGXORLpzMeo/tSmx0ejmjMjdjTByaDGY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0/go.mod h1:o1/kMADX0SlB4hJjWtcs3M6VIUOGR78yhPyiBv6oBkk= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= @@ -826,16 +814,16 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.23.1 h1:rjYG0vVoxWyETiwc7R8YdD7CIzs9vVNEOzu7w6dgGzc= -github.com/sacloud/iaas-api-go v1.23.1/go.mod h1:EGIHOWRB9azOv7HPCVM8WpOEl28WIV9TNRbnEVg+Q3U= -github.com/sacloud/packages-go v0.0.12 h1:MKeZNN3FQn1heqUSRBrbZw89YusZA1n4kammjMFZYvQ= -github.com/sacloud/packages-go v0.0.12/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= +github.com/sacloud/iaas-api-go v1.20.0 h1:L4TfAzoFSwxrD3QXX8UxJa2o+GZrP9b863K+voTy3tQ= +github.com/sacloud/iaas-api-go v1.20.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= +github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= +github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 h1:8xfn1RzeI9yoCUuEwDy08F+No6PcKZGEDOQ6hrRyLts= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35/go.mod h1:47B1d/YXmSAxlJxUJxClzHR6b3T4M1WyCvwENPQNBWc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -851,8 +839,13 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= +github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/softlayer/softlayer-go v1.2.1 h1:8ucHxn5laVsVPb0/aMGnr6tOMt1I9BgEtU5mn70OGKw= github.com/softlayer/softlayer-go v1.2.1/go.mod h1:Gz9/ktcmB7Z8EJlu+QEJJpkv8lAmnhYdB9Tc6gedjmo= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= @@ -908,10 +901,9 @@ 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.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 h1:aoRUrz2ag27jQWcOKHgeE+toSti6/xPqHKMLruOtJuM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -924,12 +916,12 @@ github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/vinyldns/go-vinyldns v0.9.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/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= +github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= +github.com/volcengine/volc-sdk-golang v1.0.224 h1:k9Vtg64tQAgFTOGWzhyL0b0axuTuExXbLNVlslWlBZI= +github.com/volcengine/volc-sdk-golang v1.0.224/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.24.0 h1:fTTTj0VBve+Miy+wGhlb90M2NMDfpGFi6Frlj3HVy6M= +github.com/vultr/govultr/v3 v3.24.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/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 +930,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.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.34.0 h1:qhTJpPxOTKQbV44rIqoZSdzxDtZW27fkFjAcipEy8Zs= +github.com/yandex-cloud/go-genproto v0.34.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16 h1:0UYrBlQjTO2ct5xcSx6rqkQB95wRBPMVwxfqLQD1sUE= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16/go.mod h1:HlS3aIAdYEmJu2Ska/nzpcuv9LLVSMMXKGhzyLQwf5s= +github.com/yandex-cloud/go-sdk/v2 v2.24.0 h1:G53N/RB5g/jw2xNN0egspnwd2ByHA1OVH6wbTx/tIlo= +github.com/yandex-cloud/go-sdk/v2 v2.24.0/go.mod h1:ZRdpyOig8c/W3bNhwvkeXWWPeDScd9nmXv4AJzKvOsk= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -967,22 +959,22 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1035,8 +1027,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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1080,8 +1072,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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1139,16 +1131,17 @@ 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.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1165,8 +1158,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1252,8 +1245,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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1268,8 +1261,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.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1288,8 +1281,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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1355,8 +1348,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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1385,8 +1378,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= -google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= +google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1425,12 +1418,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-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1448,8 +1441,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1464,8 +1457,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1479,12 +1472,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.15.1 h1:8rri2TzAPYcVbBGXn48+dz1Xg30PzHfZ4k8A9JOS0Z0= +gopkg.in/ns1/ns1-go.v2 v2.15.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1515,5 +1507,5 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0= -software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index 9355d0d1b..c7f9ef8c7 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -190,9 +190,14 @@ func generateReadMe(models *descriptors.Providers) error { } func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { + providers := append(models.Providers, descriptors.Provider{ + Name: "Manual", + Code: "manual", + }) + const nbCol = 4 - slices.SortFunc(models.Providers, func(a, b descriptors.Provider) int { + slices.SortFunc(providers, func(a, b descriptors.Provider) int { return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) }) @@ -201,13 +206,13 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { row []descriptors.Provider ) - for i, p := range models.Providers { + for i, p := range providers { switch { case len(row) == nbCol: matrix = append(matrix, row) row = []descriptors.Provider{p} - case i == len(models.Providers)-1: + case i == len(providers)-1: row = append(row, p) for j := len(row); j < nbCol; j++ { row = append(row, descriptors.Provider{}) diff --git a/internal/dns/docs/templates/dns.go.tmpl b/internal/dns/docs/templates/dns.go.tmpl index c1896c91a..e8b336254 100644 --- a/internal/dns/docs/templates/dns.go.tmpl +++ b/internal/dns/docs/templates/dns.go.tmpl @@ -12,6 +12,7 @@ import ( func allDNSCodes() string { providers := []string{ + "manual", {{- range $provider := .Providers }} "{{ $provider.Code }}", {{- end}} @@ -47,6 +48,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/{{ $provider.Code }}`) {{end}} + case "manual": + ew.writeln(`Solving the DNS-01 challenge using CLI prompt.`) default: return fmt.Errorf("%q is not yet supported", name) } diff --git a/internal/dns/providers/dns_providers.go.tmpl b/internal/dns/providers/dns_providers.go.tmpl index c974ef6a9..2030a3ed0 100644 --- a/internal/dns/providers/dns_providers.go.tmpl +++ b/internal/dns/providers/dns_providers.go.tmpl @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" {{- range $provider := .Providers }} "github.com/go-acme/lego/v4/providers/dns/{{ cleanName $provider.Code }}" {{- end}} @@ -14,6 +15,8 @@ import ( // NewDNSChallengeProviderByName Factory for DNS providers. func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { switch name { + case "manual": + return dns01.NewDNSProviderManual() {{- range $provider := .Providers }} case "{{ $provider.Code }}"{{range $alias := $provider.Aliases }},"{{ $alias }}"{{end}}: return {{ cleanName $provider.Code }}.NewDNSProvider() diff --git a/providers/dns/acmedns/acmedns.toml b/providers/dns/acmedns/acmedns.toml index e491569b0..6d68a013d 100644 --- a/providers/dns/acmedns/acmedns.toml +++ b/providers/dns/acmedns/acmedns.toml @@ -8,13 +8,13 @@ Since = "v1.1.0" Example = ''' ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ -lego --dns "acme-dns" -d '*.example.com' -d example.com run +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run # or ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ -lego --dns "acme-dns" -d '*.example.com' -d example.com run +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go index 0b925de6a..c8107cab6 100644 --- a/providers/dns/active24/active24.go +++ b/providers/dns/active24/active24.go @@ -2,15 +2,17 @@ package active24 import ( + "context" "errors" "fmt" "net/http" + "strconv" "time" - "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "active24.cz" @@ -29,7 +31,15 @@ const ( ) // Config is used to configure the creation of the DNSProvider. -type Config = active24.Config +type Config struct { + APIKey string + Secret string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -45,7 +55,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *active24.Client } // NewDNSProvider returns a DNSProvider instance configured for Active24. @@ -68,29 +79,83 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("active24: the configuration of the DNS provider is nil") } - provider, err := active24.NewDNSProviderConfig(config, baseAPIDomain) + client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) if err != nil { return nil, fmt.Errorf("active24: %w", err) } - return &DNSProvider{prv: provider}, nil + 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 { - err := d.prv.Present(domain, token, keyAuth) + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { return fmt.Errorf("active24: %w", err) } + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("active24: find service ID: %w", err) + } + + record := active24.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) + if err != nil { + return fmt.Errorf("active24: create record: %w", err) + } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("active24: %w", err) + return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("active24: find service ID: %w", err) + } + + recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) + if err != nil { + return fmt.Errorf("active24: find record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) + if err != nil { + return fmt.Errorf("active24: delete record %w", err) } return nil @@ -99,5 +164,58 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { + services, err := d.client.GetServices(ctx) + if err != nil { + return 0, fmt.Errorf("get services: %w", err) + } + + for _, service := range services { + if service.ServiceName != "domain" { + continue + } + + if service.Name != domain { + continue + } + + return service.ID, nil + } + + return 0, fmt.Errorf("service not found for domain: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { + // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. + filter := active24.RecordFilter{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: []string{"TXT"}, + Content: info.Value, + } + + records, err := d.client.GetRecords(ctx, serviceID, filter) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" { + continue + } + + if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { + continue + } + + if record.Content != info.Value { + continue + } + + return record.ID, nil + } + + return 0, errors.New("no record found") } diff --git a/providers/dns/active24/active24.toml b/providers/dns/active24/active24.toml index b0eaabab8..6a54d4695 100644 --- a/providers/dns/active24/active24.toml +++ b/providers/dns/active24/active24.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' ACTIVE24_API_KEY="xxx" \ ACTIVE24_SECRET="yyy" \ -lego --dns active24 -d '*.example.com' -d example.com run +lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/active24/active24_test.go b/providers/dns/active24/active24_test.go index 2987fb27b..363e0229a 100644 --- a/providers/dns/active24/active24_test.go +++ b/providers/dns/active24/active24_test.go @@ -60,7 +60,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -109,7 +110,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } 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..49a9aeeab 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -7,13 +7,13 @@ Since = "v1.1.0" Example = ''' # Setup using instance RAM role ALICLOUD_RAM_ROLE=lego \ -lego --dns alidns -d '*.example.com' -d example.com run +lego --email you@example.com --dns alidns -d '*.example.com' -d example.com run # Or, using credentials ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALICLOUD_SECRET_KEY=your-secret-key \ ALICLOUD_SECURITY_TOKEN=your-sts-token \ -lego --dns alidns - -d '*.example.com' -d example.com run +lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com run ''' [Configuration] @@ -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/aliesa/aliesa.go b/providers/dns/aliesa/aliesa.go deleted file mode 100644 index 2a38389be..000000000 --- a/providers/dns/aliesa/aliesa.go +++ /dev/null @@ -1,255 +0,0 @@ -// Package aliesa implements a DNS provider for solving the DNS-01 challenge using AlibabaCloud ESA. -package aliesa - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" - "github.com/alibabacloud-go/tea/dara" - "github.com/aliyun/credentials-go/credentials" - esa "github.com/go-acme/esa-20240910/v2/client" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/ptr" -) - -// Environment variables names. -const ( - envNamespace = "ALIESA_" - - EnvRAMRole = envNamespace + "RAM_ROLE" - EnvAccessKey = envNamespace + "ACCESS_KEY" - EnvSecretKey = envNamespace + "SECRET_KEY" - EnvSecurityToken = envNamespace + "SECURITY_TOKEN" - EnvRegionID = envNamespace + "REGION_ID" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultRegionID = "cn-hangzhou" - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - RAMRole string - APIKey string - SecretKey string - SecurityToken string - RegionID string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *esa.Client - - recordIDs map[string]int64 - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for AlibabaCloud ESA. -func NewDNSProvider() (*DNSProvider, error) { - config := NewDefaultConfig() - config.RegionID = env.GetOrFile(EnvRegionID) - - values, err := env.Get(EnvRAMRole) - if err == nil { - config.RAMRole = values[EnvRAMRole] - return NewDNSProviderConfig(config) - } - - values, err = env.Get(EnvAccessKey, EnvSecretKey) - if err != nil { - return nil, fmt.Errorf("aliesa: %w", err) - } - - config.APIKey = values[EnvAccessKey] - config.SecretKey = values[EnvSecretKey] - config.SecurityToken = env.GetOrFile(EnvSecurityToken) - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for AlibabaCloud ESA. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("aliesa: the configuration of the DNS provider is nil") - } - - if config.RegionID == "" { - config.RegionID = defaultRegionID - } - - cfg := new(openapi.Config). - SetRegionId(config.RegionID). - SetReadTimeout(int(config.HTTPTimeout.Milliseconds())) - - switch { - case config.RAMRole != "": - // https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance - credentialsCfg := new(credentials.Config). - SetType("ecs_ram_role"). - SetRoleName(config.RAMRole) - - credentialClient, err := credentials.NewCredential(credentialsCfg) - if err != nil { - return nil, fmt.Errorf("aliesa: new credential: %w", err) - } - - cfg = cfg.SetCredential(credentialClient) - - case config.APIKey != "" && config.SecretKey != "" && config.SecurityToken != "": - cfg = cfg. - SetAccessKeyId(config.APIKey). - SetAccessKeySecret(config.SecretKey). - SetSecurityToken(config.SecurityToken) - - case config.APIKey != "" && config.SecretKey != "": - cfg = cfg. - SetAccessKeyId(config.APIKey). - SetAccessKeySecret(config.SecretKey) - - default: - return nil, errors.New("aliesa: ram role or credentials missing") - } - - client, err := esa.NewClient(cfg) - if err != nil { - return nil, fmt.Errorf("aliesa: new client: %w", err) - } - - // Workaround to get a regional URL. - // https://github.com/alibabacloud-go/esa-20240910/blame/7660e3aab2045d4820e4b83427a154efe0c79319/client/client.go#L27 - // The `EndpointRule` is hardcoded with an empty string, so the region is ignored. - client.Endpoint = nil - client.EndpointRule = ptr.Pointer("regional") - - client.Endpoint, err = esa.GetEndpoint(client, dara.String("esa"), client.RegionId, client.EndpointRule, client.Network, client.Suffix, client.EndpointMap, client.Endpoint) - if err != nil { - return nil, fmt.Errorf("aliesa: get endpoint: %w", err) - } - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int64), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - siteID, err := d.getSiteID(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("aliesa: %w", err) - } - - crReq := new(esa.CreateRecordRequest). - SetSiteId(siteID). - SetType("TXT"). - SetRecordName(dns01.UnFqdn(info.EffectiveFQDN)). - SetTtl(int32(d.config.TTL)). - SetData(new(esa.CreateRecordRequestData).SetValue(info.Value)) - - // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-createrecord - crResp, err := esa.CreateRecordWithContext(ctx, d.client, crReq, &dara.RuntimeOptions{}) - if err != nil { - return fmt.Errorf("aliesa: create record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = ptr.Deref(crResp.Body.GetRecordId()) - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("aliesa: unknown record ID for '%s'", info.EffectiveFQDN) - } - - drReq := new(esa.DeleteRecordRequest). - SetRecordId(recordID) - - // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-deleterecord - _, err := esa.DeleteRecordWithContext(ctx, d.client, drReq, &dara.RuntimeOptions{}) - if err != nil { - return fmt.Errorf("aliesa: delete record: %w", err) - } - - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) getSiteID(ctx context.Context, fqdn string) (int64, error) { - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return 0, fmt.Errorf("aliesa: could not find zone for domain %q: %w", fqdn, err) - } - - lsReq := new(esa.ListSitesRequest). - SetSiteName(dns01.UnFqdn(authZone)). - SetSiteSearchType("suffix") - - // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites - lsResp, err := esa.ListSitesWithContext(ctx, d.client, lsReq, &dara.RuntimeOptions{}) - if err != nil { - return 0, fmt.Errorf("list sites: %w", err) - } - - for f := range dns01.UnFqdnDomainsSeq(fqdn) { - domain := dns01.UnFqdn(f) - - for _, site := range lsResp.Body.GetSites() { - if ptr.Deref(site.GetSiteName()) == domain { - return ptr.Deref(site.GetSiteId()), nil - } - } - } - - return 0, fmt.Errorf("site not found (fqdn: %q)", fqdn) -} diff --git a/providers/dns/aliesa/aliesa.toml b/providers/dns/aliesa/aliesa.toml deleted file mode 100644 index 5e7345e40..000000000 --- a/providers/dns/aliesa/aliesa.toml +++ /dev/null @@ -1,33 +0,0 @@ -Name = "AlibabaCloud ESA" -Description = '''''' -URL = "https://www.alibabacloud.com/en/product/esa" -Code = "aliesa" -Since = "v4.29.0" - -Example = ''' -# Setup using instance RAM role -ALIESA_RAM_ROLE=lego \ -lego --dns aliesa -d '*.example.com' -d example.com run - -# Or, using credentials -ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ -ALIESA_SECRET_KEY=your-secret-key \ -ALIESA_SECURITY_TOKEN=your-sts-token \ -lego --dns aliesa - -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - ALIESA_RAM_ROLE = "Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)" - ALIESA_ACCESS_KEY = "Access key ID" - ALIESA_SECRET_KEY = "Access Key secret" - ALIESA_SECURITY_TOKEN = "STS Security Token (optional)" - [Configuration.Additional] - ALIESA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - ALIESA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - ALIESA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - ALIESA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records" - GoClient = "https://github.com/alibabacloud-go/esa-20240910" diff --git a/providers/dns/aliesa/aliesa_test.go b/providers/dns/aliesa/aliesa_test.go deleted file mode 100644 index 025529409..000000000 --- a/providers/dns/aliesa/aliesa_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package aliesa - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvAccessKey, - EnvSecretKey, - EnvRAMRole). - WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAccessKey: "123", - EnvSecretKey: "456", - }, - }, - { - desc: "success (RAM role)", - envVars: map[string]string{ - EnvRAMRole: "LegoInstanceRole", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvAccessKey: "", - EnvSecretKey: "", - }, - expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY,ALIESA_SECRET_KEY", - }, - { - desc: "missing access key", - envVars: map[string]string{ - EnvAccessKey: "", - EnvSecretKey: "456", - }, - expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY", - }, - { - desc: "missing secret key", - envVars: map[string]string{ - EnvAccessKey: "123", - EnvSecretKey: "", - }, - expected: "aliesa: some credentials information are missing: ALIESA_SECRET_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - ramRole string - apiKey string - secretKey string - expected string - }{ - { - desc: "success", - apiKey: "123", - secretKey: "456", - }, - { - desc: "success", - ramRole: "LegoInstanceRole", - }, - { - desc: "missing credentials", - expected: "aliesa: ram role or credentials missing", - }, - { - desc: "missing api key", - secretKey: "456", - expected: "aliesa: ram role or credentials missing", - }, - { - desc: "missing secret key", - apiKey: "123", - expected: "aliesa: ram role or credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - config.SecretKey = test.secretKey - config.RAMRole = test.ramRole - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 376b0903c..a5b27ff59 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,20 +121,20 @@ 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) if err != nil { - return fmt.Errorf("allinkl: authentication: %w", err) + return fmt.Errorf("allinkl: %w", err) } 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) @@ -150,7 +149,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { recordID, err := d.client.AddDNSSettings(ctx, record) if err != nil { - return fmt.Errorf("allinkl: add DNS settings: %w", err) + return fmt.Errorf("allinkl: %w", err) } d.recordIDsMu.Lock() @@ -168,7 +167,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { credential, err := d.identifier.Authentication(ctx, 60, true) if err != nil { - return fmt.Errorf("allinkl: authentication: %w", err) + return fmt.Errorf("allinkl: %w", err) } ctx = internal.WithContext(ctx, credential) @@ -184,26 +183,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { _, err = d.client.DeleteDNSSettings(ctx, recordID) if err != nil { - return fmt.Errorf("allinkl: delete DNS settings: %w", err) + return fmt.Errorf("allinkl: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - 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.toml b/providers/dns/allinkl/allinkl.toml index 774f8fb9f..d9c937ee1 100644 --- a/providers/dns/allinkl/allinkl.toml +++ b/providers/dns/allinkl/allinkl.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --dns allinkl -d '*.example.com' -d example.com run +lego --email you@example.com --dns allinkl -d '*.example.com' -d example.com run ''' [Configuration] 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/alwaysdata/alwaysdata.go b/providers/dns/alwaysdata/alwaysdata.go deleted file mode 100644 index b2e0f3957..000000000 --- a/providers/dns/alwaysdata/alwaysdata.go +++ /dev/null @@ -1,185 +0,0 @@ -// Package alwaysdata implements a DNS provider for solving the DNS-01 challenge using Alwaysdata. -package alwaysdata - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/alwaysdata/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "ALWAYSDATA_" - - EnvAPIKey = envNamespace + "API_KEY" - EnvAccount = envNamespace + "ACCOUNT" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - Account string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for Alwaysdata. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("alwaysdata: %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - config.Account = env.GetOrFile(EnvAccount) - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Alwaysdata. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("alwaysdata: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIKey, config.Account) - if err != nil { - return nil, fmt.Errorf("alwaysdata: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - zone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("alwaysdata: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) - if err != nil { - return fmt.Errorf("alwaysdata: %w", err) - } - - record := internal.RecordRequest{ - DomainID: zone.ID, - Name: subDomain, - Type: "TXT", - Value: info.Value, - TTL: d.config.TTL, - Annotation: "lego", - } - - err = d.client.AddRecord(ctx, record) - if err != nil { - return fmt.Errorf("alwaysdata: add TXT record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - zone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("alwaysdata: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) - if err != nil { - return fmt.Errorf("alwaysdata: %w", err) - } - - records, err := d.client.ListRecords(ctx, zone.ID, subDomain) - if err != nil { - return fmt.Errorf("alwaysdata: list records: %w", err) - } - - for _, record := range records { - if record.Type != "TXT" || record.Value != info.Value { - continue - } - - err = d.client.DeleteRecord(ctx, record.ID) - if err != nil { - return fmt.Errorf("alwaysdata: delete TXT record: %w", err) - } - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*internal.Domain, error) { - domains, err := d.client.ListDomains(ctx) - if err != nil { - return nil, fmt.Errorf("list domains: %w", err) - } - - for a := range dns01.UnFqdnDomainsSeq(fqdn) { - for _, domain := range domains { - if a == domain.Name { - return &domain, nil - } - } - } - - return nil, errors.New("domain not found") -} diff --git a/providers/dns/alwaysdata/alwaysdata.toml b/providers/dns/alwaysdata/alwaysdata.toml deleted file mode 100644 index d00c6f032..000000000 --- a/providers/dns/alwaysdata/alwaysdata.toml +++ /dev/null @@ -1,26 +0,0 @@ -Name = "Alwaysdata" -Description = '''''' -URL = "https://alwaysdata.com/" -Code = "alwaysdata" -Since = "v4.31.0" - -Example = ''' -ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns alwaysdata -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - ALWAYSDATA_API_KEY = "API Key" - [Configuration.Additional] - ALWAYSDATA_ACCOUNT = "Account name" - ALWAYSDATA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - ALWAYSDATA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - ALWAYSDATA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - ALWAYSDATA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://help.alwaysdata.com/en/api/resources/" - APIDocDomains = "https://api.alwaysdata.com/v1/domain/doc/" - APIDocRecords = "https://api.alwaysdata.com/v1/record/doc/" - APIExamples = "https://help.alwaysdata.com/en/api/examples/" diff --git a/providers/dns/alwaysdata/alwaysdata_test.go b/providers/dns/alwaysdata/alwaysdata_test.go deleted file mode 100644 index 6084c2ae4..000000000 --- a/providers/dns/alwaysdata/alwaysdata_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package alwaysdata - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIKey, EnvAccount).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIKey: "secret", - }, - }, - { - desc: "success with an account", - envVars: map[string]string{ - EnvAPIKey: "secret", - EnvAccount: "foo", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "alwaysdata: some credentials information are missing: ALWAYSDATA_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - account string - expected string - }{ - { - desc: "success", - apiKey: "secret", - }, - { - desc: "success with an account", - apiKey: "secret", - account: "foo", - }, - { - desc: "missing credentials", - expected: "alwaysdata: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - config.Account = test.account - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIKey = "secret" - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithBasicAuth("secret", ""), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /domain/", - servermock.ResponseFromInternal("domains.json")). - Route("POST /record/", - servermock.Noop().WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromInternal("record_add-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("GET /domain/", - servermock.ResponseFromInternal("domains.json")). - Route("GET /record/", - servermock.ResponseFromInternal("records.json"), - servermock.CheckQueryParameter().Strict(). - With("domain", "132"). - With("name", "_acme-challenge"), - ). - Route("DELETE /record/789/", - servermock.Noop()). - Build(t) - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/alwaysdata/internal/client.go b/providers/dns/alwaysdata/internal/client.go deleted file mode 100644 index 5db11dcd1..000000000 --- a/providers/dns/alwaysdata/internal/client.go +++ /dev/null @@ -1,177 +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.alwaysdata.com/v1" - -// Client the Alwaysdata API client. -type Client struct { - apiKey string - account string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(apiKey, account string) (*Client, error) { - if apiKey == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiKey: apiKey, - account: account, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { - endpoint := c.BaseURL.JoinPath("domain", "/") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result []Domain - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) AddRecord(ctx context.Context, record RecordRequest) error { - endpoint := c.BaseURL.JoinPath("record", "/") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return err - } - - err = c.do(req, nil) - if err != nil { - return err - } - - return nil -} - -func (c *Client) DeleteRecord(ctx context.Context, recordID int64) error { - endpoint := c.BaseURL.JoinPath("record", strconv.FormatInt(recordID, 10), "/") - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) ListRecords(ctx context.Context, domainID int64, name string) ([]Record, error) { - endpoint := c.BaseURL.JoinPath("record", "/") - - query := endpoint.Query() - query.Set("domain", strconv.FormatInt(domainID, 10)) - query.Set("name", name) - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result []Record - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - user := c.apiKey - - if c.account != "" { - user += "account=" + c.account - } - - req.SetBasicAuth(user, "") - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} diff --git a/providers/dns/alwaysdata/internal/client_test.go b/providers/dns/alwaysdata/internal/client_test.go deleted file mode 100644 index e6a349662..000000000 --- a/providers/dns/alwaysdata/internal/client_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package internal - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder[*Client]( - func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret", "") - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = clientdebug.Wrap(server.Client()) - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithBasicAuth("secret", ""), - ) -} - -func TestClient_ListDomains(t *testing.T) { - client := mockBuilder(). - Route("GET /domain/", - servermock.ResponseFromFixture("domains.json")). - Build(t) - - result, err := client.ListDomains(t.Context()) - require.NoError(t, err) - - expected := []Domain{ - {ID: 132, Name: "example.com", Annotation: "test"}, - {ID: 133, Name: "example.net", IsInternal: true}, - {ID: 134, Name: "example.org"}, - } - - assert.Equal(t, expected, result) -} - -func TestClient_AddRecord(t *testing.T) { - t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") - - client := mockBuilder(). - Route("POST /record/", - servermock.Noop().WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFixture("record_add-request.json")). - Build(t) - - record := RecordRequest{ - DomainID: 132, - Name: "_acme-challenge", - Type: "TXT", - Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - Annotation: "lego", - } - - err := client.AddRecord(t.Context(), record) - require.NoError(t, err) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /record/789/", - servermock.Noop()). - Build(t) - - err := client.DeleteRecord(t.Context(), 789) - require.NoError(t, err) -} - -func TestClient_ListRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /record/", - servermock.ResponseFromFixture("records.json"), - servermock.CheckQueryParameter().Strict(). - With("domain", "132"). - With("name", "_acme-challenge"), - ). - Build(t) - - result, err := client.ListRecords(t.Context(), 132, "_acme-challenge") - require.NoError(t, err) - - expected := []Record{ - { - ID: 789, - Domain: &Domain{ - Href: "/v1/domain/132/", - }, - Type: "TXT", - Name: "_acme-challenge", - Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - Annotation: "lego", - }, - { - ID: 11619270, - Domain: &Domain{ - Href: "/v1/domain/118935/", - }, - Name: "home", - Type: "A", - Value: "149.202.90.65", - TTL: 300, - IsUserDefined: true, - IsActive: true, - }, - } - - assert.Equal(t, expected, result) -} diff --git a/providers/dns/alwaysdata/internal/fixtures/domains.json b/providers/dns/alwaysdata/internal/fixtures/domains.json deleted file mode 100644 index dc34a948f..000000000 --- a/providers/dns/alwaysdata/internal/fixtures/domains.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "id": 132, - "name": "example.com", - "annotation": "test" - }, - { - "id": 133, - "name": "example.net", - "is_internal": true - }, - { - "id": 134, - "name": "example.org" - } -] diff --git a/providers/dns/alwaysdata/internal/fixtures/record_add-request.json b/providers/dns/alwaysdata/internal/fixtures/record_add-request.json deleted file mode 100644 index 5b6db2646..000000000 --- a/providers/dns/alwaysdata/internal/fixtures/record_add-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": 132, - "name": "_acme-challenge", - "type": "TXT", - "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120, - "annotation": "lego" -} diff --git a/providers/dns/alwaysdata/internal/fixtures/records.json b/providers/dns/alwaysdata/internal/fixtures/records.json deleted file mode 100644 index fa207395a..000000000 --- a/providers/dns/alwaysdata/internal/fixtures/records.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "id": 789, - "domain": { - "href": "/v1/domain/132/" - }, - "name": "_acme-challenge", - "type": "TXT", - "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120, - "annotation": "lego" - }, - { - "id": 11619270, - "domain": { - "href": "/v1/domain/118935/" - }, - "type": "A", - "name": "home", - "value": "149.202.90.65", - "priority": null, - "ttl": 300, - "href": "/v1/record/11619270/", - "annotation": "", - "is_user_defined": true, - "is_active": true - } -] diff --git a/providers/dns/alwaysdata/internal/types.go b/providers/dns/alwaysdata/internal/types.go deleted file mode 100644 index b1e66fa5b..000000000 --- a/providers/dns/alwaysdata/internal/types.go +++ /dev/null @@ -1,33 +0,0 @@ -package internal - -type RecordRequest struct { - ID int64 `json:"id,omitempty"` - DomainID int64 `json:"domain,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Value string `json:"value,omitempty"` - TTL int `json:"ttl,omitempty"` - Annotation string `json:"annotation,omitempty"` - IsUserDefined bool `json:"is_user_defined,omitempty"` - IsActive bool `json:"is_active,omitempty"` -} - -type Record struct { - ID int64 `json:"id,omitempty"` - Domain *Domain `json:"domain,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` - TTL int `json:"ttl,omitempty"` - Annotation string `json:"annotation,omitempty"` - IsUserDefined bool `json:"is_user_defined,omitempty"` - IsActive bool `json:"is_active,omitempty"` -} - -type Domain struct { - ID int64 `json:"id,omitempty"` - Href string `json:"href,omitempty"` - Name string `json:"name,omitempty"` - IsInternal bool `json:"is_internal,omitempty"` - Annotation string `json:"annotation,omitempty"` -} diff --git a/providers/dns/anexia/anexia.toml b/providers/dns/anexia/anexia.toml index 332f0b8b1..4fad8ea48 100644 --- a/providers/dns/anexia/anexia.toml +++ b/providers/dns/anexia/anexia.toml @@ -6,7 +6,7 @@ Since = "v4.28.0" Example = ''' ANEXIA_TOKEN=xxx \ -lego --dns anexia -d '*.example.com' -d example.com run +lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run ''' Additional = ''' 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/arvancloud/arvancloud.toml b/providers/dns/arvancloud/arvancloud.toml index aa5cafb51..e94452a8b 100644 --- a/providers/dns/arvancloud/arvancloud.toml +++ b/providers/dns/arvancloud/arvancloud.toml @@ -6,7 +6,7 @@ Since = "v3.8.0" Example = ''' ARVANCLOUD_API_KEY="Apikey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \ -lego --dns arvancloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns arvancloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 50d2fbc25..95d6ab759 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -53,11 +53,10 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *auroradns.Client - recordIDs map[string]string recordIDsMu sync.Mutex + config *Config + client *auroradns.Client } // NewDNSProvider returns a DNSProvider instance configured for AuroraDNS. diff --git a/providers/dns/auroradns/auroradns.toml b/providers/dns/auroradns/auroradns.toml index 59b5e7ab1..e000e015e 100644 --- a/providers/dns/auroradns/auroradns.toml +++ b/providers/dns/auroradns/auroradns.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' AURORA_API_KEY=xxxxx \ AURORA_SECRET=yyyyyy \ -lego --dns auroradns -d '*.example.com' -d example.com run +lego --email you@example.com --dns auroradns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go index 8a9361bc0..770bac99b 100644 --- a/providers/dns/autodns/autodns.go +++ b/providers/dns/autodns/autodns.go @@ -128,9 +128,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Value: info.Value, }} - _, err := d.client.AddRecords(context.Background(), info.EffectiveFQDN, records) + _, err := d.client.AddTxtRecords(context.Background(), info.EffectiveFQDN, records) if err != nil { - return fmt.Errorf("autodns: add record: %w", err) + return fmt.Errorf("autodns: %w", err) } return nil @@ -147,9 +147,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { Value: info.Value, }} - _, err := d.client.RemoveRecords(context.Background(), info.EffectiveFQDN, records) - if err != nil { - return fmt.Errorf("autodns: remove record: %w", err) + if err := d.client.RemoveTXTRecords(context.Background(), info.EffectiveFQDN, records); err != nil { + return fmt.Errorf("autodns: %w", err) } return nil diff --git a/providers/dns/autodns/autodns.toml b/providers/dns/autodns/autodns.toml index 2798d4cee..78015e431 100644 --- a/providers/dns/autodns/autodns.toml +++ b/providers/dns/autodns/autodns.toml @@ -7,7 +7,7 @@ Since = "v3.2.0" Example = ''' AUTODNS_API_USER=username \ AUTODNS_API_PASSWORD=supersecretpassword \ -lego --dns autodns -d '*.example.com' -d example.com run +lego --email you@example.com --dns autodns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/autodns/internal/client.go b/providers/dns/autodns/internal/client.go index d92490a60..547596f81 100644 --- a/providers/dns/autodns/internal/client.go +++ b/providers/dns/autodns/internal/client.go @@ -43,22 +43,24 @@ func NewClient(username, password string, clientContext int) *Client { } } -// AddRecords adds records. -func (c *Client) AddRecords(ctx context.Context, domain string, records []*ResourceRecord) (*DataZoneResponse, error) { +// AddTxtRecords adds TXT records. +func (c *Client) AddTxtRecords(ctx context.Context, domain string, records []*ResourceRecord) (*Zone, error) { zoneStream := &ZoneStream{Adds: records} return c.updateZone(ctx, domain, zoneStream) } -// RemoveRecords removes records. -func (c *Client) RemoveRecords(ctx context.Context, domain string, records []*ResourceRecord) (*DataZoneResponse, error) { +// RemoveTXTRecords removes TXT records. +func (c *Client) RemoveTXTRecords(ctx context.Context, domain string, records []*ResourceRecord) error { zoneStream := &ZoneStream{Removes: records} - return c.updateZone(ctx, domain, zoneStream) + _, err := c.updateZone(ctx, domain, zoneStream) + + return err } // https://github.com/InterNetX/domainrobot-api/blob/bdc8fe92a2f32fcbdb29e30bf6006ab446f81223/src/domainrobot.json#L21090 -func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *ZoneStream) (*DataZoneResponse, error) { +func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *ZoneStream) (*Zone, error) { endpoint := c.BaseURL.JoinPath("zone", domain, "_stream") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, zoneStream) @@ -66,12 +68,12 @@ func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *Zone return nil, err } - var resp *DataZoneResponse - if err := c.do(req, &resp); err != nil { + var zone *Zone + if err := c.do(req, &zone); err != nil { return nil, err } - return resp, nil + return zone, nil } func (c *Client) do(req *http.Request, result any) error { @@ -86,7 +88,7 @@ func (c *Client) do(req *http.Request, result any) error { defer func() { _ = resp.Body.Close() }() if resp.StatusCode/100 != 2 { - return parseError(req, resp) + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) } if result == nil { @@ -129,16 +131,3 @@ func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, paylo return req, nil } - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/autodns/internal/client_test.go b/providers/dns/autodns/internal/client_test.go index 9b0968fdc..6fc31ca34 100644 --- a/providers/dns/autodns/internal/client_test.go +++ b/providers/dns/autodns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "net/http" "net/http/httptest" "net/url" "testing" @@ -25,7 +24,7 @@ func mockBuilder() *servermock.Builder[*Client] { WithJSONHeaders()) } -func TestClient_AddRecords(t *testing.T) { +func TestClient_AddTxtRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("add_record.json"), @@ -34,81 +33,28 @@ func TestClient_AddRecords(t *testing.T) { With("X-Domainrobot-Context", "123")). Build(t) - records := []*ResourceRecord{{ - Name: "example.com", - TTL: 600, - Type: "TXT", - Value: "txtTXTtxt", - }} + records := []*ResourceRecord{{}} - resp, err := client.AddRecords(t.Context(), "example.com", records) + zone, err := client.AddTxtRecords(t.Context(), "example.com", records) require.NoError(t, err) - expected := &DataZoneResponse{ - STID: "20251121-appf4923-126284", - CTID: "", - Messages: []ResponseMessage{ - { - Text: "string", - Messages: []string{ - "string", - }, - Objects: []GenericObject{ - { - Type: "string", - Value: "string", - }, - }, - Code: "string", - Status: "SUCCESS", - }, - }, - Status: &ResponseStatus{ - Code: "S0301", - Text: "Zone was updated successfully on the name server.", - Type: "SUCCESS", - }, - Object: nil, - Data: []Zone{ - { - Name: "example.com", - ResourceRecords: []ResourceRecord{ - { - Name: "example.com", - TTL: 120, - Type: "TXT", - Value: "txt", - Pref: 1, - }, - }, - Action: "xxx", - VirtualNameServer: "yyy", - }, - }, + expected := &Zone{ + Name: "example.com", + ResourceRecords: []*ResourceRecord{{ + Name: "example.com", + TTL: 120, + Type: "TXT", + Value: "txt", + Pref: 1, + }}, + Action: "xxx", + VirtualNameServer: "yyy", } - assert.Equal(t, expected, resp) + assert.Equal(t, expected, zone) } -func TestClient_AddRecords_error(t *testing.T) { - client := mockBuilder(). - Route("POST /zone/example.com/_stream", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - records := []*ResourceRecord{{ - Name: "example.com", - TTL: 600, - Type: "TXT", - Value: "txtTXTtxt", - }} - - _, err := client.AddRecords(t.Context(), "example.com", records) - require.EqualError(t, err, `STID: 20251121-appf4923-126284, status: code: E0202002, text: Zone konnte auf dem Nameserver nicht aktualisiert werden., type: ERROR, message: code: EF02022, text: Der Zusatzeintrag wurde doppelt eingetragen., status: ERROR, object: OURDOMAIN.TLD@nsa7.schlundtech.de/rr[17]: _acme-challenge.www.whoami.int.OURDOMAIN.TLD TXT "rK2SJb_ZcrYefbfCKU6jZEANfEAJeOtSh1Fv8hkUoVc"`) -} - -func TestClient_RemoveRecords(t *testing.T) { +func TestClient_RemoveTXTRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("remove_record.json"), @@ -117,58 +63,8 @@ func TestClient_RemoveRecords(t *testing.T) { With("X-Domainrobot-Context", "123")). Build(t) - records := []*ResourceRecord{{ - Name: "example.com", - TTL: 600, - Type: "TXT", - Value: "txtTXTtxt", - }} + records := []*ResourceRecord{{}} - resp, err := client.RemoveRecords(t.Context(), "example.com", records) + err := client.RemoveTXTRecords(t.Context(), "example.com", records) require.NoError(t, err) - - expected := &DataZoneResponse{ - STID: "20251121-appf4923-126284", - CTID: "", - Messages: []ResponseMessage{ - { - Text: "string", - Messages: []string{ - "string", - }, - Objects: []GenericObject{ - { - Type: "string", - Value: "string", - }, - }, - Code: "string", - Status: "SUCCESS", - }, - }, - Status: &ResponseStatus{ - Code: "S0301", - Text: "Zone was updated successfully on the name server.", - Type: "SUCCESS", - }, - Object: nil, - Data: []Zone{ - { - Name: "example.com", - ResourceRecords: []ResourceRecord{ - { - Name: "example.com", - TTL: 120, - Type: "TXT", - Value: "txt", - Pref: 1, - }, - }, - Action: "xxx", - VirtualNameServer: "yyy", - }, - }, - } - - assert.Equal(t, expected, resp) } diff --git a/providers/dns/autodns/internal/fixtures/add_record-request.json b/providers/dns/autodns/internal/fixtures/add_record-request.json index 6105c77ac..b798b4fbd 100644 --- a/providers/dns/autodns/internal/fixtures/add_record-request.json +++ b/providers/dns/autodns/internal/fixtures/add_record-request.json @@ -1,10 +1,10 @@ { "adds": [ { - "name": "example.com", - "ttl": 600, - "type": "TXT", - "value": "txtTXTtxt" + "name": "", + "ttl": 0, + "type": "", + "value": "" } ], "rems": null diff --git a/providers/dns/autodns/internal/fixtures/add_record.json b/providers/dns/autodns/internal/fixtures/add_record.json index a0ce66ba6..4a95f0784 100644 --- a/providers/dns/autodns/internal/fixtures/add_record.json +++ b/providers/dns/autodns/internal/fixtures/add_record.json @@ -1,41 +1,14 @@ { - "stid": "20251121-appf4923-126284", - "messages": [ + "origin": "example.com", + "resourceRecords": [ { - "text": "string", - "notice": "string", - "messages": [ - "string" - ], - "objects": [ - { - "type": "string", - "value": "string" - } - ], - "code": "string", - "status": "SUCCESS" + "name": "example.com", + "ttl": 120, + "type": "TXT", + "value": "txt", + "pref": 1 } ], - "status": { - "code": "S0301", - "text": "Zone was updated successfully on the name server.", - "type": "SUCCESS" - }, - "data": [ - { - "origin": "example.com", - "resourceRecords": [ - { - "name": "example.com", - "ttl": 120, - "type": "TXT", - "value": "txt", - "pref": 1 - } - ], - "action": "xxx", - "virtualNameServer": "yyy" - } - ] + "action": "xxx", + "virtualNameServer": "yyy" } diff --git a/providers/dns/autodns/internal/fixtures/error.json b/providers/dns/autodns/internal/fixtures/error.json deleted file mode 100644 index 2ed635d58..000000000 --- a/providers/dns/autodns/internal/fixtures/error.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "stid": "20251121-appf4923-126284", - "messages": [ - { - "text": "Der Zusatzeintrag wurde doppelt eingetragen.", - "objects": [ - { - "type": "OURDOMAIN.TLD@nsa7.schlundtech.de/rr[17]", - "value": "_acme-challenge.www.whoami.int.OURDOMAIN.TLD TXT \"rK2SJb_ZcrYefbfCKU6jZEANfEAJeOtSh1Fv8hkUoVc\"" - } - ], - "code": "EF02022", - "status": "ERROR" - } - ], - "status": { - "code": "E0202002", - "text": "Zone konnte auf dem Nameserver nicht aktualisiert werden.", - "type": "ERROR" - } -} diff --git a/providers/dns/autodns/internal/fixtures/remove_record-request.json b/providers/dns/autodns/internal/fixtures/remove_record-request.json index 92361403e..0702c7367 100644 --- a/providers/dns/autodns/internal/fixtures/remove_record-request.json +++ b/providers/dns/autodns/internal/fixtures/remove_record-request.json @@ -2,10 +2,10 @@ "adds": null, "rems": [ { - "name": "example.com", - "ttl": 600, - "type": "TXT", - "value": "txtTXTtxt" + "name": "", + "ttl": 0, + "type": "", + "value": "" } ] } diff --git a/providers/dns/autodns/internal/fixtures/remove_record.json b/providers/dns/autodns/internal/fixtures/remove_record.json index a0ce66ba6..4a95f0784 100644 --- a/providers/dns/autodns/internal/fixtures/remove_record.json +++ b/providers/dns/autodns/internal/fixtures/remove_record.json @@ -1,41 +1,14 @@ { - "stid": "20251121-appf4923-126284", - "messages": [ + "origin": "example.com", + "resourceRecords": [ { - "text": "string", - "notice": "string", - "messages": [ - "string" - ], - "objects": [ - { - "type": "string", - "value": "string" - } - ], - "code": "string", - "status": "SUCCESS" + "name": "example.com", + "ttl": 120, + "type": "TXT", + "value": "txt", + "pref": 1 } ], - "status": { - "code": "S0301", - "text": "Zone was updated successfully on the name server.", - "type": "SUCCESS" - }, - "data": [ - { - "origin": "example.com", - "resourceRecords": [ - { - "name": "example.com", - "ttl": 120, - "type": "TXT", - "value": "txt", - "pref": 1 - } - ], - "action": "xxx", - "virtualNameServer": "yyy" - } - ] + "action": "xxx", + "virtualNameServer": "yyy" } diff --git a/providers/dns/autodns/internal/types.go b/providers/dns/autodns/internal/types.go index 8a06f4889..93fd678ca 100644 --- a/providers/dns/autodns/internal/types.go +++ b/providers/dns/autodns/internal/types.go @@ -1,133 +1,33 @@ package internal -import ( - "fmt" - "strings" -) - -type APIResponse[T any] struct { - STID string `json:"stid"` - CTID string `json:"ctid"` - Messages []ResponseMessage `json:"messages"` - Status *ResponseStatus `json:"status"` - Object *ResponseObject `json:"object"` - Data T `json:"data"` -} - -type APIError APIResponse[any] - -func (a *APIError) Error() string { - var parts []string - - if a.STID != "" { - parts = append(parts, fmt.Sprintf("STID: %s", a.STID)) - } - - if a.CTID != "" { - parts = append(parts, fmt.Sprintf("CTID: %s", a.CTID)) - } - - if a.Status != nil { - parts = append(parts, "status: "+a.Status.String()) - } - - for _, message := range a.Messages { - parts = append(parts, "message: "+message.String()) - } - - if a.Object != nil { - parts = append(parts, "object: "+a.Object.String()) - } - - return strings.Join(parts, ", ") -} - -type DataZoneResponse APIResponse[[]Zone] - type ResponseMessage struct { - Text string `json:"text"` - Code string `json:"code"` - Status string `json:"status"` - Messages []string `json:"messages"` - Objects []GenericObject `json:"objects"` -} - -func (r ResponseMessage) String() string { - var parts []string - - if r.Code != "" { - parts = append(parts, "code: "+r.Code) - } - - if r.Text != "" { - parts = append(parts, "text: "+r.Text) - } - - if r.Status != "" { - parts = append(parts, "status: "+r.Status) - } - - if len(r.Messages) > 0 { - parts = append(parts, "messages: "+strings.Join(r.Messages, ";")) - } - - for _, object := range r.Objects { - parts = append(parts, fmt.Sprintf("object: %s", object)) - } - - return strings.Join(parts, ", ") -} - -type GenericObject struct { - Type string `json:"type"` - Value string `json:"value"` -} - -func (g GenericObject) String() string { - return g.Type + ": " + g.Value + Text string `json:"text"` + Messages []string `json:"messages"` + Objects []string `json:"objects"` + Code string `json:"code"` + Status string `json:"status"` } type ResponseStatus struct { Code string `json:"code"` Text string `json:"text"` - Type string `json:"type"` // SUCCESS, ERROR, NOTIFY, NOTICE, NICCOM_NOTIFY -} - -func (r ResponseStatus) String() string { - return fmt.Sprintf("code: %s, text: %s, type: %s", r.Code, r.Text, r.Type) + Type string `json:"type"` } type ResponseObject struct { - Type string `json:"type"` - Value string `json:"value"` - Summary int32 `json:"summary"` - Data *ResponseObjectData `json:"data"` + Type string `json:"type"` + Value string `json:"value"` + Summary int32 `json:"summary"` + Data string } -func (r ResponseObject) String() string { - var parts []string - - if r.Type != "" { - parts = append(parts, fmt.Sprintf("type: %s", r.Type)) - } - - if r.Value != "" { - parts = append(parts, fmt.Sprintf("value: %s", r.Value)) - } - - if r.Summary != 0 { - parts = append(parts, fmt.Sprintf("summary: %d", r.Summary)) - } - - if r.Data != nil { - parts = append(parts, fmt.Sprintf("data: %s", r.Data.Description)) - } - - return strings.Join(parts, ", ") -} - -type ResponseObjectData struct { - Description string `json:"description"` +type DataZoneResponse struct { + STID string `json:"stid"` + CTID string `json:"ctid"` + Messages []*ResponseMessage `json:"messages"` + Status *ResponseStatus `json:"status"` + Object any `json:"object"` + Data []*Zone `json:"data"` } // ResourceRecord holds a resource record. @@ -143,10 +43,10 @@ type ResourceRecord struct { // Zone is an autodns zone record with all for us relevant fields. // https://help.internetx.com/display/APIXMLEN/Zone+Object type Zone struct { - Name string `json:"origin"` - ResourceRecords []ResourceRecord `json:"resourceRecords"` - Action string `json:"action"` - VirtualNameServer string `json:"virtualNameServer"` + Name string `json:"origin"` + ResourceRecords []*ResourceRecord `json:"resourceRecords"` + Action string `json:"action"` + VirtualNameServer string `json:"virtualNameServer"` } // ZoneStream body of the requests. diff --git a/providers/dns/axelname/axelname.toml b/providers/dns/axelname/axelname.toml index 1e2ad6e72..ee348d5d8 100644 --- a/providers/dns/axelname/axelname.toml +++ b/providers/dns/axelname/axelname.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' AXELNAME_NICKNAME="yyy" \ AXELNAME_TOKEN="xxx" \ -lego --dns axelname -d '*.example.com' -d example.com run +lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 5584ece0b..8150d90d5 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "sync" "time" "github.com/aziontech/azionapi-go-sdk/idns" @@ -55,6 +56,9 @@ func NewDefaultConfig() *Config { type DNSProvider struct { config *Config client *idns.APIClient + + recordIDs map[string]int32 + recordIDsMu sync.Mutex } // NewDNSProvider returns a DNSProvider instance configured for Azion. @@ -94,8 +98,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := idns.NewAPIClient(clientConfig) return &DNSProvider{ - config: config, - client: client, + config: config, + client: client, + recordIDs: make(map[string]int32), }, nil } @@ -156,6 +161,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return errors.New("azion: create zone record error") } + results := resp.GetResults() + + d.recordIDsMu.Lock() + d.recordIDs[token] = results.GetId() + d.recordIDsMu.Unlock() + return nil } @@ -175,6 +186,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("azion: %w", err) } + defer func() { + // Cleans the record ID. + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + }() + existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) if err != nil { return fmt.Errorf("azion: find existing record: %w", err) diff --git a/providers/dns/azion/azion.toml b/providers/dns/azion/azion.toml index 52df20ab5..eacfe74a6 100644 --- a/providers/dns/azion/azion.toml +++ b/providers/dns/azion/azion.toml @@ -6,7 +6,7 @@ URL = "https://www.azion.com/en/products/edge-dns/" Example = ''' AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns azion -d '*.example.com' -d example.com run +lego --email you@example.com --dns azion -d '*.example.com' -d example.com run ''' [Configuration] 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/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index 7c800ce7e..6c1e1ccff 100644 --- a/providers/dns/azuredns/azuredns.toml +++ b/providers/dns/azuredns/azuredns.toml @@ -10,32 +10,32 @@ Example = ''' AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_SECRET= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using client certificate AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_CERTIFICATE_PATH= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Azure CLI az login \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure VM) AZURE_TENANT_ID= \ AZURE_RESOURCE_GROUP= \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure Arc) AZURE_TENANT_ID= \ IMDS_ENDPOINT=http://localhost:40342 \ IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \ -lego --dns azuredns -d '*.example.com' -d example.com run +lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run ''' diff --git a/providers/dns/baiducloud/baiducloud.go b/providers/dns/baiducloud/baiducloud.go index 1dc8d90ed..fc317904a 100644 --- a/providers/dns/baiducloud/baiducloud.go +++ b/providers/dns/baiducloud/baiducloud.go @@ -24,9 +24,6 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) -// 300 is the minimum TTL for free users. -const defaultTTL = 300 - // Config is used to configure the creation of the DNSProvider. type Config struct { AccessKeyID string @@ -40,7 +37,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), } @@ -106,7 +103,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Rr: subDomain, Type: "TXT", Value: info.Value, - Ttl: ptr.Pointer(int32(d.config.TTL)), } err = d.client.CreateRecord(dns01.UnFqdn(authZone), crr, "") @@ -126,7 +122,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err) } - recordID, err := d.findRecordID(dns01.UnFqdn(authZone), info.Value) + lrr := &baidudns.ListRecordRequest{} + + recordResponse, err := d.client.ListRecord(dns01.UnFqdn(authZone), lrr) + if err != nil { + return fmt.Errorf("baiducloud: list record: %w", err) + } + + recordID, err := findRecordID(recordResponse, info) if err != nil { return fmt.Errorf("baiducloud: find record: %w", err) } @@ -139,26 +142,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) findRecordID(zoneName, tokenValue string) (string, error) { - lrr := &baidudns.ListRecordRequest{} - - for { - recordResponse, err := d.client.ListRecord(zoneName, lrr) - if err != nil { - return "", fmt.Errorf("baiducloud: list record: %w", err) +func findRecordID(recordResponse *baidudns.ListRecordResponse, info dns01.ChallengeInfo) (string, error) { + for _, record := range recordResponse.Records { + if record.Type == "TXT" && record.Value == info.Value { + return record.Id, nil } - - for _, record := range recordResponse.Records { - if record.Type == "TXT" && record.Value == tokenValue { - return record.Id, nil - } - } - - if !recordResponse.IsTruncated { - break - } - - lrr.Marker = recordResponse.NextMarker } return "", errors.New("record not found") diff --git a/providers/dns/baiducloud/baiducloud.toml b/providers/dns/baiducloud/baiducloud.toml index 54f1f6312..941d90b2c 100644 --- a/providers/dns/baiducloud/baiducloud.toml +++ b/providers/dns/baiducloud/baiducloud.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ -lego --dns baiducloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run ''' [Configuration] @@ -17,7 +17,7 @@ lego --dns baiducloud -d '*.example.com' -d example.com run [Configuration.Additional] BAIDUCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" BAIDUCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - BAIDUCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + BAIDUCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://cloud.baidu.com/doc/DNS/s/El4s7lssr" diff --git a/providers/dns/beget/beget.toml b/providers/dns/beget/beget.toml index 4ed26d850..3cef2f38c 100644 --- a/providers/dns/beget/beget.toml +++ b/providers/dns/beget/beget.toml @@ -7,7 +7,7 @@ Since = "v4.27.0" Example = ''' BEGET_USERNAME=xxxxxx \ BEGET_PASSWORD=yyyyyy \ -lego --dns beget -d '*.example.com' -d example.com run +lego --email you@example.com --dns beget -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go index 5bbb7a16a..9ff80d698 100644 --- a/providers/dns/binarylane/binarylane.go +++ b/providers/dns/binarylane/binarylane.go @@ -151,10 +151,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("binarylane: delete record: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/binarylane/binarylane.toml b/providers/dns/binarylane/binarylane.toml index 8b382f3b2..5038fc3e6 100644 --- a/providers/dns/binarylane/binarylane.toml +++ b/providers/dns/binarylane/binarylane.toml @@ -6,7 +6,7 @@ Since = "v4.26.0" Example = ''' BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns binarylane -d '*.example.com' -d example.com run +lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run ''' [Configuration] 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/bindman/bindman.toml b/providers/dns/bindman/bindman.toml index 768601588..5c69e18ff 100644 --- a/providers/dns/bindman/bindman.toml +++ b/providers/dns/bindman/bindman.toml @@ -6,7 +6,7 @@ Since = "v2.6.0" Example = ''' BINDMAN_MANAGER_ADDRESS= \ -lego --dns bindman -d '*.example.com' -d example.com run +lego --email you@example.com --dns bindman -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bluecat/bluecat.toml b/providers/dns/bluecat/bluecat.toml index 15df6ed34..a01a5918d 100644 --- a/providers/dns/bluecat/bluecat.toml +++ b/providers/dns/bluecat/bluecat.toml @@ -11,7 +11,7 @@ BLUECAT_USER_NAME=myusername \ BLUECAT_CONFIG_NAME=myconfig \ BLUECAT_SERVER_URL=https://bam.example.com \ BLUECAT_TTL=30 \ -lego --dns bluecat -d '*.example.com' -d example.com run +lego --email you@example.com --dns bluecat -d '*.example.com' -d example.com run ''' [Configuration] 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/bookmyname/bookmyname.toml b/providers/dns/bookmyname/bookmyname.toml index 76fcb85e7..5111c4fbd 100644 --- a/providers/dns/bookmyname/bookmyname.toml +++ b/providers/dns/bookmyname/bookmyname.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' BOOKMYNAME_USERNAME="xxx" \ BOOKMYNAME_PASSWORD="yyy" \ -lego --dns bookmyname -d '*.example.com' -d example.com run +lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/brandit/brandit.toml b/providers/dns/brandit/brandit.toml index 4c43e27a9..32d15c15c 100644 --- a/providers/dns/brandit/brandit.toml +++ b/providers/dns/brandit/brandit.toml @@ -12,7 +12,7 @@ Since = "v4.11.0" Example = ''' BRANDIT_API_KEY=xxxxxxxxxxxxxxxxxxxxx \ BRANDIT_API_USERNAME=yyyyyyyyyyyyyyyyyyyy \ -lego --dns brandit -d '*.example.com' -d example.com run +lego --email you@example.com --dns brandit -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bunny/bunny.toml b/providers/dns/bunny/bunny.toml index 758c4f202..cbe22d6db 100644 --- a/providers/dns/bunny/bunny.toml +++ b/providers/dns/bunny/bunny.toml @@ -6,7 +6,7 @@ Since = "v4.11.0" Example = ''' BUNNY_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --dns bunny -d '*.example.com' -d example.com run +lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/checkdomain/checkdomain.toml b/providers/dns/checkdomain/checkdomain.toml index 0b93058ba..c3ac14e36 100644 --- a/providers/dns/checkdomain/checkdomain.toml +++ b/providers/dns/checkdomain/checkdomain.toml @@ -6,7 +6,7 @@ Since = "v3.3.0" Example = ''' CHECKDOMAIN_TOKEN=yoursecrettoken \ -lego --dns checkdomain -d '*.example.com' -d example.com run +lego --email you@example.com --dns checkdomain -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/checkdomain/internal/client.go b/providers/dns/checkdomain/internal/client.go index 68d090755..d626275ab 100644 --- a/providers/dns/checkdomain/internal/client.go +++ b/providers/dns/checkdomain/internal/client.go @@ -36,11 +36,11 @@ const maxInt = int((^uint(0)) >> 1) // Client the Autodns API client. type Client struct { - BaseURL *url.URL - httpClient *http.Client - domainIDMapping map[string]int domainIDMu sync.Mutex + + BaseURL *url.URL + httpClient *http.Client } // NewClient creates a new Client. diff --git a/providers/dns/civo/civo.toml b/providers/dns/civo/civo.toml index b525712c8..9458f01c3 100644 --- a/providers/dns/civo/civo.toml +++ b/providers/dns/civo/civo.toml @@ -6,7 +6,7 @@ Since = "v4.9.0" Example = ''' CIVO_TOKEN=xxxxxx \ -lego --dns civo -d '*.example.com' -d example.com run +lego --email you@example.com --dns civo -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/clouddns/clouddns.toml b/providers/dns/clouddns/clouddns.toml index 6f516e834..154d4da67 100644 --- a/providers/dns/clouddns/clouddns.toml +++ b/providers/dns/clouddns/clouddns.toml @@ -8,7 +8,7 @@ Example = ''' CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \ CLOUDDNS_EMAIL=you@example.com \ CLOUDDNS_PASSWORD=b9841238feb177a84330f \ -lego --dns clouddns -d '*.example.com' -d example.com run +lego --email you@example.com --dns clouddns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudflare/cloudflare.toml b/providers/dns/cloudflare/cloudflare.toml index c46130fe6..caf132bb4 100644 --- a/providers/dns/cloudflare/cloudflare.toml +++ b/providers/dns/cloudflare/cloudflare.toml @@ -7,12 +7,12 @@ Since = "v0.3.0" Example = ''' CLOUDFLARE_EMAIL=you@example.com \ CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --dns cloudflare -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run # or CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns cloudflare -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run ''' Additional = ''' 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/cloudns/cloudns.toml b/providers/dns/cloudns/cloudns.toml index ad52ef5b1..dd191f06a 100644 --- a/providers/dns/cloudns/cloudns.toml +++ b/providers/dns/cloudns/cloudns.toml @@ -7,7 +7,7 @@ Since = "v2.3.0" Example = ''' CLOUDNS_AUTH_ID=xxxx \ CLOUDNS_AUTH_PASSWORD=yyyy \ -lego --dns cloudns -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudru/cloudru.go b/providers/dns/cloudru/cloudru.go index dd597952a..287c12045 100644 --- a/providers/dns/cloudru/cloudru.go +++ b/providers/dns/cloudru/cloudru.go @@ -61,9 +61,8 @@ func NewDefaultConfig() *Config { } type DNSProvider struct { - config *Config - client *internal.Client - + config *Config + client *internal.Client records map[string]*internal.Record recordsMu sync.Mutex } diff --git a/providers/dns/cloudru/cloudru.toml b/providers/dns/cloudru/cloudru.toml index b74098a72..a6563a3df 100644 --- a/providers/dns/cloudru/cloudru.toml +++ b/providers/dns/cloudru/cloudru.toml @@ -8,7 +8,7 @@ Example = ''' CLOUDRU_SERVICE_INSTANCE_ID=ppp \ CLOUDRU_KEY_ID=xxx \ CLOUDRU_SECRET=yyy \ -lego --dns cloudru -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudru -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudxns/cloudxns.toml b/providers/dns/cloudxns/cloudxns.toml index 32eae8beb..e87a741df 100644 --- a/providers/dns/cloudxns/cloudxns.toml +++ b/providers/dns/cloudxns/cloudxns.toml @@ -9,7 +9,7 @@ Since = "v0.5.0" Example = ''' CLOUDXNS_API_KEY=xxxx \ CLOUDXNS_SECRET_KEY=yyyy \ -lego --dns cloudxns -d '*.example.com' -d example.com run +lego --email you@example.com --dns cloudxns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/com35/com35.go b/providers/dns/com35/com35.go deleted file mode 100644 index 4a9de3a18..000000000 --- a/providers/dns/com35/com35.go +++ /dev/null @@ -1,104 +0,0 @@ -// Package com35 implements a DNS provider for solving the DNS-01 challenge using 35.com/三五互联. -package com35 - -import ( - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/westcn" -) - -// Environment variables names. -const ( - envNamespace = "COM35_" - - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultBaseURL = "https://api.35.cn/api/v2" - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config = westcn.Config - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 60), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - prv challenge.ProviderTimeout -} - -// NewDNSProvider returns a DNSProvider instance configured for 35.com/三五互联. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsername, EnvPassword) - if err != nil { - return nil, fmt.Errorf("35com: %w", err) - } - - config := NewDefaultConfig() - config.Username = values[EnvUsername] - config.Password = values[EnvPassword] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for 35.com/三五互联. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("35com: the configuration of the DNS provider is nil") - } - - provider, err := westcn.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("35com: %w", err) - } - - return &DNSProvider{prv: provider}, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("35com: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("35com: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/com35/com35.toml b/providers/dns/com35/com35.toml deleted file mode 100644 index 386ee0043..000000000 --- a/providers/dns/com35/com35.toml +++ /dev/null @@ -1,24 +0,0 @@ -Name = "35.com/三五互联" -Description = '''''' -URL = "https://www.35.cn/" -Code = "com35" -Since = "v4.31.0" - -Example = ''' -COM35_USERNAME="xxx" \ -COM35_PASSWORD="yyy" \ -lego --dns com35 -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - COM35_USERNAME = "Username" - COM35_PASSWORD = "API password" - [Configuration.Additional] - COM35_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" - COM35_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" - COM35_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" - COM35_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://api.35.cn/CustomerCenter/doc/domain_v2.html" diff --git a/providers/dns/com35/com35_test.go b/providers/dns/com35/com35_test.go deleted file mode 100644 index 78fd8f829..000000000 --- a/providers/dns/com35/com35_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package com35 - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvUsername, EnvPassword).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "secret", - }, - }, - { - desc: "missing username", - envVars: map[string]string{ - EnvUsername: "", - EnvPassword: "secret", - }, - expected: "35com: some credentials information are missing: COM35_USERNAME", - }, - { - desc: "missing password", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "", - }, - expected: "35com: some credentials information are missing: COM35_PASSWORD", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "35com: some credentials information are missing: COM35_USERNAME,COM35_PASSWORD", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - username string - password string - expected string - }{ - { - desc: "success", - username: "user", - password: "secret", - }, - { - desc: "missing username", - password: "secret", - expected: "35com: credentials missing", - }, - { - desc: "missing password", - username: "user", - expected: "35com: credentials missing", - }, - { - desc: "missing credentials", - expected: "35com: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/conoha/conoha.toml b/providers/dns/conoha/conoha.toml index be90acb0d..8bd83247e 100644 --- a/providers/dns/conoha/conoha.toml +++ b/providers/dns/conoha/conoha.toml @@ -8,7 +8,7 @@ Example = ''' CONOHA_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHA_API_USERNAME=xxxx \ CONOHA_API_PASSWORD=yyyy \ -lego --dns conoha -d '*.example.com' -d example.com run +lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/conohav3/conohav3.toml b/providers/dns/conohav3/conohav3.toml index e2c80259d..7608e6742 100644 --- a/providers/dns/conohav3/conohav3.toml +++ b/providers/dns/conohav3/conohav3.toml @@ -8,7 +8,7 @@ Example = ''' CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHAV3_API_USER_ID=xxxx \ CONOHAV3_API_PASSWORD=yyyy \ -lego --dns conohav3 -d '*.example.com' -d example.com run +lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/constellix/constellix.toml b/providers/dns/constellix/constellix.toml index 171a0de99..c4ae0a194 100644 --- a/providers/dns/constellix/constellix.toml +++ b/providers/dns/constellix/constellix.toml @@ -7,7 +7,7 @@ Since = "v3.4.0" Example = ''' CONSTELLIX_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ CONSTELLIX_SECRET_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --dns constellix -d '*.example.com' -d example.com run +lego --email you@example.com --dns constellix -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/corenetworks/corenetworks.toml b/providers/dns/corenetworks/corenetworks.toml index 09840bb1b..8546d8723 100644 --- a/providers/dns/corenetworks/corenetworks.toml +++ b/providers/dns/corenetworks/corenetworks.toml @@ -7,7 +7,7 @@ Since = "v4.20.0" Example = ''' CORENETWORKS_LOGIN="xxxx" \ CORENETWORKS_PASSWORD="yyyy" \ -lego --dns corenetworks -d '*.example.com' -d example.com run +lego --email you@example.com --dns corenetworks -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cpanel/cpanel.toml b/providers/dns/cpanel/cpanel.toml index b64adf0cf..faed2abe2 100644 --- a/providers/dns/cpanel/cpanel.toml +++ b/providers/dns/cpanel/cpanel.toml @@ -10,7 +10,7 @@ Example = ''' CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2083" \ -lego --dns cpanel -d '*.example.com' -d example.com run +lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ## WHM @@ -18,7 +18,7 @@ CPANEL_MODE=whm \ CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2087" \ -lego --dns cpanel -d '*.example.com' -d example.com run +lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ''' [Configuration] 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/derak/derak.toml b/providers/dns/derak/derak.toml index 72f49883a..45d7e1fcf 100644 --- a/providers/dns/derak/derak.toml +++ b/providers/dns/derak/derak.toml @@ -6,7 +6,7 @@ Since = "v4.12.0" Example = ''' DERAK_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns derak -d '*.example.com' -d example.com run +lego --email you@example.com --dns derak -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/desec/desec.toml b/providers/dns/desec/desec.toml index f7e66ae07..a79b38cd3 100644 --- a/providers/dns/desec/desec.toml +++ b/providers/dns/desec/desec.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' DESEC_TOKEN=x-xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns desec -d '*.example.com' -d example.com run +lego --email you@example.com --dns desec -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/designate/designate.go b/providers/dns/designate/designate.go index 41bf251f6..47c8ad8f1 100644 --- a/providers/dns/designate/designate.go +++ b/providers/dns/designate/designate.go @@ -68,9 +68,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *gophercloud.ServiceClient - + config *Config + client *gophercloud.ServiceClient dnsEntriesMu sync.Mutex } diff --git a/providers/dns/designate/designate.toml b/providers/dns/designate/designate.toml index a36034f64..3ea6260a6 100644 --- a/providers/dns/designate/designate.toml +++ b/providers/dns/designate/designate.toml @@ -7,7 +7,7 @@ Since = "v2.2.0" Example = ''' # With a `clouds.yaml` OS_CLOUD=my_openstack \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run # or @@ -16,7 +16,7 @@ OS_REGION_NAME=RegionOne \ OS_PROJECT_ID=23d4522a987d4ab529f722a007c27846 OS_USERNAME=myuser \ OS_PASSWORD=passw0rd \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run # or @@ -25,7 +25,7 @@ OS_REGION_NAME=RegionOne \ OS_AUTH_TYPE=v3applicationcredential \ OS_APPLICATION_CREDENTIAL_ID=imn74uq0or7dyzz20dwo1ytls4me8dry \ OS_APPLICATION_CREDENTIAL_SECRET=68FuSPSdQqkFQYH5X1OoriEIJOwyLtQ8QSqXZOc9XxFK1A9tzZT6He2PfPw0OMja \ -lego --dns designate -d '*.example.com' -d example.com run +lego --email you@example.com --dns designate -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/digitalocean/digitalocean.toml b/providers/dns/digitalocean/digitalocean.toml index 8f9107c26..b30d986f2 100644 --- a/providers/dns/digitalocean/digitalocean.toml +++ b/providers/dns/digitalocean/digitalocean.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' DO_AUTH_TOKEN=xxxxxx \ -lego --dns digitalocean -d '*.example.com' -d example.com run +lego --email you@example.com --dns digitalocean -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/directadmin/directadmin.toml b/providers/dns/directadmin/directadmin.toml index 294eaca1c..bd1c9316a 100644 --- a/providers/dns/directadmin/directadmin.toml +++ b/providers/dns/directadmin/directadmin.toml @@ -8,7 +8,7 @@ Example = ''' DIRECTADMIN_API_URL="http://example.com:2222" \ DIRECTADMIN_USERNAME=xxxx \ DIRECTADMIN_PASSWORD=yyy \ -lego --dns directadmin -d '*.example.com' -d example.com run +lego --email you@example.com --dns directadmin -d '*.example.com' -d example.com run ''' [Configuration] 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/dnshomede/dnshomede.toml b/providers/dns/dnshomede/dnshomede.toml index 9c3b65277..bc52bb6dd 100644 --- a/providers/dns/dnshomede/dnshomede.toml +++ b/providers/dns/dnshomede/dnshomede.toml @@ -6,10 +6,10 @@ Since = "v4.10.0" Example = ''' DNSHOMEDE_CREDENTIALS=example.org:password \ -lego --dns dnshomede -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnshomede -d '*.example.com' -d example.com run DNSHOMEDE_CREDENTIALS=my.example.org:password1,demo.example.org:password2 \ -lego --dns dnshomede -d my.example.org -d demo.example.org +lego --email you@example.com --dns dnshomede -d my.example.org -d demo.example.org ''' [Configuration] diff --git a/providers/dns/dnsimple/dnsimple.toml b/providers/dns/dnsimple/dnsimple.toml index 158fb7011..dcf999136 100644 --- a/providers/dns/dnsimple/dnsimple.toml +++ b/providers/dns/dnsimple/dnsimple.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' DNSIMPLE_OAUTH_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns dnsimple -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnsimple -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml index d71ab5303..11a5f85ac 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' DNSMADEEASY_API_KEY=xxxxxx \ DNSMADEEASY_API_SECRET=yyyyy \ -lego --dns dnsmadeeasy -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnsmadeeasy -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index 52a873c7b..c9376b956 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -165,7 +165,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) { } if hostedZone.ID == "" || hostedZone.ID == "0" { - return "", "", fmt.Errorf("zone %s not found for domain %s", authZone, domain) + return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) } return hostedZone.ID.String(), hostedZone.Name, nil diff --git a/providers/dns/dnspod/dnspod.toml b/providers/dns/dnspod/dnspod.toml index 162685d76..a0bf50e31 100644 --- a/providers/dns/dnspod/dnspod.toml +++ b/providers/dns/dnspod/dnspod.toml @@ -8,7 +8,7 @@ Since = "v0.4.0" Example = ''' DNSPOD_API_KEY=xxxxxx \ -lego --dns dnspod -d '*.example.com' -d example.com run +lego --email you@example.com --dns dnspod -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dode/dode.toml b/providers/dns/dode/dode.toml index eb629bb3e..a96e9ee43 100644 --- a/providers/dns/dode/dode.toml +++ b/providers/dns/dode/dode.toml @@ -6,7 +6,7 @@ Since = "v2.4.0" Example = ''' DODE_TOKEN=xxxxxx \ -lego --dns dode -d '*.example.com' -d example.com run +lego --email you@example.com --dns dode -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/domeneshop/domeneshop.toml b/providers/dns/domeneshop/domeneshop.toml index b74af598e..a8d2a1064 100644 --- a/providers/dns/domeneshop/domeneshop.toml +++ b/providers/dns/domeneshop/domeneshop.toml @@ -8,7 +8,7 @@ Since = "v4.3.0" Example = ''' DOMENESHOP_API_TOKEN= \ DOMENESHOP_API_SECRET= \ -lego --dns domeneshop -d '*.example.com' -d example.com run +lego --email example@example.com --dns domeneshop -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/dreamhost/dreamhost.toml b/providers/dns/dreamhost/dreamhost.toml index c3a9db360..4345e9ece 100644 --- a/providers/dns/dreamhost/dreamhost.toml +++ b/providers/dns/dreamhost/dreamhost.toml @@ -6,7 +6,7 @@ Since = "v1.1.0" Example = ''' DREAMHOST_API_KEY="YOURAPIKEY" \ -lego --dns dreamhost -d '*.example.com' -d example.com run +lego --email you@example.com --dns dreamhost -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/duckdns/duckdns.toml b/providers/dns/duckdns/duckdns.toml index 6866da57c..9c0b3a6be 100644 --- a/providers/dns/duckdns/duckdns.toml +++ b/providers/dns/duckdns/duckdns.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' DUCKDNS_TOKEN=xxxxxx \ -lego --dns duckdns -d '*.example.com' -d example.com run +lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dyn/dyn.toml b/providers/dns/dyn/dyn.toml index c4b3563e0..4b0d3e652 100644 --- a/providers/dns/dyn/dyn.toml +++ b/providers/dns/dyn/dyn.toml @@ -8,7 +8,7 @@ Example = ''' DYN_CUSTOMER_NAME=xxxxxx \ DYN_USER_NAME=yyyyy \ DYN_PASSWORD=zzzz \ -lego --dns dyn -d '*.example.com' -d example.com run +lego --email you@example.com --dns dyn -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dyndnsfree/dyndnsfree.toml b/providers/dns/dyndnsfree/dyndnsfree.toml index e64bb0080..dd354fb33 100644 --- a/providers/dns/dyndnsfree/dyndnsfree.toml +++ b/providers/dns/dyndnsfree/dyndnsfree.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' DYNDNSFREE_USERNAME="xxx" \ DYNDNSFREE_PASSWORD="yyy" \ -lego --dns dyndnsfree -d '*.example.com' -d example.com run +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dynu/dynu.toml b/providers/dns/dynu/dynu.toml index ae2367087..ba59034dd 100644 --- a/providers/dns/dynu/dynu.toml +++ b/providers/dns/dynu/dynu.toml @@ -6,7 +6,7 @@ Since = "v3.5.0" Example = ''' DYNU_API_KEY=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --dns dynu -d '*.example.com' -d example.com run +lego --email you@example.com --dns dynu -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/easydns/easydns.go b/providers/dns/easydns/easydns.go index 205063e7b..ae0a0c3b8 100644 --- a/providers/dns/easydns/easydns.go +++ b/providers/dns/easydns/easydns.go @@ -190,14 +190,16 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), recordID) + + d.recordIDsMu.Lock() + defer delete(d.recordIDs, key) + + d.recordIDsMu.Unlock() + if err != nil { return fmt.Errorf("easydns: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, key) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/easydns/easydns.toml b/providers/dns/easydns/easydns.toml index 307c86a09..71521bbd6 100644 --- a/providers/dns/easydns/easydns.toml +++ b/providers/dns/easydns/easydns.toml @@ -7,7 +7,7 @@ Since = "v2.6.0" Example = ''' EASYDNS_TOKEN=xxx \ EASYDNS_KEY=yyy \ -lego --dns easydns -d '*.example.com' -d example.com run +lego --email you@example.com --dns easydns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/edgecenter/edgecenter.go b/providers/dns/edgecenter/edgecenter.go deleted file mode 100644 index cfc75b521..000000000 --- a/providers/dns/edgecenter/edgecenter.go +++ /dev/null @@ -1,103 +0,0 @@ -// Package edgecenter implements a DNS provider for solving the DNS-01 challenge using EdgeCenter. -package edgecenter - -import ( - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/gcore" -) - -// Environment variables names. -const ( - envNamespace = "EDGECENTER_" - - EnvPermanentAPIToken = envNamespace + "PERMANENT_API_TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultBaseURL = "https://api.edgecenter.ru/dns" - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config for DNSProvider. -type Config = gcore.Config - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, gcore.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, gcore.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), - }, - } -} - -// DNSProvider an implementation of challenge.Provider contract. -type DNSProvider struct { - prv challenge.ProviderTimeout -} - -// NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvPermanentAPIToken) - if err != nil { - return nil, fmt.Errorf("edgecenter: %w", err) - } - - config := NewDefaultConfig() - config.APIToken = values[EnvPermanentAPIToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for G-Core DNS API. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("edgecenter: the configuration of the DNS provider is nil") - } - - provider, err := gcore.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("edgecenter: %w", err) - } - - return &DNSProvider{prv: provider}, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("edgecenter: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("edgecenter: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/edgecenter/edgecenter.toml b/providers/dns/edgecenter/edgecenter.toml deleted file mode 100644 index 1c9e9b2a9..000000000 --- a/providers/dns/edgecenter/edgecenter.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "EdgeCenter" -Description = '''''' -URL = "https://edgecenter.ru/dns" -Code = "edgecenter" -Since = "v4.29.0" - -Example = ''' -EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ -lego --dns edgecenter -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - EDGECENTER_PERMANENT_API_TOKEN = "Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/)" - [Configuration.Additional] - EDGECENTER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" - EDGECENTER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 360)" - EDGECENTER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - EDGECENTER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" - -[Links] - API = "https://apidocs.edgecenter.ru/dns" diff --git a/providers/dns/edgecenter/edgecenter_test.go b/providers/dns/edgecenter/edgecenter_test.go deleted file mode 100644 index e3ec43981..000000000 --- a/providers/dns/edgecenter/edgecenter_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package edgecenter - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -var envTest = tester.NewEnvTest(EnvPermanentAPIToken).WithDomain(envNamespace + "DOMAIN") - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvPermanentAPIToken: "A", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvPermanentAPIToken: "", - }, - expected: "edgecenter: some credentials information are missing: EDGECENTER_PERMANENT_API_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "A", - }, - { - desc: "missing credentials", - expected: "edgecenter: incomplete credentials provided", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/edgedns/edgedns.toml b/providers/dns/edgedns/edgedns.toml index 7c7c5b3aa..d40d5cc03 100644 --- a/providers/dns/edgedns/edgedns.toml +++ b/providers/dns/edgedns/edgedns.toml @@ -12,7 +12,7 @@ AKAMAI_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG= \ AKAMAI_CLIENT_TOKEN=akab-mnbvcxzlkjhgfdsapoiuytrewq1234567 \ AKAMAI_HOST=akab-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.luna.akamaiapis.net \ AKAMAI_ACCESS_TOKEN=akab-1234567890qwerty-asdfghjklzxcvtnu \ -lego --dns edgedns -d '*.example.com' -d example.com run +lego --email you@example.com --dns edgedns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go index 6931c6715..3402122bb 100644 --- a/providers/dns/edgeone/edgeone.go +++ b/providers/dns/edgeone/edgeone.go @@ -26,7 +26,6 @@ const ( EnvSecretKey = envNamespace + "SECRET_KEY" EnvRegion = envNamespace + "REGION" EnvSessionToken = envNamespace + "SESSION_TOKEN" - EnvZonesMapping = envNamespace + "ZONES_MAPPING" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -41,8 +40,6 @@ type Config struct { Region string SessionToken string - ZonesMapping map[string]string - PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -81,14 +78,6 @@ func NewDNSProvider() (*DNSProvider, error) { config.Region = env.GetOrDefaultString(EnvRegion, "") config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "") - mapping := env.GetOrDefaultString(EnvZonesMapping, "") - if mapping != "" { - config.ZonesMapping, err = env.ParsePairs(mapping) - if err != nil { - return nil, fmt.Errorf("edgeone: zones mapping: %w", err) - } - } - return NewDNSProviderConfig(config) } @@ -119,9 +108,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } return &DNSProvider{ - config: config, - client: client, - recordIDs: map[string]*string{}, + config: config, + client: client, + recordIDs: map[string]*string{}, + recordIDsMu: sync.Mutex{}, }, nil } @@ -131,7 +121,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() - zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -143,7 +133,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { request := teo.NewCreateDnsRecordRequest() request.Name = ptr.Pointer(punnyCoded) - request.ZoneId = zoneID + request.ZoneId = zone.ZoneId request.Type = ptr.Pointer("TXT") request.Content = ptr.Pointer(info.Value) request.TTL = ptr.Pointer(int64(d.config.TTL)) @@ -166,7 +156,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() - zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -181,7 +171,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } request := teo.NewDeleteDnsRecordsRequest() - request.ZoneId = zoneID + request.ZoneId = zone.ZoneId request.RecordIds = []*string{recordID} _, err = teo.DeleteDnsRecordsWithContext(ctx, d.client, request) @@ -189,10 +179,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("edgeone: delete record failed: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml index 05b8bc516..120756da6 100644 --- a/providers/dns/edgeone/edgeone.toml +++ b/providers/dns/edgeone/edgeone.toml @@ -7,7 +7,7 @@ Since = "v4.26.0" Example = ''' EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ EDGEONE_SECRET_KEY=your-secret-key \ -lego --dns edgeone -d '*.example.com' -d example.com run +lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run ''' [Configuration] @@ -17,7 +17,6 @@ lego --dns edgeone -d '*.example.com' -d example.com run [Configuration.Additional] EDGEONE_SESSION_TOKEN = "Access Key token" EDGEONE_REGION = "Region" - EDGEONE_ZONES_MAPPING = "Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')" EDGEONE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" EDGEONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" EDGEONE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go index 7bd4f6f6d..1c92118dc 100644 --- a/providers/dns/edgeone/edgeone_test.go +++ b/providers/dns/edgeone/edgeone_test.go @@ -9,11 +9,8 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest( - EnvSecretID, - EnvSecretKey, - EnvZonesMapping, -).WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvSecretID, EnvSecretKey). + WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -28,14 +25,6 @@ func TestNewDNSProvider(t *testing.T) { EnvSecretKey: "456", }, }, - { - desc: "success with zones mapping", - envVars: map[string]string{ - EnvSecretID: "123", - EnvSecretKey: "456", - EnvZonesMapping: "example.org:id1,example.com:id2", - }, - }, { desc: "missing credentials", envVars: map[string]string{ @@ -60,15 +49,6 @@ func TestNewDNSProvider(t *testing.T) { }, expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_KEY", }, - { - desc: "invalid mapping", - envVars: map[string]string{ - EnvSecretID: "123", - EnvSecretKey: "456", - EnvZonesMapping: "example.org:id1,example.com", - }, - expected: "edgeone: zones mapping: incorrect pair: example.com", - }, } for _, test := range testCases { diff --git a/providers/dns/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go index 53fae9427..c3e9d965b 100644 --- a/providers/dns/edgeone/wrapper.go +++ b/providers/dns/edgeone/wrapper.go @@ -9,22 +9,10 @@ import ( teo "github.com/go-acme/tencentedgdeone/v20220901" ) -func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*string, error) { - authZone, err := dns01.FindZoneByFqdn(domain) - if err != nil { - return nil, fmt.Errorf("could not find zone: %w", err) - } - - if d.config.ZonesMapping != nil { - zoneID, ok := d.config.ZonesMapping[authZone] - if ok { - return ptr.Pointer(zoneID), nil - } - } - +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zone, error) { request := teo.NewDescribeZonesRequest() - var zones []*teo.Zone + var domains []*teo.Zone for { response, err := teo.DescribeZonesWithContext(ctx, d.client, request) @@ -32,18 +20,23 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*stri return nil, fmt.Errorf("API call failed: %w", err) } - zones = append(zones, response.Response.Zones...) + domains = append(domains, response.Response.Zones...) - if int64(len(zones)) >= ptr.Deref(response.Response.TotalCount) { + if int64(len(domains)) >= ptr.Deref(response.Response.TotalCount) { break } - request.Offset = ptr.Pointer(int64(len(zones))) + request.Offset = ptr.Pointer(int64(len(domains))) + } + + authZone, err := dns01.FindZoneByFqdn(domain) + if err != nil { + return nil, fmt.Errorf("could not find zone: %w", err) } var hostedZone *teo.Zone - for _, zone := range zones { + for _, zone := range domains { unfqdn := dns01.UnFqdn(authZone) if ptr.Deref(zone.ZoneName) == unfqdn { hostedZone = zone @@ -51,8 +44,8 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*stri } if hostedZone == nil { - return nil, fmt.Errorf("zone %s not found for domain %s", authZone, domain) + return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) } - return hostedZone.ZoneId, nil + return hostedZone, nil } diff --git a/providers/dns/efficientip/efficientip.toml b/providers/dns/efficientip/efficientip.toml index 6e1874319..565c9575b 100644 --- a/providers/dns/efficientip/efficientip.toml +++ b/providers/dns/efficientip/efficientip.toml @@ -9,7 +9,7 @@ EFFICIENTIP_USERNAME="user" \ EFFICIENTIP_PASSWORD="secret" \ EFFICIENTIP_HOSTNAME="ipam.example.org" \ EFFICIENTIP_DNS_NAME="dns.smart" \ -lego --dns efficientip -d '*.example.com' -d example.com run +lego --email you@example.com --dns efficientip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/epik/epik.toml b/providers/dns/epik/epik.toml index faf453581..7b4688609 100644 --- a/providers/dns/epik/epik.toml +++ b/providers/dns/epik/epik.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns epik -d '*.example.com' -d example.com run +lego --email you@example.com --dns epik -d '*.example.com' -d example.com run ''' [Configuration] 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/exec/exec.toml b/providers/dns/exec/exec.toml index 2f9c77c67..4c8d70b1c 100644 --- a/providers/dns/exec/exec.toml +++ b/providers/dns/exec/exec.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' EXEC_PATH=/the/path/to/myscript.sh \ -lego --dns exec -d '*.example.com' -d example.com run +lego --email you@example.com --dns exec -d '*.example.com' -d example.com run ''' Additional = ''' @@ -39,7 +39,7 @@ For example, requesting a certificate for the domain 'my.example.org' can be ach ```bash EXEC_PATH=./update-dns.sh \ -lego --dns exec --d my.example.org run +lego --email you@example.com --dns exec --d my.example.org run ``` It will then call the program './update-dns.sh' with like this: @@ -59,7 +59,7 @@ If you want to use the raw domain, token, and keyAuth values with your program, ```bash EXEC_MODE=RAW \ EXEC_PATH=./update-dns.sh \ -lego --dns exec -d my.example.org run +lego --email you@example.com --dns exec -d my.example.org run ``` It will then call the program `./update-dns.sh` like this: diff --git a/providers/dns/exoscale/exoscale.toml b/providers/dns/exoscale/exoscale.toml index bcc912b07..82c005d26 100644 --- a/providers/dns/exoscale/exoscale.toml +++ b/providers/dns/exoscale/exoscale.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' EXOSCALE_API_KEY=abcdefghijklmnopqrstuvwx \ EXOSCALE_API_SECRET=xxxxxxx \ -lego --dns exoscale -d '*.example.com' -d example.com run +lego --email you@example.com --dns exoscale -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index 76a6e0262..6f8a8c493 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -22,7 +22,6 @@ const ( EnvToken = envNamespace + "API_TOKEN" EnvTenantName = envNamespace + "TENANT_NAME" - EnvServer = envNamespace + "SERVER" EnvGroupName = envNamespace + "GROUP_NAME" EnvTTL = envNamespace + "TTL" @@ -35,7 +34,6 @@ const ( type Config struct { APIToken string TenantName string - Server string GroupName string PropagationTimeout time.Duration @@ -73,7 +71,6 @@ func NewDNSProvider() (*DNSProvider, error) { config.APIToken = values[EnvToken] config.TenantName = values[EnvTenantName] config.GroupName = values[EnvGroupName] - config.Server = env.GetOrFile(EnvServer) return NewDNSProviderConfig(config) } @@ -88,7 +85,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("f5xc: missing group name") } - client, err := internal.NewClient(config.APIToken, config.TenantName, config.Server) + client, err := internal.NewClient(config.APIToken, config.TenantName) if err != nil { return nil, fmt.Errorf("f5xc: %w", err) } diff --git a/providers/dns/f5xc/f5xc.toml b/providers/dns/f5xc/f5xc.toml index 6be604ddd..7a4cab419 100644 --- a/providers/dns/f5xc/f5xc.toml +++ b/providers/dns/f5xc/f5xc.toml @@ -8,7 +8,7 @@ Example = ''' F5XC_API_TOKEN="xxx" \ F5XC_TENANT_NAME="yyy" \ F5XC_GROUP_NAME="zzz" \ -lego --dns f5xc -d '*.example.com' -d example.com run +lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run ''' [Configuration] @@ -17,7 +17,6 @@ lego --dns f5xc -d '*.example.com' -d example.com run F5XC_TENANT_NAME = "XC Tenant shortname" F5XC_GROUP_NAME = "Group name" [Configuration.Additional] - F5XC_SERVER = "Server domain (Default: console.ves.volterra.io)" F5XC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" F5XC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" F5XC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/f5xc/f5xc_test.go b/providers/dns/f5xc/f5xc_test.go index 890a4cf09..98f7484e7 100644 --- a/providers/dns/f5xc/f5xc_test.go +++ b/providers/dns/f5xc/f5xc_test.go @@ -9,12 +9,7 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest( - EnvToken, - EnvTenantName, - EnvServer, - EnvGroupName, -).WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvToken, EnvTenantName, EnvGroupName).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { diff --git a/providers/dns/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go index 7beab0d03..b0b5d0468 100644 --- a/providers/dns/f5xc/internal/client.go +++ b/providers/dns/f5xc/internal/client.go @@ -14,7 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultServer = "console.ves.volterra.io" +const defaultHost = "console.ves.volterra.io" const authorizationHeader = "Authorization" @@ -27,14 +27,18 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiToken, tenantName, server string) (*Client, error) { +func NewClient(apiToken, tenantName string) (*Client, error) { if apiToken == "" { return nil, errors.New("credentials missing") } - baseURL, err := createBaseURL(tenantName, server) + if tenantName == "" { + return nil, errors.New("missing tenant name") + } + + baseURL, err := url.Parse(fmt.Sprintf("https://%s.%s", tenantName, defaultHost)) if err != nil { - return nil, err + return nil, fmt.Errorf("parse base URL: %w", err) } return &Client{ @@ -205,20 +209,3 @@ func parseError(req *http.Request, resp *http.Response) error { return &apiErr } - -func createBaseURL(tenant, server string) (*url.URL, error) { - if tenant == "" { - return nil, errors.New("missing tenant name") - } - - if server == "" { - server = defaultServer - } - - baseURL, err := url.Parse(fmt.Sprintf("https://%s.%s", tenant, server)) - if err != nil { - return nil, fmt.Errorf("parse base URL: %w", err) - } - - return baseURL, nil -} diff --git a/providers/dns/f5xc/internal/client_test.go b/providers/dns/f5xc/internal/client_test.go index bb188ef3f..0357abb16 100644 --- a/providers/dns/f5xc/internal/client_test.go +++ b/providers/dns/f5xc/internal/client_test.go @@ -14,7 +14,7 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret", "shortname", "") + client, err := NewClient("secret", "shortname") if err != nil { return nil, err } @@ -28,7 +28,7 @@ func mockBuilder() *servermock.Builder[*Client] { WithAuthorization("APIToken secret")) } -func TestClient_CreateRRSet(t *testing.T) { +func TestClient_Create(t *testing.T) { client := mockBuilder(). Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", servermock.ResponseFromFixture("create.json"), @@ -62,7 +62,7 @@ func TestClient_CreateRRSet(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_CreateRRSet_error(t *testing.T) { +func TestClient_Create_error(t *testing.T) { client := mockBuilder(). Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -81,7 +81,7 @@ func TestClient_CreateRRSet_error(t *testing.T) { require.Error(t, err) } -func TestClient_GetRRSet(t *testing.T) { +func TestClient_Get(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json")). @@ -108,7 +108,7 @@ func TestClient_GetRRSet(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_GetRRSet_not_found(t *testing.T) { +func TestClient_Get_not_found(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("error_404.json").WithStatusCode(http.StatusNotFound)). @@ -120,7 +120,7 @@ func TestClient_GetRRSet_not_found(t *testing.T) { assert.Nil(t, result) } -func TestClient_GetRRSet_error(t *testing.T) { +func TestClient_Get_error(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -130,7 +130,7 @@ func TestClient_GetRRSet_error(t *testing.T) { require.Error(t, err) } -func TestClient_DeleteRRSet(t *testing.T) { +func TestClient_Delete(t *testing.T) { client := mockBuilder(). Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json")). @@ -157,7 +157,7 @@ func TestClient_DeleteRRSet(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_DeleteRRSet_error(t *testing.T) { +func TestClient_Delete_error(t *testing.T) { client := mockBuilder(). Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -167,7 +167,7 @@ func TestClient_DeleteRRSet_error(t *testing.T) { require.Error(t, err) } -func TestClient_ReplaceRRSet(t *testing.T) { +func TestClient_Replace(t *testing.T) { client := mockBuilder(). Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json"), @@ -204,7 +204,7 @@ func TestClient_ReplaceRRSet(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_ReplaceRRSet_error(t *testing.T) { +func TestClient_Replace_error(t *testing.T) { client := mockBuilder(). Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -222,70 +222,3 @@ func TestClient_ReplaceRRSet_error(t *testing.T) { _, err := client.ReplaceRRSet(t.Context(), "example.com", "groupA", "www", "TXT", rrSet) require.Error(t, err) } - -func Test_createBaseURL(t *testing.T) { - testCases := []struct { - desc string - tenant string - server string - expected string - }{ - { - desc: "only tenant", - tenant: "foo", - expected: "https://foo.console.ves.volterra.io", - }, - { - desc: "custom server", - tenant: "foo", - server: "example.com", - expected: "https://foo.example.com", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - baseURL, err := createBaseURL(test.tenant, test.server) - require.NoError(t, err) - - assert.Equal(t, test.expected, baseURL.String()) - }) - } -} - -func Test_createBaseURL_error(t *testing.T) { - testCases := []struct { - desc string - tenant string - server string - expected string - }{ - { - desc: "no tenant", - tenant: "", - expected: "missing tenant name", - }, - { - desc: "invalid tenant", - tenant: "%31", - expected: `parse base URL: parse "https://%31.console.ves.volterra.io": invalid URL escape "%31"`, - }, - { - desc: "invalid host", - tenant: "foo", - server: "192.168.0.%31", - expected: `parse base URL: parse "https://foo.192.168.0.%31": invalid URL escape "%31"`, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - _, err := createBaseURL(test.tenant, test.server) - require.EqualError(t, err, test.expected) - }) - } -} diff --git a/providers/dns/freemyip/freemyip.toml b/providers/dns/freemyip/freemyip.toml index adbf9e213..4821e2a9c 100644 --- a/providers/dns/freemyip/freemyip.toml +++ b/providers/dns/freemyip/freemyip.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' FREEMYIP_TOKEN=xxxxxx \ -lego --dns freemyip -d '*.example.com' -d example.com run +lego --email you@example.com --dns freemyip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gandi/gandi.toml b/providers/dns/gandi/gandi.toml index 23d7de5db..96d5233be 100644 --- a/providers/dns/gandi/gandi.toml +++ b/providers/dns/gandi/gandi.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' GANDI_API_KEY=abcdefghijklmnopqrstuvwx \ -lego --dns gandi -d '*.example.com' -d example.com run +lego --email you@example.com --dns gandi -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gandiv5/gandiv5.toml b/providers/dns/gandiv5/gandiv5.toml index 31568e89b..246b03524 100644 --- a/providers/dns/gandiv5/gandiv5.toml +++ b/providers/dns/gandiv5/gandiv5.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \ -lego --dns gandiv5 -d '*.example.com' -d example.com run +lego --email you@example.com --dns gandiv5 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index bfb71c9f6..018a05799 100644 --- a/providers/dns/gandiv5/internal/client.go +++ b/providers/dns/gandiv5/internal/client.go @@ -15,7 +15,10 @@ import ( ) // defaultBaseURL endpoint is the Gandi API endpoint used by Present and CleanUp. -const defaultBaseURL = "https://api.gandi.net/v5/livedns" +const defaultBaseURL = "https://dns.api.gandi.net/api/v5" + +// APIKeyHeader API key header. +const APIKeyHeader = "X-Api-Key" // Related to Personal Access Token. const authorizationHeader = "Authorization" @@ -130,7 +133,7 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain, name string) error func (c *Client) do(req *http.Request, result any) error { if c.apiKey != "" { - req.Header.Set(authorizationHeader, "Apikey "+c.apiKey) + req.Header.Set(APIKeyHeader, c.apiKey) } if c.pat != "" { diff --git a/providers/dns/gandiv5/internal/client_test.go b/providers/dns/gandiv5/internal/client_test.go index 6a4158dcb..2465566f9 100644 --- a/providers/dns/gandiv5/internal/client_test.go +++ b/providers/dns/gandiv5/internal/client_test.go @@ -9,29 +9,23 @@ import ( "github.com/stretchr/testify/require" ) -func mockBuilder(apiKey, pat string) *servermock.Builder[*Client] { - checkHeaders := servermock.CheckHeader().WithJSONHeaders() - - if apiKey != "" { - checkHeaders = checkHeaders.WithAuthorization("Apikey secret-apikey") - } else { - checkHeaders = checkHeaders.WithAuthorization("Bearer secret-pat") - } - +func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(apiKey, pat) + client := NewClient("secret", "xxx") client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() return client, nil }, - checkHeaders, + servermock.CheckHeader().WithJSONHeaders(). + With("X-Api-Key", "secret"). + WithAuthorization("Bearer xxx"), ) } func TestClient_AddTXTRecord(t *testing.T) { - client := mockBuilder("secret-apikey", ""). + client := mockBuilder(). Route("GET /domains/example.com/records/foo/TXT", servermock.ResponseFromFixture("add_txt_record_get.json")). Route("PUT /domains/example.com/records/foo/TXT", @@ -44,7 +38,7 @@ func TestClient_AddTXTRecord(t *testing.T) { } func TestClient_DeleteTXTRecord(t *testing.T) { - client := mockBuilder("", "secret-pat"). + client := mockBuilder(). Route("DELETE /domains/example.com/records/foo/TXT", servermock.ResponseFromFixture("api_response.json")). Build(t) diff --git a/providers/dns/gcloud/gcloud.toml b/providers/dns/gcloud/gcloud.toml index 63d22bed3..471e2e9d1 100644 --- a/providers/dns/gcloud/gcloud.toml +++ b/providers/dns/gcloud/gcloud.toml @@ -8,18 +8,18 @@ Example = ''' # Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run # Using default credentials with impersonation GCE_PROJECT="gc-project-id" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run # Using service account key with impersonation GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 61e8ee66f..ff317946d 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -2,7 +2,6 @@ package gcloud import ( - "context" "encoding/json" "errors" "fmt" @@ -20,6 +19,7 @@ import ( "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" + "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" gdns "google.golang.org/api/dns/v1" diff --git a/providers/dns/gcore/gcore.go b/providers/dns/gcore/gcore.go index 9b98f28d4..19a548810 100644 --- a/providers/dns/gcore/gcore.go +++ b/providers/dns/gcore/gcore.go @@ -1,16 +1,18 @@ -// Package gcore implements a DNS provider for solving the DNS-01 challenge using G-Core. package gcore import ( + "context" "errors" "fmt" "net/http" + "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/gcore" + "github.com/go-acme/lego/v4/providers/dns/gcore/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -25,17 +27,28 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const ( + defaultPropagationTimeout = 360 * time.Second + defaultPollingInterval = 20 * time.Second +) + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config for DNSProvider. -type Config = gcore.Config +type Config struct { + APIToken string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, gcore.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, gcore.DefaultPollingInterval), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), }, @@ -44,7 +57,8 @@ func NewDefaultConfig() *Config { // DNSProvider an implementation of challenge.Provider contract. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *internal.Client } // NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. @@ -66,36 +80,93 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("gcore: the configuration of the DNS provider is nil") } - provider, err := gcore.NewDNSProviderConfig(config, "") - if err != nil { - return nil, fmt.Errorf("gcore: %w", err) + if config.APIToken == "" { + return nil, errors.New("gcore: incomplete credentials provided") } - return &DNSProvider{prv: provider}, nil + client := internal.NewClient(config.APIToken) + + 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 { - err := d.prv.Present(domain, token, keyAuth) +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("gcore: %w", err) } + err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) + if err != nil { + return fmt.Errorf("gcore: add txt record: %w", err) + } + return nil } -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) +// CleanUp removes the record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("gcore: %w", err) } + err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("gcore: remove txt record: %w", err) + } + return nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { + var lastErr error + + for _, zone := range extractAllZones(fqdn) { + dnsZone, err := d.client.GetZone(ctx, zone) + if err == nil { + return dnsZone.Name, nil + } + + lastErr = err + } + + return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) +} + +func extractAllZones(fqdn string) []string { + parts := strings.Split(dns01.UnFqdn(fqdn), ".") + if len(parts) < 3 { + return nil + } + + var zones []string + for i := 1; i < len(parts)-1; i++ { + zones = append(zones, strings.Join(parts[i:], ".")) + } + + return zones } diff --git a/providers/dns/gcore/gcore.toml b/providers/dns/gcore/gcore.toml index 983c35f8a..986455e80 100644 --- a/providers/dns/gcore/gcore.toml +++ b/providers/dns/gcore/gcore.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' GCORE_PERMANENT_API_TOKEN=xxxxx \ -lego --dns gcore -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcore -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gcore/gcore_test.go b/providers/dns/gcore/gcore_test.go index 6f8e38c12..88769df21 100644 --- a/providers/dns/gcore/gcore_test.go +++ b/providers/dns/gcore/gcore_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -43,7 +44,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -77,7 +79,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -112,3 +115,30 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func Test_extractAllZones(t *testing.T) { + testCases := []struct { + desc string + fqdn string + expected []string + }{ + { + desc: "success", + fqdn: "_acme-challenge.my.test.domain.com.", + expected: []string{"my.test.domain.com", "test.domain.com", "domain.com"}, + }, + { + desc: "empty", + fqdn: "_acme-challenge.com.", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + got := extractAllZones(test.fqdn) + assert.Equal(t, test.expected, got) + }) + } +} diff --git a/providers/dns/internal/gcore/internal/client.go b/providers/dns/gcore/internal/client.go similarity index 92% rename from providers/dns/internal/gcore/internal/client.go rename to providers/dns/gcore/internal/client.go index f3ad4e461..638aaf0d7 100644 --- a/providers/dns/internal/gcore/internal/client.go +++ b/providers/dns/gcore/internal/client.go @@ -27,7 +27,7 @@ const txtRecordType = "TXT" type Client struct { token string - BaseURL *url.URL + baseURL *url.URL HTTPClient *http.Client } @@ -37,7 +37,7 @@ func NewClient(token string) *Client { return &Client{ token: token, - BaseURL: baseURL, + baseURL: baseURL, HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -45,7 +45,7 @@ func NewClient(token string) *Client { // GetZone gets zone information. // https://api.gcore.com/docs/dns#tag/zones/operation/Zone func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) { - endpoint := c.BaseURL.JoinPath("v2", "zones", name) + endpoint := c.baseURL.JoinPath("v2", "zones", name) zone := Zone{} @@ -60,7 +60,7 @@ func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) { // GetRRSet gets RRSet item. // https://api.gcore.com/docs/dns#tag/rrsets/operation/RRSet func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) { - endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) var result RRSet @@ -75,7 +75,7 @@ func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) // DeleteRRSet removes RRSet record. // https://api.gcore.com/docs/dns#tag/rrsets/operation/DeleteRRSet func (c *Client) DeleteRRSet(ctx context.Context, zone, name string) error { - endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) err := c.doRequest(ctx, http.MethodDelete, endpoint, nil, nil) if err != nil { @@ -106,14 +106,14 @@ func (c *Client) AddRRSet(ctx context.Context, zone, recordName, value string, t // https://api.gcore.com/docs/dns#tag/rrsets/operation/CreateRRSet func (c *Client) createRRSet(ctx context.Context, zone, name string, record RRSet) error { - endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) return c.doRequest(ctx, http.MethodPost, endpoint, record, nil) } // https://api.gcore.com/docs/dns#tag/rrsets/operation/UpdateRRSet func (c *Client) updateRRSet(ctx context.Context, zone, name string, record RRSet) error { - endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) return c.doRequest(ctx, http.MethodPut, endpoint, record, nil) } diff --git a/providers/dns/internal/gcore/internal/client_test.go b/providers/dns/gcore/internal/client_test.go similarity index 99% rename from providers/dns/internal/gcore/internal/client_test.go rename to providers/dns/gcore/internal/client_test.go index 7d70c9308..4a0f83311 100644 --- a/providers/dns/internal/gcore/internal/client_test.go +++ b/providers/dns/gcore/internal/client_test.go @@ -21,7 +21,7 @@ func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient(testToken) - client.BaseURL, _ = url.Parse(server.URL) + client.baseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() return client, nil diff --git a/providers/dns/internal/gcore/internal/types.go b/providers/dns/gcore/internal/types.go similarity index 100% rename from providers/dns/internal/gcore/internal/types.go rename to providers/dns/gcore/internal/types.go diff --git a/providers/dns/gigahostno/gigahostno.go b/providers/dns/gigahostno/gigahostno.go deleted file mode 100644 index b9ed23f3f..000000000 --- a/providers/dns/gigahostno/gigahostno.go +++ /dev/null @@ -1,233 +0,0 @@ -// Package gigahostno implements a DNS provider for solving the DNS-01 challenge using Gigahost.no. -package gigahostno - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/gigahostno/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "GIGAHOSTNO_" - - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - EnvSecret = envNamespace + "SECRET" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Username string - Password string - Secret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - - identifier *internal.Identifier - client *internal.Client - - tokenMu sync.Mutex - token *internal.Token -} - -// NewDNSProvider returns a DNSProvider instance configured for Gigahost. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsername, EnvPassword) - if err != nil { - return nil, fmt.Errorf("gigahostno: %w", err) - } - - config := NewDefaultConfig() - config.Username = values[EnvUsername] - config.Password = values[EnvPassword] - config.Secret = env.GetOrFile(EnvSecret) - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Gigahost. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("gigahostno: the configuration of the DNS provider is nil") - } - - identifier, err := internal.NewIdentifier(config.Username, config.Password, config.Secret) - if err != nil { - return nil, fmt.Errorf("gigahostno: %w", err) - } - - if config.HTTPClient != nil { - identifier.HTTPClient = config.HTTPClient - } - - identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) - - client := internal.NewClient() - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - identifier: identifier, - client: client, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - err := d.authenticate(ctx) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - ctx = internal.WithContext(ctx, d.token.Token) - - zone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - record := internal.Record{ - Name: subDomain, - Type: "TXT", - Value: info.Value, - TTL: d.config.TTL, - } - - err = d.client.CreateNewRecord(ctx, zone.ID, record) - if err != nil { - return fmt.Errorf("gigahostno: create new record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - err := d.authenticate(ctx) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - ctx = internal.WithContext(ctx, d.token.Token) - - zone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) - if err != nil { - return fmt.Errorf("gigahostno: %w", err) - } - - records, err := d.client.GetZoneRecords(ctx, zone.ID) - if err != nil { - return fmt.Errorf("gigahostno: get zone records: %w", err) - } - - for _, record := range records { - if record.Type == "TXT" && record.Name == subDomain && record.Value == info.Value { - err := d.client.DeleteRecord(ctx, zone.ID, record.ID, record.Name, record.Type) - if err != nil { - return fmt.Errorf("gigahostno: delete record: %w", err) - } - - break - } - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) authenticate(ctx context.Context) error { - d.tokenMu.Lock() - defer d.tokenMu.Unlock() - - if !d.token.IsExpired() { - return nil - } - - tok, err := d.identifier.Authenticate(ctx) - if err != nil { - return fmt.Errorf("authenticate: %w", err) - } - - d.token = tok - - return nil -} - -func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*internal.Zone, error) { - zones, err := d.client.GetZones(ctx) - if err != nil { - return nil, fmt.Errorf("get zones: %w", err) - } - - for d := range dns01.UnFqdnDomainsSeq(fqdn) { - for _, zone := range zones { - if zone.Name == d && zone.Active == "1" { - return &zone, nil - } - } - } - - return nil, fmt.Errorf("zone not found for %q", fqdn) -} diff --git a/providers/dns/gigahostno/gigahostno.toml b/providers/dns/gigahostno/gigahostno.toml deleted file mode 100644 index b8d3fad2b..000000000 --- a/providers/dns/gigahostno/gigahostno.toml +++ /dev/null @@ -1,25 +0,0 @@ -Name = "Gigahost.no" -Description = '''''' -URL = "https://gigahost.no/" -Code = "gigahostno" -Since = "v4.29.0" - -Example = ''' -GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ -GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns gigahostno -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - GIGAHOSTNO_USERNAME = "Username" - GIGAHOSTNO_PASSWORD = "Password" - [Configuration.Additional] - GIGAHOSTNO_SECRET = "TOTP secret" - GIGAHOSTNO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - GIGAHOSTNO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - GIGAHOSTNO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - GIGAHOSTNO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://gigahost.no/api-dokumentasjon" diff --git a/providers/dns/gigahostno/gigahostno_test.go b/providers/dns/gigahostno/gigahostno_test.go deleted file mode 100644 index 7aaac0159..000000000 --- a/providers/dns/gigahostno/gigahostno_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package gigahostno - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/go-acme/lego/v4/providers/dns/gigahostno/internal" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvUsername, - EnvPassword, - EnvSecret, -).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "secret", - EnvSecret: "super-secret", - }, - }, - { - desc: "missing GIGAHOSTNO_USERNAME", - envVars: map[string]string{ - EnvPassword: "secret", - }, - expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_USERNAME", - }, - { - desc: "missing GIGAHOSTNO_PASSWORD", - envVars: map[string]string{ - EnvUsername: "user", - }, - expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_PASSWORD", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_USERNAME,GIGAHOSTNO_PASSWORD", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - username string - password string - secret string - expected string - }{ - { - desc: "success", - username: "user", - password: "secret", - secret: "super-secret", - }, - { - desc: "missing username", - password: "secret", - expected: "gigahostno: credentials missing", - }, - { - desc: "missing password", - username: "user", - expected: "gigahostno: credentials missing", - }, - { - desc: "missing credentials", - expected: "gigahostno: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Username = test.username - config.Password = test.password - config.Secret = test.secret - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.Username = "user" - config.Password = "secret" - config.Secret = "JBSWY3DPEHPK3PXP" - - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - p.identifier.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /authenticate", - servermock.ResponseFromInternal("authenticate.json")). - Route("GET /dns/zones", - servermock.ResponseFromInternal("zones.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secrettoken")). - Route("POST /dns/zones/123/records", - servermock.ResponseFromInternal("create_record.json"), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secrettoken")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_Present_token_not_expired(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromInternal("zones.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secret-token")). - Route("POST /dns/zones/123/records", - servermock.ResponseFromInternal("create_record.json"), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secret-token")). - Build(t) - - provider.token = &internal.Token{ - Token: "secret-token", - TokenExpire: 65322892800, // 2040-01-01 - CustomerID: "123", - } - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("POST /authenticate", - servermock.ResponseFromInternal("authenticate.json")). - Route("GET /dns/zones", - servermock.ResponseFromInternal("zones.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secrettoken")). - Route("GET /dns/zones/123/records", - servermock.ResponseFromInternal("zone_records.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secrettoken")). - Route("DELETE /dns/zones/123/records/jkl012", - servermock.ResponseFromInternal("delete_record.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge"). - With("type", "TXT"), - servermock.CheckHeader(). - WithAuthorization("Bearer secrettoken")). - Build(t) - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp_token_not_expired(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromInternal("zones.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secret-token")). - Route("GET /dns/zones/123/records", - servermock.ResponseFromInternal("zone_records.json"), - servermock.CheckHeader(). - WithAuthorization("Bearer secret-token")). - Route("DELETE /dns/zones/123/records/jkl012", - servermock.ResponseFromInternal("delete_record.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge"). - With("type", "TXT"), - servermock.CheckHeader(). - WithAuthorization("Bearer secret-token")). - Build(t) - - provider.token = &internal.Token{ - Token: "secret-token", - TokenExpire: 65322892800, // 2040-01-01 - CustomerID: "123", - } - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/gigahostno/internal/client.go b/providers/dns/gigahostno/internal/client.go deleted file mode 100644 index cfff3a7b8..000000000 --- a/providers/dns/gigahostno/internal/client.go +++ /dev/null @@ -1,172 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "github.com/go-acme/lego/v4/providers/dns/internal/useragent" -) - -const defaultBaseURL = "https://api.gigahost.no/api/v0" - -const authorizationHeader = "Authorization" - -// Client the Gigahost.no API client. -type Client struct { - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient() *Client { - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - } -} - -// GetZones returns all zones. -func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { - endpoint := c.BaseURL.JoinPath("dns", "zones") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result APIResponse[[]Zone] - - err = c.do(ctx, req, &result) - if err != nil { - return nil, err - } - - return result.Data, nil -} - -// GetZoneRecords returns all records for a zone. -func (c *Client) GetZoneRecords(ctx context.Context, zoneID string) ([]Record, error) { - endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result APIResponse[[]Record] - - err = c.do(ctx, req, &result) - if err != nil { - return nil, err - } - - return result.Data, nil -} - -// CreateNewRecord creates a new record. -func (c *Client) CreateNewRecord(ctx context.Context, zoneID string, record Record) error { - endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return err - } - - return c.do(ctx, req, nil) -} - -// DeleteRecord deletes a record. -func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID, name, recordType string) error { - endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records", recordID) - - query := endpoint.Query() - query.Set("name", name) - query.Set("type", recordType) - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(ctx, req, nil) -} - -func (c *Client) do(ctx context.Context, req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Set(authorizationHeader, "Bearer "+getToken(ctx)) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/gigahostno/internal/client_test.go b/providers/dns/gigahostno/internal/client_test.go deleted file mode 100644 index 8d1298947..000000000 --- a/providers/dns/gigahostno/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 := NewClient() - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestClient_GetZones(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromFixture("zones.json")). - Build(t) - - zones, err := client.GetZones(mockContext(t)) - require.NoError(t, err) - - expected := []Zone{ - { - ID: "123", - Name: "example.com", - NameDisplay: "example.com", - Type: "NATIVE", - Active: "1", - }, - { - ID: "226", - Name: "example.org", - NameDisplay: "example.org", - Type: "NATIVE", - Active: "1", - }, - { - ID: "229", - Name: "example.xn--zckzah", - NameDisplay: "example.テスト", - Type: "NATIVE", - Active: "1", - }, - } - - assert.Equal(t, expected, zones) -} - -func TestClient_GetZones_error(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.GetZones(mockContext(t)) - require.EqualError(t, err, "401: 401 Unauthorized: 401 Unauthorized") -} - -func TestClient_GetZoneRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones/123/records", - servermock.ResponseFromFixture("zone_records.json")). - Build(t) - - zones, err := client.GetZoneRecords(mockContext(t), "123") - require.NoError(t, err) - - expected := []Record{ - { - ID: "abc123", - Name: "@", - Type: "A", - Value: "185.125.168.166", - TTL: 3600, - }, - { - ID: "def456", - Name: "www", - Type: "A", - Value: "185.125.168.166", - TTL: 3600, - }, - { - ID: "ghi789", - Name: "@", - Type: "MX", - Value: "mail.example.no", - TTL: 3600, - }, - { - ID: "jkl012", - Name: "_acme-challenge", - Type: "TXT", - Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - }, - } - - assert.Equal(t, expected, zones) -} - -func TestClient_CreateNewRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /dns/zones/example.com/records", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := Record{ - Name: "_acme-challenge", - Type: "TXT", - Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - } - - err := client.CreateNewRecord(mockContext(t), "example.com", record) - require.NoError(t, err) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("/dns/zones/123/records/abc123", - servermock.ResponseFromFixture("delete_record.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge"). - With("type", "TXT")). - Build(t) - - err := client.DeleteRecord(mockContext(t), "123", "abc123", "_acme-challenge", "TXT") - require.NoError(t, err) -} diff --git a/providers/dns/gigahostno/internal/fixtures/authenticate-request.json b/providers/dns/gigahostno/internal/fixtures/authenticate-request.json deleted file mode 100644 index c641cd3e5..000000000 --- a/providers/dns/gigahostno/internal/fixtures/authenticate-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "username": "user", - "password": "secret" -} diff --git a/providers/dns/gigahostno/internal/fixtures/authenticate.json b/providers/dns/gigahostno/internal/fixtures/authenticate.json deleted file mode 100644 index 2c43ccbfe..000000000 --- a/providers/dns/gigahostno/internal/fixtures/authenticate.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "meta": { - "status": 200, - "status_message": "200 OK", - "maintenance": false - }, - "data": { - "token": "secrettoken", - "token_expire": 1577836800, - "customer_id": "16030", - "contact_id": "15182", - "customer_name": "Cloudline AS", - "contact_username": "test@example.com", - "contact_access_level": "admin", - "customer_address": "Grønland 14", - "customer_zipcode": "5918", - "customer_city": "Frekhaug", - "customer_province": "Vestland", - "ga_secret": "ga_secret", - "ga_enabled": "1", - "vat": 1 - } -} diff --git a/providers/dns/gigahostno/internal/fixtures/create_record-request.json b/providers/dns/gigahostno/internal/fixtures/create_record-request.json deleted file mode 100644 index f8f0b5b11..000000000 --- a/providers/dns/gigahostno/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "record_name": "_acme-challenge", - "record_type": "TXT", - "record_value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "record_ttl": 120 -} diff --git a/providers/dns/gigahostno/internal/fixtures/create_record.json b/providers/dns/gigahostno/internal/fixtures/create_record.json deleted file mode 100644 index 9232677d7..000000000 --- a/providers/dns/gigahostno/internal/fixtures/create_record.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "meta": { - "status": 201, - "status_message": "201 Created", - "message": "Record created successfully." - } -} diff --git a/providers/dns/gigahostno/internal/fixtures/delete_record.json b/providers/dns/gigahostno/internal/fixtures/delete_record.json deleted file mode 100644 index 9d87f2f42..000000000 --- a/providers/dns/gigahostno/internal/fixtures/delete_record.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "meta": { - "status": 200, - "status_message": "200 OK", - "message": "Record deleted successfully." - } -} diff --git a/providers/dns/gigahostno/internal/fixtures/error.json b/providers/dns/gigahostno/internal/fixtures/error.json deleted file mode 100644 index f2fcfd437..000000000 --- a/providers/dns/gigahostno/internal/fixtures/error.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "meta": { - "status": 401, - "status_message": "401 Unauthorized", - "maintenance": false, - "message": "401 Unauthorized" - }, - "data": [] -} diff --git a/providers/dns/gigahostno/internal/fixtures/zone_records.json b/providers/dns/gigahostno/internal/fixtures/zone_records.json deleted file mode 100644 index e67ff83f4..000000000 --- a/providers/dns/gigahostno/internal/fixtures/zone_records.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "meta": { - "status": 200, - "status_message": "200 OK" - }, - "data": [ - { - "record_id": "abc123", - "record_name": "@", - "record_type": "A", - "record_value": "185.125.168.166", - "record_ttl": 3600, - "record_priority": null - }, - { - "record_id": "def456", - "record_name": "www", - "record_type": "A", - "record_value": "185.125.168.166", - "record_ttl": 3600, - "record_priority": null - }, - { - "record_id": "ghi789", - "record_name": "@", - "record_type": "MX", - "record_value": "mail.example.no", - "record_ttl": 3600, - "record_priority": 10 - }, - { - "record_id": "jkl012", - "record_name": "_acme-challenge", - "record_type": "TXT", - "record_value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "record_ttl": 120 - } - ] -} diff --git a/providers/dns/gigahostno/internal/fixtures/zones.json b/providers/dns/gigahostno/internal/fixtures/zones.json deleted file mode 100644 index d45b0ac49..000000000 --- a/providers/dns/gigahostno/internal/fixtures/zones.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "meta": { - "status": 200, - "status_message": "200 OK", - "maintenance": false, - "message": "200 OK" - }, - "data": [ - { - "zone_id": "123", - "cust_id": "16030", - "order_id": "26117", - "zone_name": "example.com", - "zone_type": "NATIVE", - "zone_active": "1", - "zone_protected": "1", - "zone_is_registered": "1", - "domain_registrar": "norid", - "domain_status": "active", - "domain_registered_date": "2025-11-23 15:17:38", - "domain_expiry_date": "2026-11-23 15:17:38", - "domain_updated_date": "2025-11-23 16:17:38", - "domain_auto_renew": "1", - "domain_epp_id": "LEG2175D-NORID", - "domain_registrant_id": "CA19777O", - "domain_tech_id": "GH295R", - "domain_auth_info": "XXXXXXXXXXXXXXX", - "domain_locked": "0", - "domain_dnssec": "0", - "domain_dnssec_data": null, - "domain_protected_email": null, - "zone_created": "2025-11-23 16:17:29", - "zone_updated": 1700000000, - "external_dns": "0", - "record_count": 4, - "zone_name_display": "example.com" - }, - { - "zone_id": "226", - "cust_id": "16030", - "order_id": "26114", - "zone_name": "example.org", - "zone_type": "NATIVE", - "zone_active": "1", - "zone_protected": "1", - "zone_is_registered": "1", - "domain_registrar": "norid", - "domain_status": "active", - "domain_registered_date": "2025-11-23 14:15:01", - "domain_expiry_date": "2026-11-23 14:15:01", - "domain_updated_date": "2025-11-23 15:15:02", - "domain_auto_renew": "1", - "domain_epp_id": "TEO218D-NORID", - "domain_registrant_id": "CA19774O", - "domain_tech_id": "GH295R", - "domain_auth_info": "XXXXXXXXXXXXXX", - "domain_locked": "0", - "domain_dnssec": "0", - "domain_dnssec_data": null, - "domain_protected_email": null, - "zone_created": "2025-11-23 15:13:27", - "zone_updated": 1700000000, - "external_dns": "0", - "record_count": 5, - "zone_name_display": "example.org" - }, - { - "zone_id": "229", - "cust_id": "16030", - "order_id": "26119", - "zone_name": "example.xn--zckzah", - "zone_type": "NATIVE", - "zone_active": "1", - "zone_protected": "1", - "zone_is_registered": "1", - "domain_registrar": "norid", - "domain_status": "active", - "domain_registered_date": "2014-12-01 12:40:48", - "domain_expiry_date": "2026-12-01 12:40:48", - "domain_updated_date": "2025-11-23 15:37:36", - "domain_auto_renew": "1", - "domain_epp_id": "DIT1003D-NORID", - "domain_registrant_id": "DCA822O", - "domain_tech_id": "GH295R", - "domain_auth_info": "XXXXXXXXXXXXXX", - "domain_locked": "0", - "domain_dnssec": "0", - "domain_dnssec_data": null, - "domain_protected_email": null, - "zone_created": "2025-11-23 16:37:15", - "zone_updated": 1700000000, - "external_dns": "0", - "record_count": 4, - "zone_name_display": "example.\u30C6\u30B9\u30C8" - } - ] -} diff --git a/providers/dns/gigahostno/internal/identity.go b/providers/dns/gigahostno/internal/identity.go deleted file mode 100644 index 262dfabdd..000000000 --- a/providers/dns/gigahostno/internal/identity.go +++ /dev/null @@ -1,122 +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" - "github.com/pquerna/otp/totp" -) - -type token string - -const tokenKey token = "token" - -type Identifier struct { - username string - password string - Secret string - - BaseURL *url.URL - HTTPClient *http.Client -} - -func NewIdentifier(username, password, secret string) (*Identifier, error) { - if username == "" || password == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Identifier{ - username: username, - password: password, - Secret: secret, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Identifier) Authenticate(ctx context.Context) (*Token, error) { - endpoint := c.BaseURL.JoinPath("authenticate") - - auth := Auth{Username: c.username, Password: c.password} - - if c.Secret != "" { - tan, err := totp.GenerateCode(c.Secret, time.Now()) - if err != nil { - return nil, fmt.Errorf("generate TOTP: %w", err) - } - - auth.Code, err = strconv.Atoi(tan) - if err != nil { - return nil, fmt.Errorf("parse TOTP: %w", err) - } - } - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, auth) - if err != nil { - return nil, err - } - - var result APIResponse[*Token] - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Data, nil -} - -func (c *Identifier) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func WithContext(ctx context.Context, credential string) context.Context { - return context.WithValue(ctx, tokenKey, credential) -} - -func getToken(ctx context.Context) string { - credential, ok := ctx.Value(tokenKey).(string) - if !ok { - return "" - } - - return credential -} diff --git a/providers/dns/gigahostno/internal/identity_test.go b/providers/dns/gigahostno/internal/identity_test.go deleted file mode 100644 index 09d72746a..000000000 --- a/providers/dns/gigahostno/internal/identity_test.go +++ /dev/null @@ -1,108 +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 setupIdentifierClient(server *httptest.Server) (*Identifier, error) { - client, err := NewIdentifier("user", "secret", "") - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil -} - -func mockContext(t *testing.T) context.Context { - t.Helper() - - return context.WithValue(t.Context(), tokenKey, "secret") -} - -func TestIdentifier_Authenticate(t *testing.T) { - identifier := servermock.NewBuilder[*Identifier](setupIdentifierClient). - Route("POST /authenticate", - servermock.ResponseFromFixture("authenticate.json"), - servermock.CheckRequestJSONBodyFromFixture("authenticate-request.json")). - Build(t) - - token, err := identifier.Authenticate(context.Background()) - require.NoError(t, err) - - expected := &Token{ - Token: "secrettoken", - TokenExpire: 1577836800, - CustomerID: "16030", - ContactID: "15182", - CustomerName: "Cloudline AS", - ContactUsername: "test@example.com", - ContactAccessLevel: "admin", - CustomerAddress: "Grønland 14", - CustomerZipcode: "5918", - CustomerCity: "Frekhaug", - CustomerProvince: "Vestland", - GASecret: "ga_secret", - GAEnabled: "1", - VAT: 1, - } - - assert.Equal(t, expected, token) -} - -func TestToken_IsExpired(t *testing.T) { - testCases := []struct { - desc string - token *Token - assert assert.BoolAssertionFunc - }{ - { - desc: "nil", - assert: assert.True, - }, - { - desc: "empty", - token: &Token{}, - assert: assert.True, - }, - { - desc: "not expired", - token: &Token{ - TokenExpire: 65322892800, // 2040-01-01 - }, - assert: assert.False, - }, - { - desc: "now", - token: &Token{ - TokenExpire: time.Now().Unix(), - }, - assert: assert.True, - }, - { - desc: "now + 2 minutes", - token: &Token{ - TokenExpire: time.Now().Add(2 * time.Minute).Unix(), - }, - assert: assert.False, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - test.assert(t, test.token.IsExpired()) - }) - } -} diff --git a/providers/dns/gigahostno/internal/types.go b/providers/dns/gigahostno/internal/types.go deleted file mode 100644 index e998dc084..000000000 --- a/providers/dns/gigahostno/internal/types.go +++ /dev/null @@ -1,73 +0,0 @@ -package internal - -import ( - "fmt" - "time" -) - -type APIError struct { - Meta MetaData `json:"meta"` -} - -func (a *APIError) Error() string { - return fmt.Sprintf("%d: %s: %s", a.Meta.Status, a.Meta.StatusMessage, a.Meta.Message) -} - -type MetaData struct { - Status int `json:"status,omitempty"` - StatusMessage string `json:"status_message,omitempty"` - Maintenance bool `json:"maintenance"` - Message string `json:"message,omitempty"` -} - -type APIResponse[T any] struct { - Meta MetaData `json:"meta"` - Data T `json:"data,omitempty"` -} - -type Zone struct { - ID string `json:"zone_id,omitempty"` - Name string `json:"zone_name,omitempty"` - NameDisplay string `json:"zone_name_display,omitempty"` - Type string `json:"zone_type,omitempty"` - Active string `json:"zone_active,omitempty"` -} - -type Record struct { - ID string `json:"record_id,omitempty"` - Name string `json:"record_name,omitempty"` - Type string `json:"record_type,omitempty"` - Value string `json:"record_value,omitempty"` - TTL int `json:"record_ttl,omitempty"` -} - -type Auth struct { - Username string `json:"username"` - Password string `json:"password"` - Code int `json:"code,omitempty"` -} - -type Token struct { - Token string `json:"token,omitempty"` - TokenExpire int64 `json:"token_expire,omitempty"` - CustomerID string `json:"customer_id,omitempty"` - ContactID string `json:"contact_id,omitempty"` - CustomerName string `json:"customer_name,omitempty"` - ContactUsername string `json:"contact_username,omitempty"` - ContactAccessLevel string `json:"contact_access_level,omitempty"` - CustomerAddress string `json:"customer_address,omitempty"` - CustomerZipcode string `json:"customer_zipcode,omitempty"` - CustomerCity string `json:"customer_city,omitempty"` - CustomerProvince string `json:"customer_province,omitempty"` - GASecret string `json:"ga_secret,omitempty"` - GAEnabled string `json:"ga_enabled,omitempty"` - VAT int `json:"vat,omitempty"` -} - -func (t *Token) IsExpired() bool { - if t == nil { - return true - } - - return time.Now().UTC().Add(1 * time.Minute).After(time.Unix(t.TokenExpire, 0).UTC()) -} diff --git a/providers/dns/glesys/glesys.toml b/providers/dns/glesys/glesys.toml index c0e2613b8..1bdd43c2b 100644 --- a/providers/dns/glesys/glesys.toml +++ b/providers/dns/glesys/glesys.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' GLESYS_API_USER=xxxxx \ GLESYS_API_KEY=yyyyy \ -lego --dns glesys -d '*.example.com' -d example.com run +lego --email you@example.com --dns glesys -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/godaddy/godaddy.toml b/providers/dns/godaddy/godaddy.toml index b906605b3..acf0bf404 100644 --- a/providers/dns/godaddy/godaddy.toml +++ b/providers/dns/godaddy/godaddy.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' GODADDY_API_KEY=xxxxxxxx \ GODADDY_API_SECRET=yyyyyyyy \ -lego --dns godaddy -d '*.example.com' -d example.com run +lego --email you@example.com --dns godaddy -d '*.example.com' -d example.com run ''' Additional = ''' 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/googledomains/googledomains.toml b/providers/dns/googledomains/googledomains.toml index 52330795d..1ac7e5e54 100644 --- a/providers/dns/googledomains/googledomains.toml +++ b/providers/dns/googledomains/googledomains.toml @@ -8,7 +8,7 @@ Since = "v4.11.0" Example = ''' GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns googledomains -d '*.example.com' -d example.com run +lego --email you@example.com --dns googledomains -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gravity/gravity.go b/providers/dns/gravity/gravity.go deleted file mode 100644 index b0bbb2fcb..000000000 --- a/providers/dns/gravity/gravity.go +++ /dev/null @@ -1,209 +0,0 @@ -// Package gravity implements a DNS provider for solving the DNS-01 challenge using Gravity. -package gravity - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/gravity/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/google/uuid" -) - -// Environment variables names. -const ( - envNamespace = "GRAVITY_" - - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - EnvServerURL = envNamespace + "SERVER_URL" - - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" - EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Username string - Password string - ServerURL string - - PropagationTimeout time.Duration - PollingInterval time.Duration - SequenceInterval time.Duration - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, 1*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - records map[string]internal.Record - recordsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Gravity. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsername, EnvPassword, EnvServerURL) - if err != nil { - return nil, fmt.Errorf("gravity: %w", err) - } - - config := NewDefaultConfig() - config.Username = values[EnvUsername] - config.Password = values[EnvPassword] - config.ServerURL = values[EnvServerURL] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Gravity. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("gravity: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.ServerURL, config.Username, config.Password) - if err != nil { - return nil, fmt.Errorf("gravity: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - records: make(map[string]internal.Record), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - _, err := d.client.Login(ctx) - if err != nil { - return fmt.Errorf("gravity: login: %w", err) - } - - zone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("gravity: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) - if err != nil { - return fmt.Errorf("gravity: %w", err) - } - - id := uuid.New() - - record := internal.Record{ - Data: info.Value, - Hostname: subDomain, - Type: "TXT", - UID: id.String(), - } - - err = d.client.CreateDNSRecord(ctx, zone, record) - if err != nil { - return fmt.Errorf("gravity: create DNS record: %w", err) - } - - d.recordsMu.Lock() - - record.Fqdn = zone - d.records[token] = record - d.recordsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - d.recordsMu.Lock() - record, ok := d.records[token] - d.recordsMu.Unlock() - - if !ok { - return fmt.Errorf("gravity: unknown record for '%s' '%s'", info.EffectiveFQDN, token) - } - - err := d.client.DeleteDNSRecord(context.Background(), record.Fqdn, record) - if err != nil { - return fmt.Errorf("gravity: delete record: %w", err) - } - - 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 -} - -// Sequential implements the [dns01.sequential] interface. -// It changes the behavior of the provider to resolve DNS challenges sequentially. -// Returns the interval between each iteration. -// -// Gravity supports adding multiple records for the same domain, but the DNS server doesn't work as expected: -// if you call the DNS server, it will answer only the latest record instead of all of them. -func (d *DNSProvider) Sequential() time.Duration { - return d.config.SequenceInterval -} - -func (d *DNSProvider) findZone(ctx context.Context, effectiveFQDN string) (string, error) { - var zone string - - for fqdn := range dns01.DomainsSeq(effectiveFQDN) { - zones, err := d.client.GetDNSZones(ctx, fqdn) - if err != nil { - return "", fmt.Errorf("get DNS zones: %w", err) - } - - if len(zones) != 0 { - zone = zones[0].Name - break - } - } - - if zone == "" { - return "", fmt.Errorf("could not find zone for %q", effectiveFQDN) - } - - return zone, nil -} diff --git a/providers/dns/gravity/gravity.toml b/providers/dns/gravity/gravity.toml deleted file mode 100644 index 87a303839..000000000 --- a/providers/dns/gravity/gravity.toml +++ /dev/null @@ -1,26 +0,0 @@ -Name = "Gravity" -Description = '''''' -URL = "https://gravity.beryju.io/" -Code = "gravity" -Since = "v4.30.0" - -Example = ''' -GRAVITY_SERVER_URL="https://example.org:1234" \ -GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ -GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns gravity -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - GRAVITY_SERVER_URL = "URL of the server" - GRAVITY_USERNAME = "Username" - GRAVITY_PASSWORD = "Password" - [Configuration.Additional] - GRAVITY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - GRAVITY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - GRAVITY_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 1)" - GRAVITY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://gravity.beryju.io/docs/api/reference/" diff --git a/providers/dns/gravity/gravity_test.go b/providers/dns/gravity/gravity_test.go deleted file mode 100644 index b59b856fe..000000000 --- a/providers/dns/gravity/gravity_test.go +++ /dev/null @@ -1,254 +0,0 @@ -package gravity - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/go-acme/lego/v4/providers/dns/gravity/internal" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvUsername, - EnvPassword, - EnvServerURL, -).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "secret", - EnvServerURL: "https://example.org:1234", - }, - }, - { - desc: "missing EnvUsername", - envVars: map[string]string{ - EnvUsername: "", - EnvPassword: "secret", - EnvServerURL: "https://example.org:1234", - }, - expected: "gravity: some credentials information are missing: GRAVITY_USERNAME", - }, - { - desc: "missing EnvPassword", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "", - EnvServerURL: "https://example.org:1234", - }, - expected: "gravity: some credentials information are missing: GRAVITY_PASSWORD", - }, - { - desc: "missing EnvServerURL", - envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "secret", - EnvServerURL: "", - }, - expected: "gravity: some credentials information are missing: GRAVITY_SERVER_URL", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "gravity: some credentials information are missing: GRAVITY_USERNAME,GRAVITY_PASSWORD,GRAVITY_SERVER_URL", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - username string - password string - serverURL string - expected string - }{ - { - desc: "success", - username: "user", - password: "secret", - serverURL: "https://example.org:1234", - }, - { - desc: "missing username", - username: "", - password: "secret", - serverURL: "https://example.org:1234", - expected: "gravity: credentials missing", - }, - { - desc: "missing password", - username: "user", - password: "", - serverURL: "https://example.org:1234", - expected: "gravity: credentials missing", - }, - { - desc: "missing server URL", - username: "user", - password: "secret", - serverURL: "", - expected: "gravity: server URL missing", - }, - { - desc: "missing credentials", - expected: "gravity: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Username = test.username - config.Password = test.password - config.ServerURL = test.serverURL - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - - config.Username = "user" - config.Password = "secret" - config.ServerURL = server.URL - - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /api/v1/auth/login", - servermock.ResponseFromInternal("login.json"), - servermock.CheckRequestJSONBodyFromInternal("login-request.json")). - Route("GET /api/v1/dns/", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if req.URL.Query().Get("name") != "example.com." { - servermock.ResponseFromInternal("zones.json").ServeHTTP(rw, req) - return - } - - servermock.ResponseFromInternal("zones_empty.json").ServeHTTP(rw, req) - }), - ). - Route("POST /api/v1/dns/zones/records", - servermock.Noop(). - WithStatusCode(http.StatusNoContent), - servermock.CheckQueryParameter().Strict(). - With("zone", "example.com."). - WithRegexp("uid", `\w{8}-\w{4}-\w{4}-\w{4}-\w{12}`). - With("hostname", "_acme-challenge")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /api/v1/dns/zones/records", - servermock.Noop(). - WithStatusCode(http.StatusNoContent), - servermock.CheckQueryParameter().Strict(). - With("zone", "example.com."). - With("uid", "123"). - With("type", "TXT"). - With("hostname", "_acme-challenge")). - Build(t) - - provider.records["abc"] = internal.Record{ - Fqdn: "example.com.", - Hostname: "_acme-challenge", - Type: "TXT", - UID: "123", - } - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/gravity/internal/client.go b/providers/dns/gravity/internal/client.go deleted file mode 100644 index 41c6294c3..000000000 --- a/providers/dns/gravity/internal/client.go +++ /dev/null @@ -1,234 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "github.com/go-acme/lego/v4/providers/dns/internal/useragent" - "golang.org/x/net/publicsuffix" -) - -// Client the Gravity API client. -type Client struct { - username string - password string - - baseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(serverURL, username, password string) (*Client, error) { - if username == "" || password == "" { - return nil, errors.New("credentials missing") - } - - if serverURL == "" { - return nil, errors.New("server URL missing") - } - - baseURL, err := url.Parse(serverURL) - if err != nil { - return nil, err - } - - return &Client{ - username: username, - password: password, - baseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) Login(ctx context.Context) (*Auth, error) { - jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) - if err != nil { - return nil, err - } - - c.HTTPClient.Jar = jar - - login := Login{ - Username: c.username, - Password: c.password, - } - - endpoint := c.baseURL.JoinPath("api", "v1", "auth", "login") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, login) - if err != nil { - return nil, err - } - - result := &Auth{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) Me(ctx context.Context) (*UserInfo, error) { - endpoint := c.baseURL.JoinPath("api", "v1", "auth", "me") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - result := &UserInfo{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, err -} - -func (c *Client) GetDNSZones(ctx context.Context, name string) ([]Zone, error) { - endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones") - - if name != "" { - query := endpoint.Query() - query.Set("name", name) - endpoint.RawQuery = query.Encode() - } - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - result := Zones{} - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Zones, nil -} - -func (c *Client) CreateDNSRecord(ctx context.Context, zone string, record Record) error { - endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones", "records") - - query := endpoint.Query() - - query.Set("zone", zone) - query.Set("hostname", record.Hostname) - - // When the UID is the same as an existing one, the record is updated, else a new record is created. - // An explicit UID is not required to create a record. - if record.UID != "" { - query.Set("uid", record.UID) - } - - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) DeleteDNSRecord(ctx context.Context, zone string, record Record) error { - endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones", "records") - - query := endpoint.Query() - - query.Set("zone", zone) - query.Set("hostname", record.Hostname) - query.Set("uid", record.UID) - query.Set("type", record.Type) - - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/gravity/internal/client_test.go b/providers/dns/gravity/internal/client_test.go deleted file mode 100644 index 98b17c59e..000000000 --- a/providers/dns/gravity/internal/client_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package internal - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder[*Client]( - func(server *httptest.Server) (*Client, error) { - client, err := NewClient(server.URL, "user", "secret") - if err != nil { - return nil, err - } - - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(), - ) -} - -func TestClient_Login(t *testing.T) { - client := mockBuilder(). - Route("POST /api/v1/auth/login", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - http.SetCookie(rw, &http.Cookie{ - Name: "gravity_session", - Value: "session_id", - Path: "/", - }) - - servermock.ResponseFromFixture("login.json").ServeHTTP(rw, req) - }), - servermock.CheckRequestJSONBodyFromFixture("login-request.json")). - Build(t) - - auth, err := client.Login(t.Context()) - require.NoError(t, err) - - cookies := client.HTTPClient.Jar.Cookies(client.baseURL) - - require.Len(t, cookies, 1) - - assert.Equal(t, "gravity_session", cookies[0].Name) - assert.Equal(t, "session_id", cookies[0].Value) - - expected := &Auth{Successful: true} - - assert.Equal(t, expected, auth) -} - -func TestClient_Login_error(t *testing.T) { - client := mockBuilder(). - Route("POST /api/v1/auth/login", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.Login(t.Context()) - require.EqualError(t, err, "status: UNAUTHENTICATED, error: unauthenticated, additionalProp1: string") -} - -func TestClient_Me(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v1/auth/me", - servermock.ResponseFromFixture("me.json")). - Build(t) - - info, err := client.Me(t.Context()) - require.NoError(t, err) - - expected := &UserInfo{ - Username: "admin", - Authenticated: true, - Permissions: []Permission{{ - Methods: []string{"GET", "POST", "PUT", "HEAD", "DELETE"}, - Path: "/*", - }}, - } - - assert.Equal(t, expected, info) -} - -func TestClient_GetDNSZones(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v1/dns/", - servermock.ResponseFromFixture("zones.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "example.com.")). - Build(t) - - zones, err := client.GetDNSZones(t.Context(), "example.com.") - require.NoError(t, err) - - expected := []Zone{{ - Name: "example.com.", - HandlerConfigs: []HandlerConfig{ - {Type: "memory"}, - {Type: "etcd"}, - }, - DefaultTTL: 86400, - RecordCount: 1, - }} - - assert.Equal(t, expected, zones) -} - -func TestClient_CreateDNSRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /api/v1/dns/zones/records", - servermock.Noop(). - WithStatusCode(http.StatusNoContent), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json"), - servermock.CheckQueryParameter().Strict(). - With("zone", "example.com."). - With("uid", "123"). - With("hostname", "_acme-challenge")). - Build(t) - - record := Record{ - Data: "txtTXTtxt", - Hostname: "_acme-challenge", - Type: "TXT", - UID: "123", - } - - err := client.CreateDNSRecord(t.Context(), "example.com.", record) - require.NoError(t, err) -} - -func TestClient_DeleteDNSRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /api/v1/dns/zones/records", - servermock.Noop(). - WithStatusCode(http.StatusNoContent), - servermock.CheckQueryParameter().Strict(). - With("zone", "example.com."). - With("uid", "123"). - With("type", "TXT"). - With("hostname", "_acme-challenge")). - Build(t) - - record := Record{ - Data: "txtTXTtxt", - Hostname: "_acme-challenge", - Type: "TXT", - UID: "123", - } - - err := client.DeleteDNSRecord(t.Context(), "example.com.", record) - require.NoError(t, err) -} diff --git a/providers/dns/gravity/internal/fixtures/create_record-request.json b/providers/dns/gravity/internal/fixtures/create_record-request.json deleted file mode 100644 index d671d1342..000000000 --- a/providers/dns/gravity/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "data": "txtTXTtxt", - "hostname": "_acme-challenge", - "type": "TXT", - "uid": "123" -} diff --git a/providers/dns/gravity/internal/fixtures/error.json b/providers/dns/gravity/internal/fixtures/error.json deleted file mode 100644 index 38b78fcca..000000000 --- a/providers/dns/gravity/internal/fixtures/error.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "code": 0, - "context": { - "additionalProp1": "string" - }, - "error": "unauthenticated", - "status": "UNAUTHENTICATED" -} diff --git a/providers/dns/gravity/internal/fixtures/login-request.json b/providers/dns/gravity/internal/fixtures/login-request.json deleted file mode 100644 index c641cd3e5..000000000 --- a/providers/dns/gravity/internal/fixtures/login-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "username": "user", - "password": "secret" -} diff --git a/providers/dns/gravity/internal/fixtures/login.json b/providers/dns/gravity/internal/fixtures/login.json deleted file mode 100644 index b9ae7145f..000000000 --- a/providers/dns/gravity/internal/fixtures/login.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "successful": true -} diff --git a/providers/dns/gravity/internal/fixtures/me.json b/providers/dns/gravity/internal/fixtures/me.json deleted file mode 100644 index 881a2ca5f..000000000 --- a/providers/dns/gravity/internal/fixtures/me.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "username": "admin", - "authenticated": true, - "permissions": [ - { - "path": "/*", - "methods": [ - "GET", - "POST", - "PUT", - "HEAD", - "DELETE" - ] - } - ] -} diff --git a/providers/dns/gravity/internal/fixtures/me_unauthenticated.json b/providers/dns/gravity/internal/fixtures/me_unauthenticated.json deleted file mode 100644 index 67698b8e2..000000000 --- a/providers/dns/gravity/internal/fixtures/me_unauthenticated.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "username": "", - "authenticated": false, - "permissions": null -} diff --git a/providers/dns/gravity/internal/fixtures/zones.json b/providers/dns/gravity/internal/fixtures/zones.json deleted file mode 100644 index 53a8df6c1..000000000 --- a/providers/dns/gravity/internal/fixtures/zones.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "zones": [ - { - "name": "example.com.", - "handlerConfigs": [ - { - "type": "memory" - }, - { - "type": "etcd" - } - ], - "defaultTTL": 86400, - "authoritative": false, - "hook": "", - "recordCount": 1 - } - ] -} diff --git a/providers/dns/gravity/internal/fixtures/zones_empty.json b/providers/dns/gravity/internal/fixtures/zones_empty.json deleted file mode 100644 index d8b70b45e..000000000 --- a/providers/dns/gravity/internal/fixtures/zones_empty.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zones": null -} diff --git a/providers/dns/gravity/internal/types.go b/providers/dns/gravity/internal/types.go deleted file mode 100644 index 872bc070f..000000000 --- a/providers/dns/gravity/internal/types.go +++ /dev/null @@ -1,82 +0,0 @@ -package internal - -import ( - "fmt" - "strings" -) - -type APIError struct { - Status string `json:"status"` - ErrorMsg string `json:"error"` - Code int `json:"code"` - Context map[string]string `json:"context"` -} - -func (a *APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "status: %s, error: %s", a.Status, a.ErrorMsg) - - if a.Code != 0 { - _, _ = fmt.Fprintf(msg, ", code: %d", a.Code) - } - - if len(a.Context) != 0 { - for k, v := range a.Context { - _, _ = fmt.Fprintf(msg, ", %s: %s", k, v) - } - } - - return msg.String() -} - -type Login struct { - Username string `json:"username"` - Password string `json:"password"` -} - -type Auth struct { - Successful bool `json:"successful"` -} - -type UserInfo struct { - Username string `json:"username"` - Authenticated bool `json:"authenticated"` - Permissions []Permission `json:"permissions"` -} - -type Permission struct { - Methods []string `json:"methods"` - Path string `json:"path"` -} - -type Zones struct { - Zones []Zone `json:"zones"` -} - -type Zone struct { - Name string `json:"name"` - HandlerConfigs []HandlerConfig `json:"handlerConfigs"` - DefaultTTL int `json:"defaultTTL"` - Authoritative bool `json:"authoritative"` - Hook string `json:"hook"` - RecordCount int `json:"recordCount"` -} - -type HandlerConfig struct { - Type string `json:"type"` - CacheTTL int `json:"cache_ttl,omitempty"` - To []string `json:"to,omitempty"` -} - -type Record struct { - Data string `json:"data,omitempty"` - Fqdn string `json:"fqdn,omitempty"` - Hostname string `json:"hostname,omitempty"` - MxPreference int `json:"mxPreference,omitempty"` - SrvPort int `json:"srvPort,omitempty"` - SrvPriority int `json:"srvPriority,omitempty"` - SrvWeight int `json:"srvWeight,omitempty"` - Type string `json:"type,omitempty"` - UID string `json:"uid,omitempty"` -} diff --git a/providers/dns/hetzner/hetzner.go b/providers/dns/hetzner/hetzner.go index bae985b3e..1b02590d6 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -4,6 +4,7 @@ package hetzner import ( "errors" "net/http" + "os" "time" "github.com/go-acme/lego/v4/challenge" @@ -61,9 +62,10 @@ type DNSProvider struct { } // NewDNSProvider returns a DNSProvider instance configured for hetzner. +// Credentials must be passed in the environment variable: HETZNER_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - foundAPIToken := env.GetOrFile(EnvAPIToken) != "" - foundAPIKey := env.GetOrFile(EnvAPIKey) != "" + _, foundAPIToken := os.LookupEnv(EnvAPIToken) + _, foundAPIKey := os.LookupEnv(EnvAPIKey) switch { case foundAPIToken: diff --git a/providers/dns/hetzner/hetzner.toml b/providers/dns/hetzner/hetzner.toml index 40d4cea72..ee1f9a970 100644 --- a/providers/dns/hetzner/hetzner.toml +++ b/providers/dns/hetzner/hetzner.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hetzner -d '*.example.com' -d example.com run +lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go index b31c766ce..4fb95eb6f 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -184,7 +184,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) waitAction(ctx context.Context, actionID int64) error { +func (d *DNSProvider) waitAction(ctx context.Context, actionID int) error { return wait.Retry(ctx, func() error { result, err := d.client.GetAction(ctx, actionID) diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client.go b/providers/dns/hetzner/internal/hetznerv1/internal/client.go index 2f29f642a..35c3d461b 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client.go @@ -85,8 +85,8 @@ func (c *Client) RemoveRRSetRecords(ctx context.Context, zoneIDName, recordType, // GetAction gets an action. // https://docs.hetzner.cloud/reference/cloud#actions-get-an-action -func (c *Client) GetAction(ctx context.Context, id int64) (*Action, error) { - endpoint := c.BaseURL.JoinPath("actions", strconv.FormatInt(id, 10)) +func (c *Client) GetAction(ctx context.Context, id int) (*Action, error) { + endpoint := c.BaseURL.JoinPath("actions", strconv.Itoa(id)) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go index 6fd3d77a7..fcbc7636f 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go @@ -49,7 +49,7 @@ func TestClient_AddRRSetRecords(t *testing.T) { Command: "add_rrset_records", Status: "running", Progress: 50, - Resources: []Resources{{ID: 590000000000000, Type: "zone"}}, + Resources: []Resources{{ID: 42, Type: "zone"}}, } assert.Equal(t, expected, result) @@ -139,11 +139,11 @@ func TestClient_GetAction(t *testing.T) { require.NoError(t, err) expected := &Action{ - ID: 590000000000000, + ID: 42, Command: "start_resource", Status: "running", Progress: 100, - Resources: []Resources{{ID: 590000000000000, Type: "server"}}, + Resources: []Resources{{ID: 42, Type: "server"}}, ErrorInfo: &ErrorInfo{ Code: "action_failed", Message: "Action failed", diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json index 7267b02cb..2341c7e6e 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json @@ -8,7 +8,7 @@ "finished": null, "resources": [ { - "id": 590000000000000, + "id": 42, "type": "zone" } ], diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json index 19278fc51..05f003b1e 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json @@ -1,6 +1,6 @@ { "action": { - "id": 590000000000000, + "id": 42, "command": "start_resource", "status": "running", "started": "2016-01-30T23:55:00+00:00", @@ -8,7 +8,7 @@ "progress": 100, "resources": [ { - "id": 590000000000000, + "id": 42, "type": "server" } ], diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go index 2b38a8a8c..08d1684c0 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() @@ -79,7 +79,7 @@ type ActionResponse struct { } type Action struct { - ID int64 `json:"id,omitempty"` + ID int `json:"id,omitempty"` Command string `json:"command,omitempty"` // It can be: `running`, `success`, `error`. @@ -93,6 +93,6 @@ type Action struct { } type Resources struct { - ID int64 `json:"id,omitempty"` + ID int `json:"id,omitempty"` Type string `json:"type,omitempty"` } diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 1e022b630..48c44998f 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -2,14 +2,17 @@ package hostingde import ( + "context" "errors" "fmt" "net/http" + "sync" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -29,7 +32,14 @@ const ( var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = hostingde.Config +type Config struct { + APIKey string + ZoneName string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -46,7 +56,11 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *hostingde.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex } // NewDNSProvider returns a DNSProvider instance configured for hosting.de. @@ -70,27 +84,130 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hostingde: the configuration of the DNS provider is nil") } - provider, err := hostingde.NewDNSProviderConfig(config, "") - if err != nil { - return nil, fmt.Errorf("hostingde: %w", err) + if config.APIKey == "" { + return nil, errors.New("hostingde: API key missing") } - return &DNSProvider{prv: provider}, nil + client := hostingde.NewClient(config.APIKey) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil } -// Present creates a TXT record using the specified parameters. +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostingde: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := hostingde.ZoneConfigsFindRequest{ + Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) if err != nil { return fmt.Errorf("hostingde: %w", err) } + zoneConfig.Name = zoneName + + rec := []hostingde.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: info.Value, + TTL: d.config.TTL, + }} + + req := hostingde.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToAdd: rec, + } + + response, err := d.client.UpdateZone(ctx, req) + if err != nil { + return fmt.Errorf("hostingde: %w", err) + } + + for _, record := range response.Records { + if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { + d.recordIDsMu.Lock() + d.recordIDs[info.EffectiveFQDN] = record.ID + d.recordIDsMu.Unlock() + } + } + + if d.recordIDs[info.EffectiveFQDN] == "" { + return fmt.Errorf("hostingde: error getting ID of just created record, for domain %s", domain) + } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostingde: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := hostingde.ZoneConfigsFindRequest{ + Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) + if err != nil { + return fmt.Errorf("hostingde: %w", err) + } + + zoneConfig.Name = zoneName + + rec := []hostingde.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: `"` + info.Value + `"`, + }} + + req := hostingde.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToDelete: rec, + } + + // Delete record ID from map + d.recordIDsMu.Lock() + delete(d.recordIDs, info.EffectiveFQDN) + d.recordIDsMu.Unlock() + + _, err = d.client.UpdateZone(ctx, req) if err != nil { return fmt.Errorf("hostingde: %w", err) } @@ -98,8 +215,19 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() +func (d *DNSProvider) getZoneName(fqdn string) (string, error) { + if d.config.ZoneName != "" { + return d.config.ZoneName, nil + } + + zoneName, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) + } + + if zoneName == "" { + return "", errors.New("empty zone name") + } + + return dns01.UnFqdn(zoneName), nil } diff --git a/providers/dns/hostingde/hostingde.toml b/providers/dns/hostingde/hostingde.toml index 502a7fe9e..569e8a781 100644 --- a/providers/dns/hostingde/hostingde.toml +++ b/providers/dns/hostingde/hostingde.toml @@ -6,7 +6,7 @@ Since = "v1.1.0" Example = ''' HOSTINGDE_API_KEY=xxxxxxxx \ -lego --dns hostingde -d '*.example.com' -d example.com run +lego --email you@example.com --dns hostingde -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hostingde/hostingde_test.go b/providers/dns/hostingde/hostingde_test.go index a92006f81..1611cb51b 100644 --- a/providers/dns/hostingde/hostingde_test.go +++ b/providers/dns/hostingde/hostingde_test.go @@ -59,7 +59,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.recordIDs) } else { require.EqualError(t, err, test.expected) } @@ -101,7 +102,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.recordIDs) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/hostinger/hostinger.toml b/providers/dns/hostinger/hostinger.toml index a6f152e73..f49e447ed 100644 --- a/providers/dns/hostinger/hostinger.toml +++ b/providers/dns/hostinger/hostinger.toml @@ -6,7 +6,7 @@ Since = "v4.27.0" Example = ''' HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostinger -d '*.example.com' -d example.com run +lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run ''' [Configuration] 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/hostingnl/hostingnl.go b/providers/dns/hostingnl/hostingnl.go deleted file mode 100644 index a49941817..000000000 --- a/providers/dns/hostingnl/hostingnl.go +++ /dev/null @@ -1,168 +0,0 @@ -// Package hostingnl implements a DNS provider for solving the DNS-01 challenge using hosting.nl. -package hostingnl - -import ( - "context" - "errors" - "fmt" - "net/http" - "strconv" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/hostingnl/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "HOSTINGNL_" - - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - HTTPClient *http.Client - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for hosting.nl. -// Credentials must be passed in the environment variables: -// HOSTINGNL_APIKEY. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("hostingnl: %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for hosting.nl. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("hostingnl: the configuration of the DNS provider is nil") - } - - if config.APIKey == "" { - return nil, errors.New("hostingnl: APIKey is missing") - } - - client := internal.NewClient(config.APIKey) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hostingnl: could not find zone for domain %q: %w", domain, err) - } - - record := internal.Record{ - Name: dns01.UnFqdn(info.EffectiveFQDN), - Type: "TXT", - Content: strconv.Quote(info.Value), - TTL: d.config.TTL, - Priority: 0, - } - - newRecord, err := d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) - if err != nil { - return fmt.Errorf("hostingnl: failed to create TXT record, fqdn=%s: %w", info.EffectiveFQDN, err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = newRecord.ID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT records matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hostingnl: could not find zone for domain %q: %w", domain, err) - } - - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("hostingnl: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("hostingnl: failed to delete TXT record, id=%s: %w", recordID, err) - } - - // deletes record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} diff --git a/providers/dns/hostingnl/hostingnl.toml b/providers/dns/hostingnl/hostingnl.toml deleted file mode 100644 index 943264ed3..000000000 --- a/providers/dns/hostingnl/hostingnl.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Hosting.nl" -Description = '''''' -URL = "https://hosting.nl" -Code = "hostingnl" -Since = "v4.30.0" - -Example = ''' -HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostingnl -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - HOSTINGNL_API_KEY = "The API key" - [Configuration.Additional] - HOSTINGNL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - HOSTINGNL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" - HOSTINGNL_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - HOSTINGNL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" - -[Links] - API = "https://api.hosting.nl/api/documentation" diff --git a/providers/dns/hostingnl/hostingnl_test.go b/providers/dns/hostingnl/hostingnl_test.go deleted file mode 100644 index cef754c7c..000000000 --- a/providers/dns/hostingnl/hostingnl_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package hostingnl - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIKey: "key", - }, - }, - { - desc: "missing API key", - envVars: map[string]string{}, - expected: "hostingnl: some credentials information are missing: HOSTINGNL_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - expected string - }{ - { - desc: "success", - apiKey: "key", - }, - { - desc: "missing API key", - expected: "hostingnl: APIKey is missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIKey = "secret" - config.HTTPClient = server.Client() - - provider, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - provider.client.BaseURL, _ = url.Parse(server.URL) - - return provider, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - With("API-TOKEN", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /domains/example.com/dns", - servermock.ResponseFromInternal("add_record.json"), - servermock.CheckQueryParameter().Strict(), - servermock.CheckRequestJSONBodyFromInternal("add_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /domains/example.com/dns", - servermock.ResponseFromInternal("delete_record.json"), - servermock.CheckQueryParameter().Strict(), - servermock.CheckRequestJSONBodyFromInternal("delete_record-request.json")). - Build(t) - - provider.recordIDs["abc"] = "12345" - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/hostingnl/internal/client.go b/providers/dns/hostingnl/internal/client.go deleted file mode 100644 index f2d7b5346..000000000 --- a/providers/dns/hostingnl/internal/client.go +++ /dev/null @@ -1,144 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "github.com/go-acme/lego/v4/providers/dns/internal/useragent" -) - -const defaultBaseURL = "https://api.hosting.nl" - -type Client struct { - apiKey string - - BaseURL *url.URL - HTTPClient *http.Client -} - -func NewClient(apiKey string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiKey: apiKey, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 5 * time.Second}, - } -} - -func (c Client) AddRecord(ctx context.Context, domain string, record Record) (*Record, error) { - endpoint := c.BaseURL.JoinPath("domains", domain, "dns") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, []Record{record}) - if err != nil { - return nil, err - } - - var result APIResponse[Record] - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - if len(result.Data) != 1 { - return nil, fmt.Errorf("unexpected response data: %v", result.Data) - } - - return &result.Data[0], nil -} - -func (c Client) DeleteRecord(ctx context.Context, domain, recordID string) error { - endpoint := c.BaseURL.JoinPath("domains", domain, "dns") - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, []Record{{ID: recordID}}) - if err != nil { - return err - } - - var result APIResponse[Record] - - err = c.do(req, &result) - if err != nil { - return err - } - - return nil -} - -func (c Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Set("API-TOKEN", c.apiKey) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusOK { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var apiErr APIError - - err := json.Unmarshal(raw, &apiErr) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return fmt.Errorf("[status code: %d] %w", resp.StatusCode, apiErr) -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} diff --git a/providers/dns/hostingnl/internal/client_test.go b/providers/dns/hostingnl/internal/client_test.go deleted file mode 100644 index efdb98980..000000000 --- a/providers/dns/hostingnl/internal/client_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package internal - -import ( - "context" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder( - func(server *httptest.Server) (*Client, error) { - client := NewClient("secret") - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - With("API-TOKEN", "secret"), - ) -} - -func TestClient_AddRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/example.com/dns", - servermock.ResponseFromFixture("add_record.json"), - servermock.CheckQueryParameter().Strict(), - servermock.CheckRequestJSONBodyFromFixture("add_record-request.json")). - Build(t) - - record := Record{ - Name: "_acme-challenge.example.com", - Type: "TXT", - Content: strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), - TTL: 120, - } - - newRecord, err := client.AddRecord(context.Background(), "example.com", record) - require.NoError(t, err) - - expected := &Record{ - ID: "12345", - Name: "_acme-challenge.example.com", - Type: "TXT", - Content: strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), - TTL: 120, - } - - assert.Equal(t, expected, newRecord) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/dns", - servermock.ResponseFromFixture("delete_record.json"), - servermock.CheckQueryParameter().Strict(), - servermock.CheckRequestJSONBodyFromFixture("delete_record-request.json")). - Build(t) - - err := client.DeleteRecord(context.Background(), "example.com", "12345") - require.NoError(t, err) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/dns", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - err := client.DeleteRecord(context.Background(), "example.com", "12345") - require.EqualError(t, err, "[status code: 401] Something went wrong") -} - -func TestClient_DeleteRecord_error_other(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/dns", - servermock.ResponseFromFixture("error_other.json"). - WithStatusCode(http.StatusNotFound)). - Build(t) - - err := client.DeleteRecord(context.Background(), "example.com", "12345") - require.EqualError(t, err, "[status code: 404] Resource not found") -} diff --git a/providers/dns/hostingnl/internal/fixtures/add_record-request.json b/providers/dns/hostingnl/internal/fixtures/add_record-request.json deleted file mode 100644 index 6b68ec3c6..000000000 --- a/providers/dns/hostingnl/internal/fixtures/add_record-request.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "name": "_acme-challenge.example.com", - "type": "TXT", - "content": "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"", - "ttl": 120 - } -] diff --git a/providers/dns/hostingnl/internal/fixtures/add_record.json b/providers/dns/hostingnl/internal/fixtures/add_record.json deleted file mode 100644 index a822a4f8d..000000000 --- a/providers/dns/hostingnl/internal/fixtures/add_record.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "success": true, - "data": [ - { - "id": "12345", - "type": "TXT", - "content": "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"", - "name": "_acme-challenge.example.com", - "prio": 0, - "ttl": 120 - } - ] -} diff --git a/providers/dns/hostingnl/internal/fixtures/delete_record-request.json b/providers/dns/hostingnl/internal/fixtures/delete_record-request.json deleted file mode 100644 index cfc26d2b9..000000000 --- a/providers/dns/hostingnl/internal/fixtures/delete_record-request.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - { - "id": "12345" - } -] diff --git a/providers/dns/hostingnl/internal/fixtures/delete_record.json b/providers/dns/hostingnl/internal/fixtures/delete_record.json deleted file mode 100644 index c041c1f6d..000000000 --- a/providers/dns/hostingnl/internal/fixtures/delete_record.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "success": true, - "data": [ - { - "id": "12345" - } - ] -} diff --git a/providers/dns/hostingnl/internal/fixtures/error.json b/providers/dns/hostingnl/internal/fixtures/error.json deleted file mode 100644 index 170587246..000000000 --- a/providers/dns/hostingnl/internal/fixtures/error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "errors": { - "message": "Something went wrong" - } -} diff --git a/providers/dns/hostingnl/internal/fixtures/error_other.json b/providers/dns/hostingnl/internal/fixtures/error_other.json deleted file mode 100644 index ca7ecab28..000000000 --- a/providers/dns/hostingnl/internal/fixtures/error_other.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "error": "Resource not found" -} diff --git a/providers/dns/hostingnl/internal/types.go b/providers/dns/hostingnl/internal/types.go deleted file mode 100644 index f324665fe..000000000 --- a/providers/dns/hostingnl/internal/types.go +++ /dev/null @@ -1,36 +0,0 @@ -package internal - -type Record struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Content string `json:"content,omitempty"` - TTL int `json:"ttl,omitempty"` - Priority int `json:"prio,omitempty"` -} - -type APIResponse[T any] struct { - Success bool `json:"success"` - Data []T `json:"data"` -} - -type APIError struct { - ErrorMsg string `json:"error"` - Errors Error `json:"errors"` -} - -func (e APIError) Error() string { - if e.ErrorMsg != "" { - return e.ErrorMsg - } - - return e.Errors.Error() -} - -type Error struct { - Message string `json:"message"` -} - -func (e Error) Error() string { - return e.Message -} diff --git a/providers/dns/hosttech/hosttech.go b/providers/dns/hosttech/hosttech.go index 73346f6cb..fac64f054 100644 --- a/providers/dns/hosttech/hosttech.go +++ b/providers/dns/hosttech/hosttech.go @@ -174,9 +174,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("hosttech: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/hosttech/hosttech.toml b/providers/dns/hosttech/hosttech.toml index 52c01fd31..5d7555499 100644 --- a/providers/dns/hosttech/hosttech.toml +++ b/providers/dns/hosttech/hosttech.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' HOSTTECH_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns hosttech -d '*.example.com' -d example.com run +lego --email you@example.com --dns hosttech -d '*.example.com' -d example.com run ''' [Configuration] 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/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index 4a88f1092..f18eefd97 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -2,14 +2,18 @@ package httpnet import ( + "context" "errors" "fmt" "net/http" + "net/url" + "sync" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -26,12 +30,17 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const defaultBaseURL = "https://partner.http.net/api/dns/v1/json" - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = hostingde.Config +type Config struct { + APIKey string + ZoneName string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -48,7 +57,11 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *hostingde.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex } // NewDNSProvider returns a DNSProvider instance configured for http.net. @@ -72,27 +85,131 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("httpnet: the configuration of the DNS provider is nil") } - provider, err := hostingde.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("httpnet: %w", err) + if config.APIKey == "" { + return nil, errors.New("httpnet: API key missing") } - return &DNSProvider{prv: provider}, nil + client := hostingde.NewClient(config.APIKey) + client.BaseURL, _ = url.Parse(hostingde.DefaultHTTPNetBaseURL) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil } -// Present creates a TXT record using the specified parameters. +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("httpnet: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := hostingde.ZoneConfigsFindRequest{ + Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) if err != nil { return fmt.Errorf("httpnet: %w", err) } + zoneConfig.Name = zoneName + + rec := []hostingde.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: info.Value, + TTL: d.config.TTL, + }} + + req := hostingde.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToAdd: rec, + } + + response, err := d.client.UpdateZone(ctx, req) + if err != nil { + return fmt.Errorf("httpnet: %w", err) + } + + for _, record := range response.Records { + if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { + d.recordIDsMu.Lock() + d.recordIDs[info.EffectiveFQDN] = record.ID + d.recordIDsMu.Unlock() + } + } + + if d.recordIDs[info.EffectiveFQDN] == "" { + return fmt.Errorf("httpnet: error getting ID of just created record, for domain %s", domain) + } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("httpnet: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := hostingde.ZoneConfigsFindRequest{ + Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) + if err != nil { + return fmt.Errorf("httpnet: %w", err) + } + + zoneConfig.Name = zoneName + + rec := []hostingde.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: `"` + info.Value + `"`, + }} + + req := hostingde.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToDelete: rec, + } + + // Delete record ID from map + d.recordIDsMu.Lock() + delete(d.recordIDs, info.EffectiveFQDN) + d.recordIDsMu.Unlock() + + _, err = d.client.UpdateZone(ctx, req) if err != nil { return fmt.Errorf("httpnet: %w", err) } @@ -100,8 +217,19 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() +func (d *DNSProvider) getZoneName(fqdn string) (string, error) { + if d.config.ZoneName != "" { + return d.config.ZoneName, nil + } + + zoneName, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) + } + + if zoneName == "" { + return "", errors.New("empty zone name") + } + + return dns01.UnFqdn(zoneName), nil } diff --git a/providers/dns/httpnet/httpnet.toml b/providers/dns/httpnet/httpnet.toml index 3dd581204..204f5bc54 100644 --- a/providers/dns/httpnet/httpnet.toml +++ b/providers/dns/httpnet/httpnet.toml @@ -6,7 +6,7 @@ Since = "v4.15.0" Example = ''' HTTPNET_API_KEY=xxxxxxxx \ -lego --dns httpnet -d '*.example.com' -d example.com run +lego --email you@example.com --dns httpnet -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/httpnet/httpnet_test.go b/providers/dns/httpnet/httpnet_test.go index ef1d2a1b7..64a94f80c 100644 --- a/providers/dns/httpnet/httpnet_test.go +++ b/providers/dns/httpnet/httpnet_test.go @@ -59,7 +59,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.recordIDs) } else { require.EqualError(t, err, test.expected) } @@ -101,7 +102,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.recordIDs) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/httpreq/httpreq.toml b/providers/dns/httpreq/httpreq.toml index d64d61a6c..6c3f8719b 100644 --- a/providers/dns/httpreq/httpreq.toml +++ b/providers/dns/httpreq/httpreq.toml @@ -6,7 +6,7 @@ Since = "v2.0.0" Example = ''' HTTPREQ_ENDPOINT=http://my.server.com:9090 \ -lego --dns httpreq -d '*.example.com' -d example.com run +lego --email you@example.com --dns httpreq -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index e47f3e2b5..5a2773ab2 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -209,10 +209,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("huaweicloud: delete record: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/huaweicloud/huaweicloud.toml b/providers/dns/huaweicloud/huaweicloud.toml index e8d417c11..f7991dfae 100644 --- a/providers/dns/huaweicloud/huaweicloud.toml +++ b/providers/dns/huaweicloud/huaweicloud.toml @@ -8,7 +8,7 @@ Example = ''' HUAWEICLOUD_ACCESS_KEY_ID=your-access-key-id \ HUAWEICLOUD_SECRET_ACCESS_KEY=your-secret-access-key \ HUAWEICLOUD_REGION=cn-south-1 \ -lego --dns huaweicloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns huaweicloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hurricane/hurricane.toml b/providers/dns/hurricane/hurricane.toml index 10b370e4f..033c73984 100644 --- a/providers/dns/hurricane/hurricane.toml +++ b/providers/dns/hurricane/hurricane.toml @@ -6,10 +6,10 @@ Since = "v4.3.0" Example = ''' HURRICANE_TOKENS=example.org:token \ -lego --dns hurricane -d '*.example.com' -d example.com run +lego --email you@example.com --dns hurricane -d '*.example.com' -d example.com run HURRICANE_TOKENS=my.example.org:token1,demo.example.org:token2 \ -lego --dns hurricane -d my.example.org -d demo.example.org +lego --email you@example.com --dns hurricane -d my.example.org -d demo.example.org ''' Additional = """ diff --git a/providers/dns/hyperone/hyperone.toml b/providers/dns/hyperone/hyperone.toml index 88814356f..0f23976c4 100644 --- a/providers/dns/hyperone/hyperone.toml +++ b/providers/dns/hyperone/hyperone.toml @@ -5,7 +5,7 @@ Code = "hyperone" Since = "v3.9.0" Example = ''' -lego --dns hyperone -d '*.example.com' -d example.com run +lego --email you@example.com --dns hyperone -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/ibmcloud/ibmcloud.toml b/providers/dns/ibmcloud/ibmcloud.toml index 01088f09b..2a6c12f82 100644 --- a/providers/dns/ibmcloud/ibmcloud.toml +++ b/providers/dns/ibmcloud/ibmcloud.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' SOFTLAYER_USERNAME=xxxxx \ SOFTLAYER_API_KEY=yyyyy \ -lego --dns ibmcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/iij/iij.toml b/providers/dns/iij/iij.toml index 95355200a..8dbf5ba1a 100644 --- a/providers/dns/iij/iij.toml +++ b/providers/dns/iij/iij.toml @@ -8,7 +8,7 @@ Example = ''' IIJ_API_ACCESS_KEY=xxxxxxxx \ IIJ_API_SECRET_KEY=yyyyyy \ IIJ_DO_SERVICE_CODE=zzzzzz \ -lego --dns iij -d '*.example.com' -d example.com run +lego --email you@example.com --dns iij -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/iijdpf/iijdpf.toml b/providers/dns/iijdpf/iijdpf.toml index 650285f95..4aaa9ca37 100644 --- a/providers/dns/iijdpf/iijdpf.toml +++ b/providers/dns/iijdpf/iijdpf.toml @@ -7,7 +7,7 @@ Since = "v4.7.0" Example = ''' IIJ_DPF_API_TOKEN=xxxxxxxx \ IIJ_DPF_DPM_SERVICE_CODE=yyyyyy \ -lego --dns iijdpf -d '*.example.com' -d example.com run +lego --email you@example.com --dns iijdpf -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/infoblox/infoblox.toml b/providers/dns/infoblox/infoblox.toml index 0e6972d3a..3c2632042 100644 --- a/providers/dns/infoblox/infoblox.toml +++ b/providers/dns/infoblox/infoblox.toml @@ -8,7 +8,7 @@ Example = ''' INFOBLOX_USERNAME=api-user-529 \ INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ INFOBLOX_HOST=infoblox.example.org -lego --dns infoblox -d '*.example.com' -d example.com run +lego --email you@example.com --dns infoblox -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/infomaniak/infomaniak.toml b/providers/dns/infomaniak/infomaniak.toml index d924e3a26..283838053 100644 --- a/providers/dns/infomaniak/infomaniak.toml +++ b/providers/dns/infomaniak/infomaniak.toml @@ -6,7 +6,7 @@ Since = "v4.1.0" Example = ''' INFOMANIAK_ACCESS_TOKEN=1234567898765432 \ -lego --dns infomaniak -d '*.example.com' -d example.com run +lego --email you@example.com --dns infomaniak -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/internal/active24/internal/client.go b/providers/dns/internal/active24/client.go similarity index 99% rename from providers/dns/internal/active24/internal/client.go rename to providers/dns/internal/active24/client.go index 69e94b367..10aaa4666 100644 --- a/providers/dns/internal/active24/internal/client.go +++ b/providers/dns/internal/active24/client.go @@ -1,4 +1,4 @@ -package internal +package active24 import ( "bytes" diff --git a/providers/dns/internal/active24/internal/client_test.go b/providers/dns/internal/active24/client_test.go similarity index 99% rename from providers/dns/internal/active24/internal/client_test.go rename to providers/dns/internal/active24/client_test.go index f62f78785..ad2a8126b 100644 --- a/providers/dns/internal/active24/internal/client_test.go +++ b/providers/dns/internal/active24/client_test.go @@ -1,4 +1,4 @@ -package internal +package active24 import ( "net/http" diff --git a/providers/dns/internal/active24/internal/fixtures/error_403.json b/providers/dns/internal/active24/fixtures/error_403.json similarity index 100% rename from providers/dns/internal/active24/internal/fixtures/error_403.json rename to providers/dns/internal/active24/fixtures/error_403.json diff --git a/providers/dns/internal/active24/internal/fixtures/error_422.json b/providers/dns/internal/active24/fixtures/error_422.json similarity index 100% rename from providers/dns/internal/active24/internal/fixtures/error_422.json rename to providers/dns/internal/active24/fixtures/error_422.json diff --git a/providers/dns/internal/active24/internal/fixtures/error_v1.json b/providers/dns/internal/active24/fixtures/error_v1.json similarity index 100% rename from providers/dns/internal/active24/internal/fixtures/error_v1.json rename to providers/dns/internal/active24/fixtures/error_v1.json diff --git a/providers/dns/internal/active24/internal/fixtures/records.json b/providers/dns/internal/active24/fixtures/records.json similarity index 100% rename from providers/dns/internal/active24/internal/fixtures/records.json rename to providers/dns/internal/active24/fixtures/records.json diff --git a/providers/dns/internal/active24/internal/fixtures/services.json b/providers/dns/internal/active24/fixtures/services.json similarity index 100% rename from providers/dns/internal/active24/internal/fixtures/services.json rename to providers/dns/internal/active24/fixtures/services.json diff --git a/providers/dns/internal/active24/provider.go b/providers/dns/internal/active24/provider.go deleted file mode 100644 index ae79b8b17..000000000 --- a/providers/dns/internal/active24/provider.go +++ /dev/null @@ -1,179 +0,0 @@ -// Package active24 implements a DNS provider for solving the DNS-01 challenge using Active24. -package active24 - -import ( - "context" - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/active24/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - Secret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Active24. -func NewDNSProviderConfig(config *Config, baseAPIDomain string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(baseAPIDomain, config.APIKey, config.Secret) - if err != nil { - return nil, err - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return err - } - - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("find service ID: %w", err) - } - - record := internal.Record{ - Type: "TXT", - Name: subDomain, - Content: info.Value, - TTL: d.config.TTL, - } - - err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) - if err != nil { - return fmt.Errorf("create record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("find service ID: %w", err) - } - - recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) - if err != nil { - return fmt.Errorf("find record ID: %w", err) - } - - err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) - if err != nil { - return fmt.Errorf("delete record %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { - services, err := d.client.GetServices(ctx) - if err != nil { - return 0, fmt.Errorf("get services: %w", err) - } - - for _, service := range services { - if service.ServiceName != "domain" { - continue - } - - if service.Name != domain { - continue - } - - return service.ID, nil - } - - return 0, fmt.Errorf("service not found for domain: %s", domain) -} - -func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { - // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. - filter := internal.RecordFilter{ - Name: dns01.UnFqdn(info.EffectiveFQDN), - Type: []string{"TXT"}, - Content: info.Value, - } - - records, err := d.client.GetRecords(ctx, serviceID, filter) - if err != nil { - return 0, fmt.Errorf("get records: %w", err) - } - - for _, record := range records { - if record.Type != "TXT" { - continue - } - - if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { - continue - } - - if record.Content != info.Value { - continue - } - - return record.ID, nil - } - - return 0, errors.New("no record found") -} diff --git a/providers/dns/internal/active24/provider_test.go b/providers/dns/internal/active24/provider_test.go deleted file mode 100644 index e2959fd6e..000000000 --- a/providers/dns/internal/active24/provider_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package active24 - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - secret string - expected string - }{ - { - desc: "success", - apiKey: "user", - secret: "secret", - }, - { - desc: "missing API key", - apiKey: "", - secret: "secret", - expected: "credentials missing", - }, - { - desc: "missing secret", - apiKey: "user", - secret: "", - expected: "credentials missing", - }, - { - desc: "missing credentials", - expected: "credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.APIKey = test.apiKey - config.Secret = test.secret - - p, err := NewDNSProviderConfig(config, "example.com") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/active24/internal/types.go b/providers/dns/internal/active24/types.go similarity index 99% rename from providers/dns/internal/active24/internal/types.go rename to providers/dns/internal/active24/types.go index ed8dfc9d3..b9a7ea427 100644 --- a/providers/dns/internal/active24/internal/types.go +++ b/providers/dns/internal/active24/types.go @@ -1,4 +1,4 @@ -package internal +package active24 import "fmt" diff --git a/providers/dns/internal/clientdebug/client.go b/providers/dns/internal/clientdebug/client.go index 342577b93..ad2a06405 100644 --- a/providers/dns/internal/clientdebug/client.go +++ b/providers/dns/internal/clientdebug/client.go @@ -91,9 +91,6 @@ func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) { _, _ = fmt.Fprintln(d.writer, d.redact(data)) resp, err := d.rt.RoundTrip(h) - if err != nil { - return nil, err - } data, _ = httputil.DumpResponse(resp, true) diff --git a/providers/dns/internal/gcore/provider.go b/providers/dns/internal/gcore/provider.go deleted file mode 100644 index b2078eba5..000000000 --- a/providers/dns/internal/gcore/provider.go +++ /dev/null @@ -1,126 +0,0 @@ -// Package gcore implements a DNS provider for solving the DNS-01 challenge using G-Core. -package gcore - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/gcore/internal" -) - -const ( - DefaultPropagationTimeout = 360 * time.Second - DefaultPollingInterval = 20 * time.Second -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config for DNSProvider. -type Config struct { - APIToken string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider an implementation of challenge.Provider contract. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProviderConfig return a DNSProvider instance configured for G-Core DNS API. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.APIToken == "" { - return nil, errors.New("incomplete credentials provided") - } - - client := internal.NewClient(config.APIToken) - - if baseURL != "" { - client.BaseURL, _ = url.Parse(baseURL) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Present creates a TXT record to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) - if err != nil { - return err - } - - err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) - if err != nil { - return fmt.Errorf("add txt record: %w", err) - } - - return nil -} - -// CleanUp removes the record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) - if err != nil { - return err - } - - err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("remove txt record: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { - var lastErr error - - for zone := range dns01.UnFqdnDomainsSeq(fqdn) { - dnsZone, err := d.client.GetZone(ctx, zone) - if err != nil { - lastErr = err - continue - } - - return dnsZone.Name, nil - } - - return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) -} diff --git a/providers/dns/internal/gcore/provider_test.go b/providers/dns/internal/gcore/provider_test.go deleted file mode 100644 index f29dadff9..000000000 --- a/providers/dns/internal/gcore/provider_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package gcore - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "A", - }, - { - desc: "missing credentials", - expected: "incomplete credentials provided", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/hostingde/internal/client.go b/providers/dns/internal/hostingde/client.go similarity index 94% rename from providers/dns/internal/hostingde/internal/client.go rename to providers/dns/internal/hostingde/client.go index 133c3479c..43354384f 100644 --- a/providers/dns/internal/hostingde/internal/client.go +++ b/providers/dns/internal/hostingde/client.go @@ -1,4 +1,4 @@ -package internal +package hostingde import ( "bytes" @@ -14,7 +14,10 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json" +const ( + DefaultHostingdeBaseURL = "https://secure.hosting.de/api/dns/v1/json" + DefaultHTTPNetBaseURL = "https://partner.http.net/api/dns/v1/json" +) // Client the API client for Hosting.de. type Client struct { @@ -26,7 +29,7 @@ type Client struct { // NewClient creates new Client. func NewClient(apiKey string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) + baseURL, _ := url.Parse(DefaultHostingdeBaseURL) return &Client{ apiKey: apiKey, diff --git a/providers/dns/internal/hostingde/internal/client_test.go b/providers/dns/internal/hostingde/client_test.go similarity index 99% rename from providers/dns/internal/hostingde/internal/client_test.go rename to providers/dns/internal/hostingde/client_test.go index d55bbf690..93e0c76e1 100644 --- a/providers/dns/internal/hostingde/internal/client_test.go +++ b/providers/dns/internal/hostingde/client_test.go @@ -1,4 +1,4 @@ -package internal +package hostingde import ( "encoding/json" diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind-request.json b/providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind-request.json rename to providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind.json b/providers/dns/internal/hostingde/fixtures/zoneConfigsFind.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind.json rename to providers/dns/internal/hostingde/fixtures/zoneConfigsFind.json diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind_error.json b/providers/dns/internal/hostingde/fixtures/zoneConfigsFind_error.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind_error.json rename to providers/dns/internal/hostingde/fixtures/zoneConfigsFind_error.json diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate-request.json b/providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneUpdate-request.json rename to providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate.json b/providers/dns/internal/hostingde/fixtures/zoneUpdate.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneUpdate.json rename to providers/dns/internal/hostingde/fixtures/zoneUpdate.json diff --git a/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate_error.json b/providers/dns/internal/hostingde/fixtures/zoneUpdate_error.json similarity index 100% rename from providers/dns/internal/hostingde/internal/fixtures/zoneUpdate_error.json rename to providers/dns/internal/hostingde/fixtures/zoneUpdate_error.json diff --git a/providers/dns/internal/hostingde/provider.go b/providers/dns/internal/hostingde/provider.go deleted file mode 100644 index b5277f042..000000000 --- a/providers/dns/internal/hostingde/provider.go +++ /dev/null @@ -1,196 +0,0 @@ -// Package hostingde implements a DNS provider for solving the DNS-01 challenge using hosting.de. -package hostingde - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/hostingde/internal" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - ZoneName string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex -} - -// NewDNSProviderConfig return a DNSProvider instance configured for hosting.de. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.APIKey == "" { - return nil, errors.New("API key missing") - } - - client := internal.NewClient(config.APIKey) - - if baseURL != "" { - client.BaseURL, _ = url.Parse(baseURL) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := internal.ZoneConfigsFindRequest{ - Filter: internal.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) - if err != nil { - return err - } - - zoneConfig.Name = zoneName - - rec := []internal.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: info.Value, - TTL: d.config.TTL, - }} - - req := internal.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToAdd: rec, - } - - response, err := d.client.UpdateZone(ctx, req) - if err != nil { - return err - } - - for _, record := range response.Records { - if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { - d.recordIDsMu.Lock() - d.recordIDs[info.EffectiveFQDN] = record.ID - d.recordIDsMu.Unlock() - } - } - - if d.recordIDs[info.EffectiveFQDN] == "" { - return fmt.Errorf("error getting ID of just created record, for domain %s", domain) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := internal.ZoneConfigsFindRequest{ - Filter: internal.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) - if err != nil { - return err - } - - zoneConfig.Name = zoneName - - rec := []internal.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: `"` + info.Value + `"`, - }} - - req := internal.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToDelete: rec, - } - - _, err = d.client.UpdateZone(ctx, req) - if err != nil { - return err - } - - // Delete record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, info.EffectiveFQDN) - d.recordIDsMu.Unlock() - - return nil -} - -func (d *DNSProvider) getZoneName(fqdn string) (string, error) { - if d.config.ZoneName != "" { - return d.config.ZoneName, nil - } - - zoneName, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) - } - - if zoneName == "" { - return "", errors.New("empty zone name") - } - - return dns01.UnFqdn(zoneName), nil -} diff --git a/providers/dns/internal/hostingde/provider_test.go b/providers/dns/internal/hostingde/provider_test.go deleted file mode 100644 index 3cdabf702..000000000 --- a/providers/dns/internal/hostingde/provider_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package hostingde - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - zoneName string - expected string - }{ - { - desc: "success", - apiKey: "123", - zoneName: "example.org", - }, - { - desc: "missing credentials", - expected: "API key missing", - }, - { - desc: "missing api key", - zoneName: "456", - expected: "API key missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.APIKey = test.apiKey - config.ZoneName = test.zoneName - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.recordIDs) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/hostingde/internal/types.go b/providers/dns/internal/hostingde/types.go similarity index 99% rename from providers/dns/internal/hostingde/internal/types.go rename to providers/dns/internal/hostingde/types.go index 330eab27d..86b69ec42 100644 --- a/providers/dns/internal/hostingde/internal/types.go +++ b/providers/dns/internal/hostingde/types.go @@ -1,4 +1,4 @@ -package internal +package hostingde import "encoding/json" diff --git a/providers/dns/internal/ionos/provider.go b/providers/dns/internal/ionos/provider.go deleted file mode 100644 index a7d145840..000000000 --- a/providers/dns/internal/ionos/provider.go +++ /dev/null @@ -1,173 +0,0 @@ -package ionos - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - ionos "github.com/go-acme/lego/v4/providers/dns/internal/ionos/internal" -) - -const MinTTL = 300 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *ionos.Client -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Ionos. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.APIKey == "" { - return nil, errors.New("credentials missing") - } - - if config.TTL < MinTTL { - return nil, fmt.Errorf("invalid TTL, TTL (%d) must be greater than %d", config.TTL, MinTTL) - } - - client, err := ionos.NewClient(config.APIKey) - if err != nil { - return nil, err - } - - if baseURL != "" { - client.BaseURL, _ = url.Parse(baseURL) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) - if err != nil { - return fmt.Errorf("failed to get zones: %w", err) - } - - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("failed to get records (zone=%s): %w", zone.ID, err) - } - - records = append(records, ionos.Record{ - Name: name, - Content: info.Value, - TTL: d.config.TTL, - Type: "TXT", - }) - - err = d.client.ReplaceRecords(ctx, zone.ID, records) - if err != nil { - return fmt.Errorf("failed to create/update records (zone=%s): %w", zone.ID, err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) - if err != nil { - return fmt.Errorf("failed to get zones: %w", err) - } - - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("failed to get records (zone=%s): %w", zone.ID, err) - } - - for _, record := range records { - if record.Name == name && record.Content == strconv.Quote(info.Value) { - err = d.client.RemoveRecord(ctx, zone.ID, record.ID) - if err != nil { - return fmt.Errorf("failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) - } - - return nil - } - } - - return fmt.Errorf("failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) -} - -func findZone(zones []ionos.Zone, domain string) *ionos.Zone { - var result *ionos.Zone - - for _, zone := range zones { - if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { - if result == nil || len(zone.Name) > len(result.Name) { - result = &zone - } - } - } - - return result -} diff --git a/providers/dns/internal/ionos/provider_test.go b/providers/dns/internal/ionos/provider_test.go deleted file mode 100644 index 6b4df5cc7..000000000 --- a/providers/dns/internal/ionos/provider_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package ionos - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - tll int - expected string - }{ - { - desc: "success", - apiKey: "123", - tll: MinTTL, - }, - { - desc: "missing credentials", - tll: MinTTL, - expected: "credentials missing", - }, - { - desc: "invalid TTL", - apiKey: "123", - tll: 30, - expected: "invalid TTL, TTL (30) must be greater than 300", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.APIKey = test.apiKey - config.TTL = test.tll - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/rimuhosting/internal/client.go b/providers/dns/internal/rimuhosting/client.go similarity index 94% rename from providers/dns/internal/rimuhosting/internal/client.go rename to providers/dns/internal/rimuhosting/client.go index 5bf7393e7..c46afc544 100644 --- a/providers/dns/internal/rimuhosting/internal/client.go +++ b/providers/dns/internal/rimuhosting/client.go @@ -1,4 +1,4 @@ -package internal +package rimuhosting import ( "context" @@ -15,7 +15,11 @@ import ( querystring "github.com/google/go-querystring/query" ) -const defaultBaseURL = "https://rimuhosting.com/dns/dyndns.jsp" +// Base URL for the RimuHosting DNS services. +const ( + DefaultZonomiBaseURL = "https://zonomi.com/app/dns/dyndns.jsp" + DefaultRimuHostingBaseURL = "https://rimuhosting.com/dns/dyndns.jsp" +) // Action names. const ( @@ -36,7 +40,7 @@ type Client struct { func NewClient(apiKey string) *Client { return &Client{ apiKey: apiKey, - BaseURL: defaultBaseURL, + BaseURL: DefaultZonomiBaseURL, HTTPClient: &http.Client{Timeout: 5 * time.Second}, } } diff --git a/providers/dns/internal/rimuhosting/internal/client_test.go b/providers/dns/internal/rimuhosting/client_test.go similarity index 99% rename from providers/dns/internal/rimuhosting/internal/client_test.go rename to providers/dns/internal/rimuhosting/client_test.go index 00126dfbe..6ee9ea3f7 100644 --- a/providers/dns/internal/rimuhosting/internal/client_test.go +++ b/providers/dns/internal/rimuhosting/client_test.go @@ -1,4 +1,4 @@ -package internal +package rimuhosting import ( "encoding/xml" diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/add_record.xml b/providers/dns/internal/rimuhosting/fixtures/add_record.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/add_record.xml rename to providers/dns/internal/rimuhosting/fixtures/add_record.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/add_record_error.xml b/providers/dns/internal/rimuhosting/fixtures/add_record_error.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/add_record_error.xml rename to providers/dns/internal/rimuhosting/fixtures/add_record_error.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/add_record_same_domain.xml b/providers/dns/internal/rimuhosting/fixtures/add_record_same_domain.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/add_record_same_domain.xml rename to providers/dns/internal/rimuhosting/fixtures/add_record_same_domain.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/delete_record.xml b/providers/dns/internal/rimuhosting/fixtures/delete_record.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/delete_record.xml rename to providers/dns/internal/rimuhosting/fixtures/delete_record.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/delete_record_error.xml b/providers/dns/internal/rimuhosting/fixtures/delete_record_error.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/delete_record_error.xml rename to providers/dns/internal/rimuhosting/fixtures/delete_record_error.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/delete_record_nothing.xml b/providers/dns/internal/rimuhosting/fixtures/delete_record_nothing.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/delete_record_nothing.xml rename to providers/dns/internal/rimuhosting/fixtures/delete_record_nothing.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/find_records.xml b/providers/dns/internal/rimuhosting/fixtures/find_records.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/find_records.xml rename to providers/dns/internal/rimuhosting/fixtures/find_records.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/find_records_empty.xml b/providers/dns/internal/rimuhosting/fixtures/find_records_empty.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/find_records_empty.xml rename to providers/dns/internal/rimuhosting/fixtures/find_records_empty.xml diff --git a/providers/dns/internal/rimuhosting/internal/fixtures/find_records_pattern.xml b/providers/dns/internal/rimuhosting/fixtures/find_records_pattern.xml similarity index 100% rename from providers/dns/internal/rimuhosting/internal/fixtures/find_records_pattern.xml rename to providers/dns/internal/rimuhosting/fixtures/find_records_pattern.xml diff --git a/providers/dns/internal/rimuhosting/provider.go b/providers/dns/internal/rimuhosting/provider.go deleted file mode 100644 index 3be764cbf..000000000 --- a/providers/dns/internal/rimuhosting/provider.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package rimuhosting implements a DNS provider for solving the DNS-01 challenge using RimuHosting DNS. -package rimuhosting - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting/internal" -) - -const DefaultTTL = 3600 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProviderConfig return a DNSProvider instance configured for RimuHosting. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.APIKey == "" { - return nil, errors.New("incomplete credentials, missing API key") - } - - client := internal.NewClient(config.APIKey) - - if baseURL != "" { - client.BaseURL = baseURL - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("failed to find record(s) for %s: %w", domain, err) - } - - actions := []internal.ActionParameter{ - internal.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), - } - - for _, record := range records { - actions = append(actions, internal.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) - } - - _, err = d.client.DoActions(ctx, actions...) - if err != nil { - return fmt.Errorf("failed to add record(s) for %s: %w", domain, err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - action := internal.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) - - _, err := d.client.DoActions(context.Background(), action) - if err != nil { - return fmt.Errorf("failed to delete record for %s: %w", domain, err) - } - - return nil -} diff --git a/providers/dns/internal/rimuhosting/provider_test.go b/providers/dns/internal/rimuhosting/provider_test.go deleted file mode 100644 index d1569af31..000000000 --- a/providers/dns/internal/rimuhosting/provider_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package rimuhosting - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - expected string - apiKey string - secretKey string - }{ - { - desc: "success", - apiKey: "api_key", - secretKey: "api_secret", - }, - { - desc: "missing api key", - apiKey: "", - secretKey: "api_secret", - expected: "incomplete credentials, missing API key", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/rimuhosting/internal/types.go b/providers/dns/internal/rimuhosting/types.go similarity index 98% rename from providers/dns/internal/rimuhosting/internal/types.go rename to providers/dns/internal/rimuhosting/types.go index c3df886a2..bdb333032 100644 --- a/providers/dns/internal/rimuhosting/internal/types.go +++ b/providers/dns/internal/rimuhosting/types.go @@ -1,4 +1,4 @@ -package internal +package rimuhosting import "encoding/xml" diff --git a/providers/dns/internal/selectel/internal/client.go b/providers/dns/internal/selectel/client.go similarity index 91% rename from providers/dns/internal/selectel/internal/client.go rename to providers/dns/internal/selectel/client.go index d441c9894..fe810ebc5 100644 --- a/providers/dns/internal/selectel/internal/client.go +++ b/providers/dns/internal/selectel/client.go @@ -1,4 +1,4 @@ -package internal +package selectel import ( "bytes" @@ -15,11 +15,15 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://api.selectel.ru/domains/v1" +// Base URL for the Selectel/VScale DNS services. +const ( + DefaultSelectelBaseURL = "https://api.selectel.ru/domains/v1" + DefaultVScaleBaseURL = "https://api.vscale.io/v1/domains" +) const tokenHeader = "X-Token" -// Client represents the DNS client. +// Client represents DNS client. type Client struct { token string @@ -29,7 +33,7 @@ type Client struct { // NewClient returns a client instance. func NewClient(token string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) + baseURL, _ := url.Parse(DefaultVScaleBaseURL) return &Client{ token: token, @@ -53,8 +57,8 @@ func (c *Client) GetDomainByName(ctx context.Context, domainName string) (*Domai if err != nil { if statusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1 { // Look up for the next subdomain - _, after, _ := strings.Cut(domainName, ".") - return c.GetDomainByName(ctx, after) + subIndex := strings.Index(domainName, ".") + return c.GetDomainByName(ctx, domainName[subIndex+1:]) } return nil, err diff --git a/providers/dns/internal/selectel/internal/client_test.go b/providers/dns/internal/selectel/client_test.go similarity index 99% rename from providers/dns/internal/selectel/internal/client_test.go rename to providers/dns/internal/selectel/client_test.go index edabe0130..292f70142 100644 --- a/providers/dns/internal/selectel/internal/client_test.go +++ b/providers/dns/internal/selectel/client_test.go @@ -1,4 +1,4 @@ -package internal +package selectel import ( "net/http" diff --git a/providers/dns/internal/selectel/internal/fixtures/add_record-request.json b/providers/dns/internal/selectel/fixtures/add_record-request.json similarity index 100% rename from providers/dns/internal/selectel/internal/fixtures/add_record-request.json rename to providers/dns/internal/selectel/fixtures/add_record-request.json diff --git a/providers/dns/internal/selectel/internal/fixtures/add_record.json b/providers/dns/internal/selectel/fixtures/add_record.json similarity index 100% rename from providers/dns/internal/selectel/internal/fixtures/add_record.json rename to providers/dns/internal/selectel/fixtures/add_record.json diff --git a/providers/dns/internal/selectel/internal/fixtures/domains.json b/providers/dns/internal/selectel/fixtures/domains.json similarity index 100% rename from providers/dns/internal/selectel/internal/fixtures/domains.json rename to providers/dns/internal/selectel/fixtures/domains.json diff --git a/providers/dns/internal/selectel/internal/fixtures/error.json b/providers/dns/internal/selectel/fixtures/error.json similarity index 100% rename from providers/dns/internal/selectel/internal/fixtures/error.json rename to providers/dns/internal/selectel/fixtures/error.json diff --git a/providers/dns/internal/selectel/internal/fixtures/list_records.json b/providers/dns/internal/selectel/fixtures/list_records.json similarity index 100% rename from providers/dns/internal/selectel/internal/fixtures/list_records.json rename to providers/dns/internal/selectel/fixtures/list_records.json diff --git a/providers/dns/internal/selectel/provider.go b/providers/dns/internal/selectel/provider.go deleted file mode 100644 index 495735736..000000000 --- a/providers/dns/internal/selectel/provider.go +++ /dev/null @@ -1,137 +0,0 @@ -// Package selectel implements a DNS provider for solving the DNS-01 challenge using Selectel Domains API. -package selectel - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/selectel/internal" -) - -const MinTTL = 60 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Token string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client - - // TODO(ldez): remove in v5? - BaseURL string -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProviderConfig return a DNSProvider instance configured for selectel. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.Token == "" { - return nil, errors.New("credentials missing") - } - - if config.TTL < MinTTL { - return nil, fmt.Errorf("invalid TTL, TTL (%d) must be greater than %d", config.TTL, MinTTL) - } - - client := internal.NewClient(config.Token) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - var err error - - client.BaseURL, err = url.Parse(config.BaseURL) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - return &DNSProvider{config: config, client: client}, nil -} - -// Timeout returns the Timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record to fulfill DNS-01 challenge. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) - if err != nil { - return fmt.Errorf("get domain by name: %w", err) - } - - txtRecord := internal.Record{ - Type: "TXT", - TTL: d.config.TTL, - Name: info.EffectiveFQDN, - Content: info.Value, - } - - _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) - if err != nil { - return fmt.Errorf("add record: %w", err) - } - - return nil -} - -// CleanUp removes a TXT record used for DNS-01 challenge. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - recordName := dns01.UnFqdn(info.EffectiveFQDN) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) - if err != nil { - return fmt.Errorf("%w", err) - } - - records, err := d.client.ListRecords(ctx, domainObj.ID) - if err != nil { - return fmt.Errorf("list records: %w", err) - } - - // Delete records with specific FQDN - var lastErr error - - for _, record := range records { - if record.Name == recordName { - err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) - if err != nil { - lastErr = fmt.Errorf("delete record: %w", err) - } - } - } - - return lastErr -} diff --git a/providers/dns/internal/selectel/provider_test.go b/providers/dns/internal/selectel/provider_test.go deleted file mode 100644 index 75a032bf4..000000000 --- a/providers/dns/internal/selectel/provider_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package selectel - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - token string - ttl int - expected string - }{ - { - desc: "success", - token: "123", - ttl: 60, - }, - { - desc: "missing api key", - token: "", - ttl: 60, - expected: "credentials missing", - }, - { - desc: "bad TTL value", - token: "123", - ttl: 59, - expected: fmt.Sprintf("invalid TTL, TTL (59) must be greater than %d", MinTTL), - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.TTL = test.ttl - config.Token = test.token - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} diff --git a/providers/dns/internal/selectel/internal/types.go b/providers/dns/internal/selectel/types.go similarity index 98% rename from providers/dns/internal/selectel/internal/types.go rename to providers/dns/internal/selectel/types.go index e6ca792c0..df7bb3fa7 100644 --- a/providers/dns/internal/selectel/internal/types.go +++ b/providers/dns/internal/selectel/types.go @@ -1,4 +1,4 @@ -package internal +package selectel import "fmt" diff --git a/providers/dns/internal/tecnocratica/internal/client.go b/providers/dns/internal/tecnocratica/internal/client.go deleted file mode 100644 index 5a529fa2f..000000000 --- a/providers/dns/internal/tecnocratica/internal/client.go +++ /dev/null @@ -1,182 +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" -) - -// defaultBaseURL is the default API endpoint. -const defaultBaseURL = "https://api.neodigit.net/v1" - -// Client is a Tecnocrática API client. -type Client struct { - token string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(token string) (*Client, error) { - if token == "" { - return nil, errors.New("credentials missing: token") - } - - baseURL, err := url.Parse(defaultBaseURL) - if err != nil { - return nil, err - } - - return &Client{ - token: token, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 30 * time.Second}, - }, nil -} - -// GetZones lists all DNS zones. -func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { - endpoint := c.BaseURL.JoinPath("dns", "zones") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var zones []Zone - - err = c.do(req, &zones) - if err != nil { - return nil, err - } - - return zones, nil -} - -// GetRecords lists all records in a zone. -func (c *Client) GetRecords(ctx context.Context, zoneID int, recordType string) ([]Record, error) { - endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records") - - if recordType != "" { - query := endpoint.Query() - query.Set("type", recordType) - endpoint.RawQuery = query.Encode() - } - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var records []Record - - err = c.do(req, &records) - if err != nil { - return nil, err - } - - return records, nil -} - -// CreateRecord creates a new DNS record. -func (c *Client) CreateRecord(ctx context.Context, zoneID int, record Record) (*Record, error) { - endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records") - - payload := RecordRequest{Record: record} - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) - if err != nil { - return nil, err - } - - var result Record - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return &result, nil -} - -// DeleteRecord deletes a DNS record. -func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID int) error { - endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records", strconv.Itoa(recordID)) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Set("X-TCpanel-Token", c.token) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} diff --git a/providers/dns/internal/tecnocratica/internal/client_test.go b/providers/dns/internal/tecnocratica/internal/client_test.go deleted file mode 100644 index 4e9cf3e85..000000000 --- a/providers/dns/internal/tecnocratica/internal/client_test.go +++ /dev/null @@ -1,174 +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("X-TCpanel-Token", "secret")) -} - -func TestClient_GetZones(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromFixture("get_zones.json")). - Build(t) - - zones, err := client.GetZones(t.Context()) - require.NoError(t, err) - - expected := []Zone{ - { - ID: 6, - Name: "example.com", - HumanName: "example.com", - }, - { - ID: 7, - Name: "example.org", - HumanName: "example.org", - }, - } - - assert.Equal(t, expected, zones) -} - -func TestClient_GetZones_error(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones", - servermock.RawStringResponse(`{"error": "unauthorized"}`). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - zones, err := client.GetZones(t.Context()) - require.Error(t, err) - - assert.Nil(t, zones) -} - -func TestClient_GetRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/zones/6/records", - servermock.ResponseFromFixture("get_records.json")). - Build(t) - - records, err := client.GetRecords(t.Context(), 6, "") - require.NoError(t, err) - - expected := []Record{ - { - ID: 98, - Name: "", - Type: "SOA", - Content: "ns1.example.org dns.example.org 2015092102 7200 7200 1209600 1800", - TTL: 7200, - }, - { - ID: 99, - Name: "", - Type: "NS", - Content: "ns1.example.org", - TTL: 7200, - }, - { - ID: 100, - Name: "_acme-challenge", - Type: "TXT", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - }, - } - - assert.Equal(t, expected, records) -} - -func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /dns/zones/6/records", - servermock.ResponseFromFixture("create_record.json"). - WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := Record{ - Name: "_acme-challenge", - Type: "TXT", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - } - - result, err := client.CreateRecord(t.Context(), 6, record) - require.NoError(t, err) - - expected := &Record{ - ID: 101, - Name: "_acme-challenge", - Type: "TXT", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - } - - assert.Equal(t, expected, result) -} - -func TestClient_CreateRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /dns/zones/6/records", - servermock.RawStringResponse(`{"error": "bad request"}`). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - record := Record{ - Name: "_acme-challenge", - Type: "TXT", - Content: "test-value", - TTL: 120, - } - - result, err := client.CreateRecord(t.Context(), 6, record) - require.Error(t, err) - - assert.Nil(t, result) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /dns/zones/6/records/101", - servermock.Noop(). - WithStatusCode(http.StatusNoContent)). - Build(t) - - err := client.DeleteRecord(t.Context(), 6, 101) - require.NoError(t, err) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /dns/zones/6/records/999", - servermock.RawStringResponse(`{"error": "not found"}`). - WithStatusCode(http.StatusNotFound)). - Build(t) - - err := client.DeleteRecord(t.Context(), 6, 999) - require.Error(t, err) -} diff --git a/providers/dns/internal/tecnocratica/internal/fixtures/create_record-request.json b/providers/dns/internal/tecnocratica/internal/fixtures/create_record-request.json deleted file mode 100644 index 4cd339c98..000000000 --- a/providers/dns/internal/tecnocratica/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "record": { - "name": "_acme-challenge", - "type": "TXT", - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120 - } -} diff --git a/providers/dns/internal/tecnocratica/internal/fixtures/create_record.json b/providers/dns/internal/tecnocratica/internal/fixtures/create_record.json deleted file mode 100644 index 6f30010ac..000000000 --- a/providers/dns/internal/tecnocratica/internal/fixtures/create_record.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": 101, - "name": "_acme-challenge", - "type": "TXT", - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120, - "prio": null, - "created_at": "2015-09-21T14:40:27.127+02:00", - "updated_at": "2015-09-21T14:40:27.127+02:00" -} diff --git a/providers/dns/internal/tecnocratica/internal/fixtures/get_records.json b/providers/dns/internal/tecnocratica/internal/fixtures/get_records.json deleted file mode 100644 index 00e09c37f..000000000 --- a/providers/dns/internal/tecnocratica/internal/fixtures/get_records.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "id": 98, - "name": "", - "type": "SOA", - "content": "ns1.example.org dns.example.org 2015092102 7200 7200 1209600 1800", - "ttl": 7200, - "prio": null - }, - { - "id": 99, - "name": "", - "type": "NS", - "content": "ns1.example.org", - "ttl": 7200, - "prio": null - }, - { - "id": 100, - "name": "_acme-challenge", - "type": "TXT", - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120, - "prio": null - } -] diff --git a/providers/dns/internal/tecnocratica/internal/fixtures/get_zones.json b/providers/dns/internal/tecnocratica/internal/fixtures/get_zones.json deleted file mode 100644 index 01a08dced..000000000 --- a/providers/dns/internal/tecnocratica/internal/fixtures/get_zones.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "id": 6, - "name": "example.com", - "created_at": "2015-09-21T12:19:04.000+02:00", - "updated_at": "2015-09-21T12:19:04.000+02:00", - "human_name": "example.com" - }, - { - "id": 7, - "name": "example.org", - "created_at": "2015-09-22T10:00:00.000+02:00", - "updated_at": "2015-09-22T10:00:00.000+02:00", - "human_name": "example.org" - } -] diff --git a/providers/dns/internal/tecnocratica/internal/types.go b/providers/dns/internal/tecnocratica/internal/types.go deleted file mode 100644 index 505bfbced..000000000 --- a/providers/dns/internal/tecnocratica/internal/types.go +++ /dev/null @@ -1,23 +0,0 @@ -package internal - -// Zone represents a DNS zone. -type Zone struct { - ID int `json:"id"` - Name string `json:"name"` - HumanName string `json:"human_name"` -} - -// Record represents a DNS record. -type Record struct { - ID int `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Content string `json:"content,omitempty"` - TTL int `json:"ttl,omitempty"` - Priority int `json:"prio,omitempty"` -} - -// RecordRequest is the request body for creating/updating a record. -type RecordRequest struct { - Record Record `json:"record"` -} diff --git a/providers/dns/internal/tecnocratica/provider.go b/providers/dns/internal/tecnocratica/provider.go deleted file mode 100644 index 17cfb8379..000000000 --- a/providers/dns/internal/tecnocratica/provider.go +++ /dev/null @@ -1,165 +0,0 @@ -// Package tecnocratica implements a DNS provider for solving the DNS-01 challenge using Tecnocrática. -package tecnocratica - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica/internal" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Token string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - zoneIDs map[string]int - recordIDs map[string]int - recordIDsMu sync.Mutex -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Tecnocrática. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - if config.Token == "" { - return nil, errors.New("missing credentials") - } - - client, err := internal.NewClient(config.Token) - if err != nil { - return nil, fmt.Errorf("create client: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - if baseURL != "" { - client.BaseURL, err = url.Parse(baseURL) - if err != nil { - return nil, err - } - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - zoneIDs: make(map[string]int), - recordIDs: make(map[string]int), - }, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - authZone = dns01.UnFqdn(authZone) - - zone, err := d.findZone(ctx, authZone) - if err != nil { - return fmt.Errorf("%w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("%w", err) - } - - record := internal.Record{ - Name: subDomain, - Type: "TXT", - Content: info.Value, - TTL: d.config.TTL, - } - - newRecord, err := d.client.CreateRecord(ctx, zone.ID, record) - if err != nil { - return fmt.Errorf("create record: %w", err) - } - - d.recordIDsMu.Lock() - d.zoneIDs[token] = zone.ID - d.recordIDs[token] = newRecord.ID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - d.recordIDsMu.Lock() - zoneID, zoneOK := d.zoneIDs[token] - recordID, recordOK := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !zoneOK || !recordOK { - return fmt.Errorf("unknown record ID or zone ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err := d.client.DeleteRecord(context.Background(), zoneID, recordID) - if err != nil { - return fmt.Errorf("delete record: fqdn=%s, zoneID=%d, recordID=%d: %w", - info.EffectiveFQDN, zoneID, recordID, err) - } - - d.recordIDsMu.Lock() - delete(d.zoneIDs, token) - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*internal.Zone, error) { - zones, err := d.client.GetZones(ctx) - if err != nil { - return nil, fmt.Errorf("get zones: %w", err) - } - - for _, zone := range zones { - if zone.Name == zoneName || zone.HumanName == zoneName { - return &zone, nil - } - } - - return nil, fmt.Errorf("zone not found: %s", zoneName) -} diff --git a/providers/dns/internal/tecnocratica/provider_test.go b/providers/dns/internal/tecnocratica/provider_test.go deleted file mode 100644 index 33e5f7c67..000000000 --- a/providers/dns/internal/tecnocratica/provider_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package tecnocratica - -import ( - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - token string - expected string - }{ - { - desc: "success", - token: "secret", - }, - { - desc: "missing token", - expected: "missing credentials", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.Token = test.token - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := &Config{ - Token: "secret", - PropagationTimeout: 10 * time.Second, - PollingInterval: 1 * time.Second, - TTL: 120, - HTTPClient: server.Client(), - } - - p, err := NewDNSProviderConfig(config, server.URL) - if err != nil { - return nil, err - } - - return p, nil - }, - servermock.CheckHeader().WithJSONHeaders(). - With("X-TCpanel-Token", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromInternal("get_zones.json")). - Route("POST /dns/zones/6/records", - servermock.ResponseFromInternal("create_record.json"). - WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /dns/zones/456/records/123", - servermock.Noop(). - WithStatusCode(http.StatusNoContent)). - Build(t) - - token := "abc" - - provider.recordIDs[token] = 123 - provider.zoneIDs[token] = 456 - - err := provider.CleanUp("example.com", token, "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 090c9109a..1fdaef71a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.32.0" + ourUserAgent = "goacme-lego/4.28.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. diff --git a/providers/dns/internal/westcn/provider.go b/providers/dns/internal/westcn/provider.go deleted file mode 100644 index a9e6dad58..000000000 --- a/providers/dns/internal/westcn/provider.go +++ /dev/null @@ -1,140 +0,0 @@ -package westcn - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/internal/westcn/internal" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Username string - Password string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]int - recordIDsMu sync.Mutex -} - -// NewDNSProviderConfig return a DNSProvider instance configured for West.cn/西部数码. -func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.Username, config.Password) - if err != nil { - return nil, fmt.Errorf("%w", err) - } - - if baseURL != "" { - client.BaseURL, err = url.Parse(baseURL) - if err != nil { - return nil, err - } - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("%w", err) - } - - record := internal.Record{ - Domain: dns01.UnFqdn(authZone), - Host: subDomain, - Type: "TXT", - Value: info.Value, - TTL: d.config.TTL, - } - - recordID, err := d.client.AddRecord(context.Background(), record) - if err != nil { - return fmt.Errorf("add record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = recordID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("could not find zone for domain %q: %w", domain, err) - } - - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("delete record: %w", err) - } - - // deletes record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} diff --git a/providers/dns/internal/westcn/provider_test.go b/providers/dns/internal/westcn/provider_test.go deleted file mode 100644 index 2ae0f09cb..000000000 --- a/providers/dns/internal/westcn/provider_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package westcn - -import ( - "net/http/httptest" - "testing" - "time" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - username string - password string - expected string - }{ - { - desc: "success", - username: "user", - password: "secret", - }, - { - desc: "missing username", - password: "secret", - expected: "credentials missing", - }, - { - desc: "missing password", - username: "user", - expected: "credentials missing", - }, - { - desc: "missing credentials", - expected: "credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := &Config{} - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config, "") - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := &Config{ - Username: "user", - Password: "secret", - PropagationTimeout: 10 * time.Second, - PollingInterval: 1 * time.Second, - TTL: 120, - HTTPClient: server.Client(), - } - - p, err := NewDNSProviderConfig(config, server.URL) - if err != nil { - return nil, err - } - - return p, nil - }, - servermock.CheckHeader(). - WithContentTypeFromURLEncoded()) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /domain/", - servermock.ResponseFromInternal("adddnsrecord.json"). - WithHeader("Content-Type", "application/json", "Charset=gb2312"), - servermock.CheckQueryParameter().Strict(). - With("act", "adddnsrecord"), - servermock.CheckForm().UsePostForm().Strict(). - With("domain", "example.com"). - With("host", "_acme-challenge"). - With("ttl", "120"). - With("type", "TXT"). - With("value", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). - // With("act", "adddnsrecord"). - With("username", "user"). - WithRegexp("time", `\d+`). - WithRegexp("token", `[a-z0-9]{32}`), - ). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("POST /domain/", - servermock.ResponseFromInternal("deldnsrecord.json"). - WithHeader("Content-Type", "application/json", "Charset=gb2312"), - servermock.CheckQueryParameter().Strict(). - With("act", "deldnsrecord"), - servermock.CheckForm().UsePostForm().Strict(). - With("id", "123"). - With("domain", "example.com"). - With("username", "user"). - WithRegexp("time", `\d+`). - WithRegexp("token", `[a-z0-9]{32}`), - ). - Build(t) - - provider.recordIDs["abc"] = 123 - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/internetbs/internetbs.toml b/providers/dns/internetbs/internetbs.toml index f22850253..d25418f22 100644 --- a/providers/dns/internetbs/internetbs.toml +++ b/providers/dns/internetbs/internetbs.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' INTERNET_BS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ INTERNET_BS_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --dns internetbs -d '*.example.com' -d example.com run +lego --email you@example.com --dns internetbs -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 0e79d71e0..794db84b3 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -177,7 +177,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("inwx: %w", err) } - var recordID string + var recordID int for _, record := range response.Records { if record.Content != info.Value { @@ -189,7 +189,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { break } - if recordID == "" { + if recordID == 0 { return errors.New("inwx: TXT record not found") } diff --git a/providers/dns/inwx/inwx.toml b/providers/dns/inwx/inwx.toml index da4c6d959..aeab5a242 100644 --- a/providers/dns/inwx/inwx.toml +++ b/providers/dns/inwx/inwx.toml @@ -7,13 +7,13 @@ Since = "v2.0.0" Example = ''' INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ -lego --dns inwx -d '*.example.com' -d example.com run +lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run # 2FA INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ INWX_SHARED_SECRET=zzzzzzzzzz \ -lego --dns inwx -d '*.example.com' -d example.com run +lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/internal/ionos/internal/client.go b/providers/dns/ionos/internal/client.go similarity index 98% rename from providers/dns/internal/ionos/internal/client.go rename to providers/dns/ionos/internal/client.go index 2a556a49b..935b6bbad 100644 --- a/providers/dns/internal/ionos/internal/client.go +++ b/providers/dns/ionos/internal/client.go @@ -14,6 +14,7 @@ import ( querystring "github.com/google/go-querystring/query" ) +// defaultBaseURL represents the API endpoint to call. const defaultBaseURL = "https://api.hosting.ionos.com/dns" // APIKeyHeader API key header. diff --git a/providers/dns/internal/ionos/internal/client_test.go b/providers/dns/ionos/internal/client_test.go similarity index 100% rename from providers/dns/internal/ionos/internal/client_test.go rename to providers/dns/ionos/internal/client_test.go diff --git a/providers/dns/internal/ionos/internal/fixtures/get_records.json b/providers/dns/ionos/internal/fixtures/get_records.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/get_records.json rename to providers/dns/ionos/internal/fixtures/get_records.json diff --git a/providers/dns/internal/ionos/internal/fixtures/get_records_error.json b/providers/dns/ionos/internal/fixtures/get_records_error.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/get_records_error.json rename to providers/dns/ionos/internal/fixtures/get_records_error.json diff --git a/providers/dns/internal/ionos/internal/fixtures/list_zones.json b/providers/dns/ionos/internal/fixtures/list_zones.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/list_zones.json rename to providers/dns/ionos/internal/fixtures/list_zones.json diff --git a/providers/dns/internal/ionos/internal/fixtures/list_zones_error.json b/providers/dns/ionos/internal/fixtures/list_zones_error.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/list_zones_error.json rename to providers/dns/ionos/internal/fixtures/list_zones_error.json diff --git a/providers/dns/internal/ionos/internal/fixtures/remove_record_error.json b/providers/dns/ionos/internal/fixtures/remove_record_error.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/remove_record_error.json rename to providers/dns/ionos/internal/fixtures/remove_record_error.json diff --git a/providers/dns/internal/ionos/internal/fixtures/replace_records_error.json b/providers/dns/ionos/internal/fixtures/replace_records_error.json similarity index 100% rename from providers/dns/internal/ionos/internal/fixtures/replace_records_error.json rename to providers/dns/ionos/internal/fixtures/replace_records_error.json diff --git a/providers/dns/internal/ionos/internal/types.go b/providers/dns/ionos/internal/types.go similarity index 100% rename from providers/dns/internal/ionos/internal/types.go rename to providers/dns/ionos/internal/types.go diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index 892370f5d..a512e8bfd 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -2,15 +2,19 @@ package ionos import ( + "context" "errors" "fmt" "net/http" + "strconv" + "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/ionos" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/ionos/internal" ) // Environment variables names. @@ -30,12 +34,18 @@ const minTTL = 300 var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = ionos.Config +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, ionos.MinTTL), + TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -46,7 +56,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *internal.Client } // NewDNSProvider returns a DNSProvider instance configured for Ionos. @@ -69,36 +80,129 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("ionos: the configuration of the DNS provider is nil") } - provider, err := ionos.NewDNSProviderConfig(config, "") + if config.APIKey == "" { + return nil, errors.New("ionos: credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("ionos: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client, err := internal.NewClient(config.APIKey) if err != nil { return nil, fmt.Errorf("ionos: %w", err) } - return &DNSProvider{prv: provider}, nil + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval } // Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) if err != nil { - return fmt.Errorf("ionos: %w", err) + return fmt.Errorf("ionos: failed to get zones: %w", err) + } + + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("ionos: no matching zone found for domain") + } + + filter := &internal.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("ionos: failed to get records (zone=%s): %w", zone.ID, err) + } + + records = append(records, internal.Record{ + Name: name, + Content: info.Value, + TTL: d.config.TTL, + Type: "TXT", + }) + + err = d.client.ReplaceRecords(ctx, zone.ID, records) + if err != nil { + return fmt.Errorf("ionos: failed to create/update records (zone=%s): %w", zone.ID, err) } return nil } // CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) if err != nil { - return fmt.Errorf("ionos: %w", err) + return fmt.Errorf("ionos: failed to get zones: %w", err) } - return nil + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("ionos: no matching zone found for domain") + } + + filter := &internal.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("ionos: failed to get records (zone=%s): %w", zone.ID, err) + } + + for _, record := range records { + if record.Name == name && record.Content == strconv.Quote(info.Value) { + err = d.client.RemoveRecord(ctx, zone.ID, record.ID) + if err != nil { + return fmt.Errorf("ionos: failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) + } + + return nil + } + } + + return fmt.Errorf("ionos: failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) +} + +func findZone(zones []internal.Zone, domain string) *internal.Zone { + var result *internal.Zone + + for _, zone := range zones { + if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { + if result == nil || len(zone.Name) > len(result.Name) { + result = &zone + } + } + } + + return result } diff --git a/providers/dns/ionos/ionos.toml b/providers/dns/ionos/ionos.toml index a2c9518fb..0c905273f 100644 --- a/providers/dns/ionos/ionos.toml +++ b/providers/dns/ionos/ionos.toml @@ -6,7 +6,7 @@ Since = "v4.2.0" Example = ''' IONOS_API_KEY=xxxxxxxx \ -lego --dns ionos -d '*.example.com' -d example.com run +lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ionos/ionos_test.go b/providers/dns/ionos/ionos_test.go index 39dc0c511..7b1f5af11 100644 --- a/providers/dns/ionos/ionos_test.go +++ b/providers/dns/ionos/ionos_test.go @@ -9,7 +9,9 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) +var envTest = tester.NewEnvTest( + EnvAPIKey). + WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -45,7 +47,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -89,7 +92,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/ionoscloud/internal/client.go b/providers/dns/ionoscloud/internal/client.go deleted file mode 100644 index 5b7d3a0fc..000000000 --- a/providers/dns/ionoscloud/internal/client.go +++ /dev/null @@ -1,172 +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://dns.de-fra.ionos.com" - -const authorizationHeader = "Authorization" - -// Client the Ionos Cloud API client. -type Client struct { - apiKey string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(apiKey string) (*Client, error) { - if apiKey == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiKey: apiKey, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -// RetrieveZones returns a list of the DNS zones. -// https://api.ionos.com/docs/dns/v1/#tag/Zones/operation/zonesGet -func (c *Client) RetrieveZones(ctx context.Context, zoneName string) ([]Zone, error) { - endpoint := c.BaseURL.JoinPath("zones") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - query := req.URL.Query() - query.Add("filter.zoneName", zoneName) - req.URL.RawQuery = query.Encode() - - result := ZonesResponse{} - - if err := c.do(req, &result); err != nil { - return nil, err - } - - return result.Items, nil -} - -// CreateRecord creates a new record for the DNS zone. -// https://api.ionos.com/docs/dns/v1/#tag/Records/operation/zonesRecordsPost -func (c *Client) CreateRecord(ctx context.Context, zoneID string, record RecordProperties) (*RecordResponse, error) { - endpoint := c.BaseURL.JoinPath("zones", zoneID, "records") - - payload := map[string]RecordProperties{ - "properties": record, - } - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) - if err != nil { - return nil, err - } - - result := &RecordResponse{} - - if err := c.do(req, result); err != nil { - return nil, err - } - - return result, nil -} - -// DeleteRecord deletes a specified record from the DNS zone. -// https://api.ionos.com/docs/dns/v1/#tag/Records/operation/zonesRecordsDelete -func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { - endpoint := c.BaseURL.JoinPath("zones", zoneID, "records", recordID) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Set(authorizationHeader, "Bearer "+c.apiKey) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/ionoscloud/internal/client_test.go b/providers/dns/ionoscloud/internal/client_test.go deleted file mode 100644 index dc478cc64..000000000 --- a/providers/dns/ionoscloud/internal/client_test.go +++ /dev/null @@ -1,134 +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 mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder[*Client]( - func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret") - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestClient_RetrieveZones(t *testing.T) { - client := mockBuilder(). - Route("GET /zones", - servermock.ResponseFromFixture("zones.json"), - servermock.CheckQueryParameter().Strict(). - With("filter.zoneName", "example.com")). - Build(t) - - zones, err := client.RetrieveZones(t.Context(), "example.com") - require.NoError(t, err) - - expected := []Zone{{ - ID: "e74d0d15-f567-4b7b-9069-26ee1f93bae3", - Type: "zone", - Metadata: ZoneMetadata{ - CreatedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), - CreatedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - CreatedByUserID: "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - LastModifiedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), - LastModifiedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - LastModifiedByUserID: "63cef532-26fe-4a64-a4e0-de7c8a506c90", - ResourceURN: "ionos::::", - State: "PROVISIONING", - Nameservers: []string{"ns-ic.ui-dns.com", "ns-ic.ui-dns.de", "ns-ic.ui-dns.org", "ns-ic.ui-dns.biz"}, - }, - Properties: ZoneProperties{ - ZoneName: "example.com", - Description: "The hosted zone is used for example.com", - Enabled: true, - }, - }} - - assert.Equal(t, expected, zones) -} - -func TestClient_RetrieveZones_error(t *testing.T) { - client := mockBuilder(). - Route("GET /zones", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.RetrieveZones(t.Context(), "example.com") - require.EqualError(t, err, "401: paas-auth-1: Unauthorized, wrong or no api key provided to process this request") -} - -func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/abc/records", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := RecordProperties{ - Name: "_acme-challenge", - Type: "TXT", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 120, - } - - result, err := client.CreateRecord(t.Context(), "abc", record) - require.NoError(t, err) - - expected := &RecordResponse{ - ID: "90d81ac0-3a30-44d4-95a5-12959effa6ee", - Type: "record", - Metadata: RecordMetadata{ - CreatedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), - CreatedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - CreatedByUserID: "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - LastModifiedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), - LastModifiedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - LastModifiedByUserID: "63cef532-26fe-4a64-a4e0-de7c8a506c90", - ResourceURN: "ionos::::", - State: "PROVISIONING", - Fqdn: "app.example.com", - ZoneID: "a363f30c-4c0c-4552-9a07-298d87f219bf", - }, - Properties: RecordProperties{ - Name: "app", - Type: "A", - Content: "1.2.3.4", - TTL: 3600, - Priority: 3600, - Enabled: true, - }, - } - - assert.Equal(t, expected, result) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /zones/abc/records/def", - servermock.Noop(). - WithStatusCode(http.StatusAccepted)). - Build(t) - - err := client.DeleteRecord(t.Context(), "abc", "def") - require.NoError(t, err) -} diff --git a/providers/dns/ionoscloud/internal/fixtures/create_record-request.json b/providers/dns/ionoscloud/internal/fixtures/create_record-request.json deleted file mode 100644 index d4f52bba8..000000000 --- a/providers/dns/ionoscloud/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "properties": { - "name": "_acme-challenge", - "type": "TXT", - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 120 - } -} diff --git a/providers/dns/ionoscloud/internal/fixtures/create_record.json b/providers/dns/ionoscloud/internal/fixtures/create_record.json deleted file mode 100644 index d3094c3b2..000000000 --- a/providers/dns/ionoscloud/internal/fixtures/create_record.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "90d81ac0-3a30-44d4-95a5-12959effa6ee", - "type": "record", - "href": "", - "metadata": { - "createdDate": "2022-08-21T15:52:53Z", - "createdBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "createdByUserId": "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "lastModifiedDate": "2022-08-21T15:52:53Z", - "lastModifiedBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "lastModifiedByUserId": "63cef532-26fe-4a64-a4e0-de7c8a506c90", - "resourceURN": "ionos::::", - "state": "PROVISIONING", - "fqdn": "app.example.com", - "zoneId": "a363f30c-4c0c-4552-9a07-298d87f219bf" - }, - "properties": { - "name": "app", - "type": "A", - "content": "1.2.3.4", - "ttl": 3600, - "priority": 3600, - "enabled": true - } -} diff --git a/providers/dns/ionoscloud/internal/fixtures/error.json b/providers/dns/ionoscloud/internal/fixtures/error.json deleted file mode 100644 index bed0e5efb..000000000 --- a/providers/dns/ionoscloud/internal/fixtures/error.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "httpStatus": 401, - "messages": [ - { - "errorCode": "paas-auth-1", - "message": "Unauthorized, wrong or no api key provided to process this request" - } - ] -} diff --git a/providers/dns/ionoscloud/internal/fixtures/zones.json b/providers/dns/ionoscloud/internal/fixtures/zones.json deleted file mode 100644 index c9c2c62f9..000000000 --- a/providers/dns/ionoscloud/internal/fixtures/zones.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "e74d0d15-f567-4b7b-9069-26ee1f93bae3", - "type": "collection", - "href": "", - "offset": 0, - "limit": 1000, - "_links": { - "prev": "http://PREVIOUS-PAGE-URI", - "self": "http://THIS-PAGE-URI", - "next": "http://NEXT-PAGE-URI" - }, - "items": [ - { - "id": "e74d0d15-f567-4b7b-9069-26ee1f93bae3", - "type": "zone", - "href": "", - "metadata": { - "createdDate": "2022-08-21T15:52:53Z", - "createdBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "createdByUserId": "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "lastModifiedDate": "2022-08-21T15:52:53Z", - "lastModifiedBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", - "lastModifiedByUserId": "63cef532-26fe-4a64-a4e0-de7c8a506c90", - "resourceURN": "ionos::::", - "state": "PROVISIONING", - "nameservers": [ - "ns-ic.ui-dns.com", - "ns-ic.ui-dns.de", - "ns-ic.ui-dns.org", - "ns-ic.ui-dns.biz" - ] - }, - "properties": { - "zoneName": "example.com", - "description": "The hosted zone is used for example.com", - "enabled": true - } - } - ] -} diff --git a/providers/dns/ionoscloud/internal/types.go b/providers/dns/ionoscloud/internal/types.go deleted file mode 100644 index 49348f4d1..000000000 --- a/providers/dns/ionoscloud/internal/types.go +++ /dev/null @@ -1,97 +0,0 @@ -package internal - -import ( - "fmt" - "strconv" - "strings" - "time" -) - -type APIError struct { - HTTPStatus int `json:"httpStatus"` - Messages []ErrorMessage `json:"messages"` -} - -func (a *APIError) Error() string { - var msg strings.Builder - - msg.WriteString(strconv.Itoa(a.HTTPStatus)) - - for _, m := range a.Messages { - msg.WriteString(": ") - msg.WriteString(m.String()) - } - - return msg.String() -} - -type ErrorMessage struct { - ErrorCode string `json:"errorCode"` - Message string `json:"message"` -} - -func (e ErrorMessage) String() string { - return fmt.Sprintf("%s: %s", e.ErrorCode, e.Message) -} - -type ZonesResponse struct { - ID string `json:"id"` - Type string `json:"type"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Items []Zone `json:"items"` -} - -type Zone struct { - ID string `json:"id"` - Type string `json:"type"` - Metadata ZoneMetadata `json:"metadata"` - Properties ZoneProperties `json:"properties"` -} - -type ZoneMetadata struct { - CreatedDate time.Time `json:"createdDate"` - CreatedBy string `json:"createdBy"` - CreatedByUserID string `json:"createdByUserId"` - LastModifiedDate time.Time `json:"lastModifiedDate"` - LastModifiedBy string `json:"lastModifiedBy"` - LastModifiedByUserID string `json:"lastModifiedByUserId"` - ResourceURN string `json:"resourceURN"` - State string `json:"state"` - Nameservers []string `json:"nameservers"` -} - -type ZoneProperties struct { - ZoneName string `json:"zoneName"` - Description string `json:"description"` - Enabled bool `json:"enabled"` -} - -type RecordResponse struct { - ID string `json:"id"` - Type string `json:"type"` - Metadata RecordMetadata `json:"metadata"` - Properties RecordProperties `json:"properties"` -} - -type RecordMetadata struct { - CreatedDate time.Time `json:"createdDate"` - CreatedBy string `json:"createdBy"` - CreatedByUserID string `json:"createdByUserId"` - LastModifiedDate time.Time `json:"lastModifiedDate"` - LastModifiedBy string `json:"lastModifiedBy"` - LastModifiedByUserID string `json:"lastModifiedByUserId"` - ResourceURN string `json:"resourceURN"` - State string `json:"state"` - Fqdn string `json:"fqdn"` - ZoneID string `json:"zoneId"` -} - -type RecordProperties struct { - Name string `json:"name"` - Type string `json:"type,omitempty"` - Content string `json:"content,omitempty"` - TTL int `json:"ttl,omitempty"` - Priority int `json:"priority,omitempty"` - Enabled bool `json:"enabled,omitempty"` -} diff --git a/providers/dns/ionoscloud/ionoscloud.go b/providers/dns/ionoscloud/ionoscloud.go deleted file mode 100644 index 0c33fba9f..000000000 --- a/providers/dns/ionoscloud/ionoscloud.go +++ /dev/null @@ -1,184 +0,0 @@ -// Package ionoscloud implements a DNS provider for solving the DNS-01 challenge using Ionos Cloud. -package ionoscloud - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/ionoscloud/internal" -) - -// Environment variables names. -const ( - envNamespace = "IONOSCLOUD_" - - EnvAPIToken = envNamespace + "API_TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIToken string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - zoneIDs map[string]string - recordIDs map[string]string - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Ionos Cloud. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIToken) - if err != nil { - return nil, fmt.Errorf("ionoscloud: %w", err) - } - - config := NewDefaultConfig() - config.APIToken = values[EnvAPIToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Ionos Cloud. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("ionoscloud: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIToken) - if err != nil { - return nil, fmt.Errorf("ionoscloud: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - zoneIDs: make(map[string]string), - recordIDs: make(map[string]string), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("ionoscloud: could not find zone for domain %q: %w", domain, err) - } - - zones, err := d.client.RetrieveZones(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("ionoscloud: retrieve zones: %w", err) - } - - if len(zones) != 1 { - return fmt.Errorf("ionoscloud: zone ID not found for domain %q", domain) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("ionoscloud: %w", err) - } - - zoneID := zones[0].ID - - request := internal.RecordProperties{ - Name: subDomain, - Type: "TXT", - Content: info.Value, - TTL: d.config.TTL, - } - - record, err := d.client.CreateRecord(ctx, zoneID, request) - if err != nil { - return fmt.Errorf("ionoscloud: create record: %w", err) - } - - d.recordIDsMu.Lock() - d.zoneIDs[token] = zoneID - d.recordIDs[token] = record.ID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - d.recordIDsMu.Lock() - zoneID, ok := d.zoneIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("ionoscloud: unknown zone ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("ionoscloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err := d.client.DeleteRecord(context.Background(), zoneID, recordID) - if err != nil { - return fmt.Errorf("ionoscloud: delete record: %w", err) - } - - d.recordIDsMu.Lock() - delete(d.zoneIDs, token) - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} diff --git a/providers/dns/ionoscloud/ionoscloud.toml b/providers/dns/ionoscloud/ionoscloud.toml deleted file mode 100644 index 6e1d080e4..000000000 --- a/providers/dns/ionoscloud/ionoscloud.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Ionos Cloud" -Description = '''''' -URL = "https://cloud.ionos.de/network/cloud-dns" -Code = "ionoscloud" -Since = "v4.30.0" - -Example = ''' -IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns ionoscloud -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - IONOSCLOUD_API_TOKEN = "API token" - [Configuration.Additional] - IONOSCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - IONOSCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" - IONOSCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - IONOSCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://api.ionos.com/docs/dns/v1/" diff --git a/providers/dns/ionoscloud/ionoscloud_test.go b/providers/dns/ionoscloud/ionoscloud_test.go deleted file mode 100644 index 8282e08fc..000000000 --- a/providers/dns/ionoscloud/ionoscloud_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package ionoscloud - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIToken: "secret", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "ionoscloud: some credentials information are missing: IONOSCLOUD_API_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "secret", - }, - { - desc: "missing credentials", - expected: "ionoscloud: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIToken = "secret" - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /zones", - servermock.ResponseFromInternal("zones.json"), - servermock.CheckQueryParameter().Strict(). - With("filter.zoneName", "example.com")). - Route("POST /zones/e74d0d15-f567-4b7b-9069-26ee1f93bae3/records", - servermock.ResponseFromInternal("create_record.json"), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /zones/e74d0d15-f567-4b7b-9069-26ee1f93bae3/records/90d81ac0-3a30-44d4-95a5-12959effa6ee", - servermock.Noop(). - WithStatusCode(http.StatusAccepted)). - Build(t) - - token := "abc" - - provider.zoneIDs[token] = "e74d0d15-f567-4b7b-9069-26ee1f93bae3" - provider.recordIDs[token] = "90d81ac0-3a30-44d4-95a5-12959effa6ee" - - err := provider.CleanUp("example.com", token, "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/ipv64/ipv64.toml b/providers/dns/ipv64/ipv64.toml index aa1720c9e..fba210bdb 100644 --- a/providers/dns/ipv64/ipv64.toml +++ b/providers/dns/ipv64/ipv64.toml @@ -6,7 +6,7 @@ Since = "v4.13.0" Example = ''' IPV64_API_KEY=xxxxxx \ -lego --dns ipv64 -d '*.example.com' -d example.com run +lego --email you@example.com --dns ipv64 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ispconfig/internal/client.go b/providers/dns/ispconfig/internal/client.go deleted file mode 100644 index 9280fdec1..000000000 --- a/providers/dns/ispconfig/internal/client.go +++ /dev/null @@ -1,318 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" -) - -type Client struct { - serverURL string - HTTPClient *http.Client -} - -func NewClient(serverURL string) (*Client, error) { - _, err := url.Parse(serverURL) - if err != nil { - return nil, fmt.Errorf("server URL: %w", err) - } - - return &Client{ - serverURL: serverURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) Login(ctx context.Context, username, password string) (string, error) { - payload := LoginRequest{ - Username: username, - Password: password, - ClientLogin: false, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return "", err - } - - endpoint.RawQuery = "login" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return "", err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return "", err - } - - return extractResponse[string](response) -} - -func (c *Client) GetClientID(ctx context.Context, sessionID, sysUserID string) (int, error) { - payload := ClientIDRequest{ - SessionID: sessionID, - SysUserID: sysUserID, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return 0, err - } - - endpoint.RawQuery = "client_get_id" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return 0, err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return 0, err - } - - return extractResponse[int](response) -} - -// GetZoneID returns the zone ID for the given name. -func (c *Client) GetZoneID(ctx context.Context, sessionID, name string) (int, error) { - payload := map[string]any{ - "session_id": sessionID, - "origin": name, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return 0, err - } - - endpoint.RawQuery = "dns_zone_get_id" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return 0, err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return 0, err - } - - return extractResponse[int](response) -} - -// GetZone returns the zone information for the zone ID. -func (c *Client) GetZone(ctx context.Context, sessionID, zoneID string) (*Zone, error) { - payload := map[string]any{ - "session_id": sessionID, - "primary_id": zoneID, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return nil, err - } - - endpoint.RawQuery = "dns_zone_get" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return nil, err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return nil, err - } - - return extractResponse[*Zone](response) -} - -// GetTXT returns the TXT record for the given name. -// `name` must be a fully qualified domain name, e.g. "example.com.". -func (c *Client) GetTXT(ctx context.Context, sessionID, name string) (*Record, error) { - payload := GetTXTRequest{ - SessionID: sessionID, - PrimaryID: struct { - Name string `json:"name"` - Type string `json:"type"` - }{ - Name: name, - Type: "txt", - }, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return nil, err - } - - endpoint.RawQuery = "dns_txt_get" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return nil, err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return nil, err - } - - return extractResponse[*Record](response) -} - -// AddTXT adds a TXT record. -// It returns the ID of the newly created record. -func (c *Client) AddTXT(ctx context.Context, sessionID, clientID string, params RecordParams) (string, error) { - payload := AddTXTRequest{ - SessionID: sessionID, - ClientID: clientID, - Params: ¶ms, - UpdateSerial: true, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return "", err - } - - endpoint.RawQuery = "dns_txt_add" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return "", err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return "", err - } - - return extractResponse[string](response) -} - -// DeleteTXT deletes a TXT record. -// It returns the number of deleted records. -func (c *Client) DeleteTXT(ctx context.Context, sessionID, recordID string) (int, error) { - payload := DeleteTXTRequest{ - SessionID: sessionID, - PrimaryID: recordID, - UpdateSerial: true, - } - - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return 0, err - } - - endpoint.RawQuery = "dns_txt_delete" - - req, err := newJSONRequest(ctx, endpoint, payload) - if err != nil { - return 0, err - } - - var response APIResponse - - err = c.do(req, &response) - if err != nil { - return 0, err - } - - return extractResponse[int](response) -} - -func (c *Client) do(req *http.Request, result any) error { - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func extractResponse[T any](response APIResponse) (T, error) { - if response.Code != "ok" { - var zero T - - return zero, &APIError{APIResponse: response} - } - - var result T - - err := json.Unmarshal(response.Response, &result) - if err != nil { - var zero T - return zero, fmt.Errorf("unable to unmarshal response: %s, %w", string(response.Response), err) - } - - return result, nil -} diff --git a/providers/dns/ispconfig/internal/client_test.go b/providers/dns/ispconfig/internal/client_test.go deleted file mode 100644 index a4db3d5f7..000000000 --- a/providers/dns/ispconfig/internal/client_test.go +++ /dev/null @@ -1,175 +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) - if err != nil { - return nil, err - } - - client.HTTPClient = server.Client() - - return client, nil - }) -} - -func TestClient_Login(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("login.json"), - servermock.CheckRequestJSONBodyFromFixture("login-request.json"), - servermock.CheckQueryParameter().Strict(). - With("login", ""), - ). - Build(t) - - sessionID, err := client.Login(t.Context(), "user", "secret") - require.NoError(t, err) - - assert.Equal(t, "abc", sessionID) -} - -func TestClient_Login_error(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("error.json"), - ). - Build(t) - - _, err := client.Login(t.Context(), "user", "secret") - require.EqualError(t, err, `code: remote_fault, message: The login failed. Username or password wrong., response: false`) -} - -func TestClient_GetClientID(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("client_get_id.json"), - servermock.CheckRequestJSONBodyFromFixture("client_get_id-request.json"), - servermock.CheckQueryParameter().Strict(). - With("client_get_id", ""), - ). - Build(t) - - id, err := client.GetClientID(t.Context(), "sessionA", "sysA") - require.NoError(t, err) - - assert.Equal(t, 123, id) -} - -func TestClient_GetZoneID(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("dns_zone_get_id.json"), - servermock.CheckRequestJSONBodyFromFixture("dns_zone_get_id-request.json"), - servermock.CheckQueryParameter().Strict(). - With("dns_zone_get_id", ""), - ). - Build(t) - - zoneID, err := client.GetZoneID(t.Context(), "sessionA", "example.com") - require.NoError(t, err) - - assert.Equal(t, 123, zoneID) -} - -func TestClient_GetZone(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("dns_zone_get.json"), - servermock.CheckRequestJSONBodyFromFixture("dns_zone_get-request.json"), - servermock.CheckQueryParameter().Strict(). - With("dns_zone_get", ""), - ). - Build(t) - - zone, err := client.GetZone(t.Context(), "sessionA", "example.com.") - require.NoError(t, err) - - expected := &Zone{ - ID: "456", - ServerID: "123", - SysUserID: "789", - SysGroupID: "2", - Origin: "example.com.", - Serial: "2025102902", - Active: "Y", - } - - assert.Equal(t, expected, zone) -} - -func TestClient_GetTXT(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("dns_txt_get.json"), - servermock.CheckRequestJSONBodyFromFixture("dns_txt_get-request.json"), - servermock.CheckQueryParameter().Strict(). - With("dns_txt_get", ""), - ). - Build(t) - - record, err := client.GetTXT(t.Context(), "sessionA", "example.com.") - require.NoError(t, err) - - expected := &Record{ID: 123} - - assert.Equal(t, expected, record) -} - -func TestClient_AddTXT(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("dns_txt_add.json"), - servermock.CheckRequestJSONBodyFromFixture("dns_txt_add-request.json"), - servermock.CheckQueryParameter().Strict(). - With("dns_txt_add", ""), - ). - Build(t) - - now := time.Date(2025, 12, 25, 1, 1, 1, 0, time.UTC) - - params := RecordParams{ - ServerID: "serverA", - Zone: "example.com.", - Name: "foo.example.com.", - Type: "txt", - Data: "txtTXTtxt", - Aux: "0", - TTL: "3600", - Active: "y", - Stamp: now.Format("2006-01-02 15:04:05"), - UpdateSerial: true, - } - - recordID, err := client.AddTXT(t.Context(), "sessionA", "clientA", params) - require.NoError(t, err) - - assert.Equal(t, "123", recordID) -} - -func TestClient_DeleteTXT(t *testing.T) { - client := mockBuilder(). - Route("POST /", - servermock.ResponseFromFixture("dns_txt_delete.json"), - servermock.CheckRequestJSONBodyFromFixture("dns_txt_delete-request.json"), - servermock.CheckQueryParameter().Strict(). - With("dns_txt_delete", ""), - ). - Build(t) - - count, err := client.DeleteTXT(t.Context(), "sessionA", "123") - require.NoError(t, err) - - assert.Equal(t, 1, count) -} diff --git a/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json b/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json deleted file mode 100644 index ba573f824..000000000 --- a/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "session_id": "sessionA", - "sys_userid": "sysA" -} diff --git a/providers/dns/ispconfig/internal/fixtures/client_get_id.json b/providers/dns/ispconfig/internal/fixtures/client_get_id.json deleted file mode 100644 index 7b9f667a0..000000000 --- a/providers/dns/ispconfig/internal/fixtures/client_get_id.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": 123 -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json deleted file mode 100644 index bf5242cd1..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "session_id": "sessionA", - "client_id": "clientA", - "params": { - "server_id": "serverA", - "zone": "example.com.", - "name": "foo.example.com.", - "type": "txt", - "data": "txtTXTtxt", - "aux": "0", - "ttl": "3600", - "active": "y", - "stamp": "2025-12-25 01:01:01", - "update_serial": true - }, - "update_serial": true -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json deleted file mode 100644 index 7980619fe..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": "123" -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json deleted file mode 100644 index 240976654..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "session_id": "sessionA", - "primary_id": "123", - "update_serial": true -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json deleted file mode 100644 index 960b650bd..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": 1 -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json deleted file mode 100644 index 8bda44067..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "session_id": "sessionA", - "primary_id": { - "name": "example.com.", - "type": "txt" - } -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json deleted file mode 100644 index f707d50c3..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": { - "id": 123 - } -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json deleted file mode 100644 index 3d44d468f..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "primary_id": "example.com.", - "session_id": "sessionA" -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json deleted file mode 100644 index 37975d0e6..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": { - "id": "456", - "sys_userid": "789", - "sys_groupid": "2", - "sys_perm_user": "riud", - "sys_perm_group": "riud", - "sys_perm_other": "", - "server_id": "123", - "origin": "example.com.", - "ns": "ns1.example.org.", - "mbox": "support.example.net.", - "serial": "2025102902", - "refresh": "7200", - "retry": "540", - "expire": "604800", - "minimum": "3600", - "ttl": "3600", - "active": "Y", - "xfer": "", - "also_notify": "", - "update_acl": "", - "dnssec_initialized": "N", - "dnssec_wanted": "N", - "dnssec_algo": "ECDSAP256SHA256", - "dnssec_last_signed": "0", - "dnssec_info": "", - "rendered_zone": "" - } -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json deleted file mode 100644 index e3084242e..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "origin": "example.com", - "session_id": "sessionA" -} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json deleted file mode 100644 index 7b9f667a0..000000000 --- a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": 123 -} diff --git a/providers/dns/ispconfig/internal/fixtures/error.json b/providers/dns/ispconfig/internal/fixtures/error.json deleted file mode 100644 index a9c76546c..000000000 --- a/providers/dns/ispconfig/internal/fixtures/error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "remote_fault", - "message": "The login failed. Username or password wrong.", - "response": false -} diff --git a/providers/dns/ispconfig/internal/fixtures/login-request.json b/providers/dns/ispconfig/internal/fixtures/login-request.json deleted file mode 100644 index c3293a2e8..000000000 --- a/providers/dns/ispconfig/internal/fixtures/login-request.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "username": "user", - "password": "secret", - "client_login": false -} diff --git a/providers/dns/ispconfig/internal/fixtures/login.json b/providers/dns/ispconfig/internal/fixtures/login.json deleted file mode 100644 index e380a86ec..000000000 --- a/providers/dns/ispconfig/internal/fixtures/login.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "code": "ok", - "message": "foo", - "response": "abc" -} diff --git a/providers/dns/ispconfig/internal/readme.md b/providers/dns/ispconfig/internal/readme.md deleted file mode 100644 index 2284c338f..000000000 --- a/providers/dns/ispconfig/internal/readme.md +++ /dev/null @@ -1,249 +0,0 @@ -## Error Response - -```json -{ - "code": "", - "message": "", - "response": false -} -``` - -## Login Endpoint - -* URL: `?login` -* HTTP Method: `POST` - -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/login.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/login.php - -### Request Body (JSON) - -```json -{ - "username": "", - "password": "", - "client_login": false -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": "abc" -} -``` - -- `response`: is the `sessionID` - -## Get Client ID Endpoint - -* URL: `?client_get_id` -* HTTP Method: `POST` - -- function `client_get_id`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/client.inc.php#L97 -- TABLE `sys_user`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L1852 -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/client_get_id.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/client_get_id.php - -### Request Body (JSON) - -```json -{ - "session_id": "", - "sys_userid": "" -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": 123 -} -``` - -## DNS Zone Get ID Endpoint - -* URL: `?dns_zone_get_id` -* HTTP Method: `POST` - -- function `dns_zone_get_id`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L142 -- TABLE `dns_soa`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L615 - -### Request Body (JSON) - -```json -{ - "session_id": "", - "origin": "" -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": 123 -} -``` - -## DNS Zone Get Endpoint - -* URL: `?dns_zone_get` -* HTTP Method: `POST` - -- function `dns_zone_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L87 -- function `getDataRecord`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remoting_lib.inc.php#L248 -- TABLE `dns_soa`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L615 -- Depending on the request, the response may be an array or an object (`primary_id` can be a string, an array or an object). -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_zone_get.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_zone_get.php - -### Request Body (JSON) - -```json -{ - "session_id": "", - "primary_id": "" -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": { - "id": 456, - "server_id": 123, - "sys_userid": 789 - } -} -``` - -## DNS TXT Get Endpoint - -* URL: `?dns_txt_get` -* HTTP Method: `POST` - -- function `dns_txt_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L640 -- function `dns_rr_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L195 -- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php -- TABLE `dns_rr`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L490 -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_get.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_get.php - -### Request Body (JSON) - -```json -{ - "session_id": "", - "primary_id": { - "name": ".", - "type": "TXT" - } -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": { - "id": 123 - } -} -``` - -## DNS TXT Add Endpoint - -* URL: `?dns_txt_add` -* HTTP Method: `POST` - -- function `dns_txt_add`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L645 -- function `dns_rr_add` https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L212 -- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_add.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_add.php - -### Request Body (JSON) - -```json -{ - "session_id": "", - "client_id": "", - "params": { - "server_id": "", - "zone": "", - "name": ".", - "type": "txt", - "data": "", - "aux": "0", - "ttl": "3600", - "active": "y", - "stamp": "", - "update_serial": true - }, - "update_serial": true -} -``` - -- `stamp`: (ex: `2025-12-17 23:35:58`) -- `serial`: (ex: `1766010947`) - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": "123" -} -``` - -## DNS TXT Delete Endpoint - -* URL: `?dns_txt_delete` -* HTTP Method: `POST` - -- function `dns_txt_delete`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L655 -- function `dns_rr_delete`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L247 -- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_delete.html -- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_delete.php - -### Request Body (JSON) - -```json -{ - "session_id": "", - "primary_id": "", - "update_serial": true -} -``` - -### Response Body (JSON) - -```json -{ - "code": "ok", - "message": "foo", - "response": 1 -} -``` - ---- - -https://www.ispconfig.org/ -https://git.ispconfig.org/ispconfig/ispconfig3 -https://forum.howtoforge.com/#ispconfig-3.23 diff --git a/providers/dns/ispconfig/internal/types.go b/providers/dns/ispconfig/internal/types.go deleted file mode 100644 index 7db0846cc..000000000 --- a/providers/dns/ispconfig/internal/types.go +++ /dev/null @@ -1,95 +0,0 @@ -package internal - -import ( - "encoding/json" - "strings" -) - -type APIError struct { - APIResponse -} - -func (e *APIError) Error() string { - var msg strings.Builder - - msg.WriteString("code: " + e.Code) - - if e.Message != "" { - msg.WriteString(", message: " + e.Message) - } - - if len(e.Response) > 0 { - msg.WriteString(", response: " + string(e.Response)) - } - - return msg.String() -} - -type APIResponse struct { - Code string `json:"code"` - Message string `json:"message"` - Response json.RawMessage `json:"response"` -} - -type LoginRequest struct { - Username string `json:"username"` - Password string `json:"password"` - ClientLogin bool `json:"client_login"` -} - -type ClientIDRequest struct { - SessionID string `json:"session_id"` - SysUserID string `json:"sys_userid"` -} - -type Zone struct { - ID string `json:"id"` - ServerID string `json:"server_id"` - SysUserID string `json:"sys_userid"` - SysGroupID string `json:"sys_groupid"` - Origin string `json:"origin"` - Serial string `json:"serial"` - Active string `json:"active"` -} - -type GetTXTRequest struct { - SessionID string `json:"session_id"` - PrimaryID struct { - Name string `json:"name"` - Type string `json:"type"` - } `json:"primary_id"` -} - -type Record struct { - ID int `json:"id"` -} - -type AddTXTRequest struct { - SessionID string `json:"session_id"` - ClientID string `json:"client_id"` - Params *RecordParams `json:"params,omitempty"` - UpdateSerial bool `json:"update_serial"` -} - -type RecordParams struct { - ServerID string `json:"server_id"` - Zone string `json:"zone"` - Name string `json:"name"` - // 'a','aaaa','alias','cname','hinfo','mx','naptr','ns','ds','ptr','rp','srv','txt' - Type string `json:"type"` - Data string `json:"data"` - // "0" - Aux string `json:"aux"` - TTL string `json:"ttl"` - // 'n','y' - Active string `json:"active"` - // `2025-12-17 23:35:58` - Stamp string `json:"stamp"` - UpdateSerial bool `json:"update_serial"` -} - -type DeleteTXTRequest struct { - SessionID string `json:"session_id"` - PrimaryID string `json:"primary_id"` - UpdateSerial bool `json:"update_serial"` -} diff --git a/providers/dns/ispconfig/ispconfig.go b/providers/dns/ispconfig/ispconfig.go deleted file mode 100644 index 9396430b7..000000000 --- a/providers/dns/ispconfig/ispconfig.go +++ /dev/null @@ -1,220 +0,0 @@ -// Package ispconfig implements a DNS provider for solving the DNS-01 challenge using ISPConfig. -package ispconfig - -import ( - "context" - "crypto/tls" - "errors" - "fmt" - "net/http" - "strconv" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/ispconfig/internal" -) - -// Environment variables names. -const ( - envNamespace = "ISPCONFIG_" - - EnvServerURL = envNamespace + "SERVER_URL" - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" - EnvInsecureSkipVerify = envNamespace + "INSECURE_SKIP_VERIFY" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - ServerURL string - Username string - Password string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client - InsecureSkipVerify bool -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for ISPConfig. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvServerURL, EnvUsername, EnvPassword) - if err != nil { - return nil, fmt.Errorf("ispconfig: %w", err) - } - - config := NewDefaultConfig() - config.ServerURL = values[EnvServerURL] - config.Username = values[EnvUsername] - config.Password = values[EnvPassword] - config.InsecureSkipVerify = env.GetOrDefaultBool(EnvInsecureSkipVerify, false) - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for ISPConfig. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("ispconfig: the configuration of the DNS provider is nil") - } - - if config.ServerURL == "" { - return nil, errors.New("ispconfig: missing server URL") - } - - if config.Username == "" || config.Password == "" { - return nil, errors.New("ispconfig: credentials missing") - } - - client, err := internal.NewClient(config.ServerURL) - if err != nil { - return nil, fmt.Errorf("ispconfig: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - if config.InsecureSkipVerify { - client.HTTPClient.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - sessionID, err := d.client.Login(ctx, d.config.Username, d.config.Password) - if err != nil { - return fmt.Errorf("ispconfig: login: %w", err) - } - - zoneID, err := d.findZone(ctx, sessionID, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("ispconfig: get zone id: %w", err) - } - - zone, err := d.client.GetZone(ctx, sessionID, strconv.Itoa(zoneID)) - if err != nil { - return fmt.Errorf("ispconfig: get zone: %w", err) - } - - clientID, err := d.client.GetClientID(ctx, sessionID, zone.SysUserID) - if err != nil { - return fmt.Errorf("ispconfig: get client id: %w", err) - } - - params := internal.RecordParams{ - ServerID: "serverA", - Zone: zone.ID, - Name: info.EffectiveFQDN, - Type: "txt", - Data: info.Value, - Aux: "0", - TTL: strconv.Itoa(d.config.TTL), - Active: "y", - Stamp: time.Now().UTC().Format("2006-01-02 15:04:05"), - } - - recordID, err := d.client.AddTXT(ctx, sessionID, strconv.Itoa(clientID), params) - if err != nil { - return fmt.Errorf("ispconfig: add txt record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = recordID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("ispconfig: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - sessionID, err := d.client.Login(ctx, d.config.Username, d.config.Password) - if err != nil { - return fmt.Errorf("ispconfig: login: %w", err) - } - - _, err = d.client.DeleteTXT(ctx, sessionID, recordID) - if err != nil { - return fmt.Errorf("ispconfig: delete txt record: %w", err) - } - - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) findZone(ctx context.Context, sessionID, fqdn string) (int, error) { - for domain := range dns01.UnFqdnDomainsSeq(fqdn) { - zoneID, err := d.client.GetZoneID(ctx, sessionID, domain) - if err == nil { - return zoneID, nil - } - } - - return 0, fmt.Errorf("zone not found for %q", fqdn) -} diff --git a/providers/dns/ispconfig/ispconfig.toml b/providers/dns/ispconfig/ispconfig.toml deleted file mode 100644 index 4defd5509..000000000 --- a/providers/dns/ispconfig/ispconfig.toml +++ /dev/null @@ -1,27 +0,0 @@ -Name = "ISPConfig 3" -Description = '''''' -URL = "https://www.ispconfig.org/" -Code = "ispconfig" -Since = "v4.31.0" - -Example = ''' -ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ -ISPCONFIG_USERNAME="xxx" \ -ISPCONFIG_PASSWORD="yyy" \ -lego --dns ispconfig -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - ISPCONFIG_SERVER_URL = "Server URL" - ISPCONFIG_USERNAME = "Username" - ISPCONFIG_PASSWORD = "Password" - [Configuration.Additional] - ISPCONFIG_INSECURE_SKIP_VERIFY = "Whether to verify the API certificate" - ISPCONFIG_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - ISPCONFIG_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - ISPCONFIG_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - ISPCONFIG_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/index.html" diff --git a/providers/dns/ispconfig/ispconfig_test.go b/providers/dns/ispconfig/ispconfig_test.go deleted file mode 100644 index b03463aee..000000000 --- a/providers/dns/ispconfig/ispconfig_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package ispconfig - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvServerURL, - EnvUsername, - EnvPassword, -).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvServerURL: "https://example.com:80/", - EnvUsername: "user", - EnvPassword: "secret", - }, - }, - { - desc: "missing server URL", - envVars: map[string]string{ - EnvServerURL: "", - EnvUsername: "user", - EnvPassword: "secret", - }, - expected: "ispconfig: some credentials information are missing: ISPCONFIG_SERVER_URL", - }, - { - desc: "missing username", - envVars: map[string]string{ - EnvServerURL: "https://example.com:80/", - EnvUsername: "", - EnvPassword: "secret", - }, - expected: "ispconfig: some credentials information are missing: ISPCONFIG_USERNAME", - }, - { - desc: "missing password", - envVars: map[string]string{ - EnvServerURL: "https://example.com:80/", - EnvUsername: "user", - EnvPassword: "", - }, - expected: "ispconfig: some credentials information are missing: ISPCONFIG_PASSWORD", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "ispconfig: some credentials information are missing: ISPCONFIG_SERVER_URL,ISPCONFIG_USERNAME,ISPCONFIG_PASSWORD", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - serverURL string - username string - password string - expected string - }{ - { - desc: "success", - serverURL: "https://example.com:80/", - username: "user", - password: "secret", - }, - { - desc: "missing server URL", - username: "user", - password: "secret", - expected: "ispconfig: missing server URL", - }, - { - desc: "missing username", - serverURL: "https://example.com:80/", - password: "secret", - expected: "ispconfig: credentials missing", - }, - { - desc: "missing password", - serverURL: "https://example.com:80/", - username: "user", - expected: "ispconfig: credentials missing", - }, - { - desc: "missing credentials", - expected: "ispconfig: missing server URL", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.ServerURL = test.serverURL - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/ispconfigddns/internal/client.go b/providers/dns/ispconfigddns/internal/client.go deleted file mode 100644 index 700b58f89..000000000 --- a/providers/dns/ispconfigddns/internal/client.go +++ /dev/null @@ -1,111 +0,0 @@ -package internal - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "github.com/go-acme/lego/v4/providers/dns/internal/useragent" - querystring "github.com/google/go-querystring/query" -) - -const ( - addAction = "add" - deleteAction = "delete" -) - -type Client struct { - token string - serverURL string - - HTTPClient *http.Client -} - -func NewClient(serverURL, token string) (*Client, error) { - _, err := url.Parse(serverURL) - if err != nil { - return nil, fmt.Errorf("server URL: %w", err) - } - - return &Client{ - serverURL: serverURL, - token: token, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) AddTXTRecord(ctx context.Context, zone, fqdn, content string) error { - return c.updateRecord(ctx, UpdateRecord{Action: addAction, Zone: zone, Type: "TXT", Record: fqdn, Data: content}) -} - -func (c *Client) DeleteTXTRecord(ctx context.Context, zone, fqdn, recordContent string) error { - return c.updateRecord(ctx, UpdateRecord{Action: deleteAction, Zone: zone, Type: "TXT", Record: fqdn, Data: recordContent}) -} - -func (c *Client) updateRecord(ctx context.Context, action UpdateRecord) error { - req, err := c.newRequest(ctx, action) - if err != nil { - return err - } - - return c.do(req) -} - -func (c *Client) do(req *http.Request) error { - useragent.SetHeader(req.Header) - - req.SetBasicAuth("anonymous", c.token) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - // The endpoint uses the `DefaultDdnsResponseWriter`, - // and this writer uses HTTP status code to determine if the request was successful or not. - // - https://github.com/mhofer117/ispconfig-ddns-module/blob/8b011a5bb138881d9f13360a5c4fec10c0084613/lib/updater/DdnsUpdater.php#L53-L57 - // - https://github.com/mhofer117/ispconfig-ddns-module/blob/master/lib/updater/response/DefaultDdnsResponseWriter.php - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return nil -} - -func (c *Client) newRequest(ctx context.Context, action UpdateRecord) (*http.Request, error) { - endpoint, err := url.Parse(c.serverURL) - if err != nil { - return nil, err - } - - endpoint = endpoint.JoinPath("ddns", "update.php") - - values, err := querystring.Values(action) - if err != nil { - return nil, err - } - - endpoint.RawQuery = values.Encode() - - method := http.MethodPost - if action.Action == deleteAction { - method = http.MethodDelete - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), nil) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", "application/json") - - return req, nil -} diff --git a/providers/dns/ispconfigddns/internal/client_test.go b/providers/dns/ispconfigddns/internal/client_test.go deleted file mode 100644 index 774e5ee46..000000000 --- a/providers/dns/ispconfigddns/internal/client_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package internal - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -func setupClient(server *httptest.Server) (*Client, error) { - client, err := NewClient(server.URL, "secret") - if err != nil { - return nil, err - } - - client.HTTPClient = server.Client() - - return client, nil -} - -func TestClient_AddTXTRecord(t *testing.T) { - client := servermock.NewBuilder[*Client](setupClient). - Route("POST /ddns/update.php", - servermock.Noop(), - servermock.CheckHeader(). - WithBasicAuth("anonymous", "secret"), - servermock.CheckQueryParameter().Strict(). - With("action", "add"). - With("zone", "example.com"). - With("type", "TXT"). - With("record", "_acme-challenge.example.com."). - With("data", "token"), - ). - Build(t) - - err := client.AddTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") - require.NoError(t, err) -} - -func TestClient_AddTXTRecord_error(t *testing.T) { - client := servermock.NewBuilder[*Client](setupClient). - Route("POST /ddns/update.php", - servermock.RawStringResponse("Missing or invalid token."). - WithStatusCode(http.StatusUnauthorized), - ). - Build(t) - - err := client.AddTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") - require.EqualError(t, err, "unexpected status code: [status code: 401] body: Missing or invalid token.") -} - -func TestClient_DeleteTXTRecord(t *testing.T) { - client := servermock.NewBuilder[*Client](setupClient). - Route("DELETE /ddns/update.php", - servermock.Noop(), - servermock.CheckHeader(). - WithBasicAuth("anonymous", "secret"), - servermock.CheckQueryParameter().Strict(). - With("action", "delete"). - With("zone", "example.com"). - With("type", "TXT"). - With("record", "_acme-challenge.example.com."). - With("data", "token"), - ). - Build(t) - - err := client.DeleteTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") - require.NoError(t, err) -} - -func TestClient_DeleteTXTRecord_error(t *testing.T) { - client := servermock.NewBuilder[*Client](setupClient). - Route("DELETE /ddns/update.php", - servermock.RawStringResponse("Missing or invalid token."). - WithStatusCode(http.StatusUnauthorized), - ). - Build(t) - - err := client.DeleteTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") - require.EqualError(t, err, "unexpected status code: [status code: 401] body: Missing or invalid token.") -} diff --git a/providers/dns/ispconfigddns/internal/types.go b/providers/dns/ispconfigddns/internal/types.go deleted file mode 100644 index 278738108..000000000 --- a/providers/dns/ispconfigddns/internal/types.go +++ /dev/null @@ -1,9 +0,0 @@ -package internal - -type UpdateRecord struct { - Action string `url:"action,omitempty"` - Zone string `url:"zone,omitempty"` - Type string `url:"type,omitempty"` - Record string `url:"record,omitempty"` - Data string `url:"data,omitempty"` -} diff --git a/providers/dns/ispconfigddns/ispconfigddns.go b/providers/dns/ispconfigddns/ispconfigddns.go deleted file mode 100644 index eab5d413f..000000000 --- a/providers/dns/ispconfigddns/ispconfigddns.go +++ /dev/null @@ -1,145 +0,0 @@ -// Package ispconfigddns implements a DNS provider for solving the DNS-01 challenge using ISPConfig 3 Dynamic DNS (DDNS) Module. -package ispconfigddns - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/ispconfigddns/internal" -) - -// Environment variables names. -const ( - envNamespace = "ISPCONFIG_DDNS_" - - EnvServerURL = envNamespace + "SERVER_URL" - EnvToken = envNamespace + "TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - ServerURL string - Token string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 3600), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for ISPConfig 3 Dynamic DNS (DDNS) Module. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvServerURL, EnvToken) - if err != nil { - return nil, fmt.Errorf("ispconfig (DDNS module): %w", err) - } - - config := NewDefaultConfig() - config.ServerURL = values[EnvServerURL] - config.Token = values[EnvToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for ISPConfig 3 Dynamic DNS (DDNS) Module. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("ispconfig (DDNS module): the configuration of the DNS provider is nil") - } - - if config.ServerURL == "" { - return nil, errors.New("ispconfig (DDNS module): missing server URL") - } - - if config.Token == "" { - return nil, errors.New("ispconfig (DDNS module): missing token") - } - - client, err := internal.NewClient(config.ServerURL, config.Token) - if err != nil { - return nil, fmt.Errorf("ispconfig (DDNS module): %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to control checking compliance to spec. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -// Present creates a TXT record to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("ispconfig (DDNS module): could not find zone for domain %q: %w", domain, err) - } - - err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(zone), info.EffectiveFQDN, info.Value) - if err != nil { - return fmt.Errorf("ispconfig (DDNS module): add record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("ispconfig (DDNS module): could not find zone for domain %q: %w", domain, err) - } - - err = d.client.DeleteTXTRecord(context.Background(), dns01.UnFqdn(zone), info.EffectiveFQDN, info.Value) - if err != nil { - return fmt.Errorf("ispconfig (DDNS module): delete record: %w", err) - } - - return nil -} diff --git a/providers/dns/ispconfigddns/ispconfigddns.toml b/providers/dns/ispconfigddns/ispconfigddns.toml deleted file mode 100644 index 158ee9fbd..000000000 --- a/providers/dns/ispconfigddns/ispconfigddns.toml +++ /dev/null @@ -1,32 +0,0 @@ -Name = "ISPConfig 3 - Dynamic DNS (DDNS) Module" -Description = '''''' -URL = "https://www.ispconfig.org/" -Code = "ispconfigddns" -Since = "v4.31.0" - -Example = ''' -ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ -ISPCONFIG_DDNS_TOKEN=xxxxxx \ -lego --dns ispconfigddns -d '*.example.com' -d example.com run -''' - -Additional = ''' -ISPConfig DNS provider supports leveraging the [ISPConfig 3 Dynamic DNS (DDNS) Module](https://github.com/mhofer117/ispconfig-ddns-module). - -Requires the DDNS module described at https://www.ispconfig.org/ispconfig/download/ - -See https://www.howtoforge.com/community/threads/ispconfig-3-danymic-dns-ddns-module.87967/ for additional details. -''' - -[Configuration] - [Configuration.Credentials] - ISPCONFIG_DDNS_SERVER_URL = "API server URL (ex: https://panel.example.com:8080)" - ISPCONFIG_DDNS_TOKEN = "DDNS API token" - [Configuration.Additional] - ISPCONFIG_DDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - ISPCONFIG_DDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - ISPCONFIG_DDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" - ISPCONFIG_DDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://github.com/mhofer117/ispconfig-ddns-module/tree/master/lib/updater" diff --git a/providers/dns/ispconfigddns/ispconfigddns_test.go b/providers/dns/ispconfigddns/ispconfigddns_test.go deleted file mode 100644 index 58e7a8f54..000000000 --- a/providers/dns/ispconfigddns/ispconfigddns_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package ispconfigddns - -import ( - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvServerURL, EnvToken). - WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvServerURL: "https://example.com", - EnvToken: "secret", - }, - }, - { - desc: "missing server URL", - envVars: map[string]string{ - EnvServerURL: "", - EnvToken: "secret", - }, - expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_SERVER_URL", - }, - { - desc: "missing token", - envVars: map[string]string{ - EnvServerURL: "https://example.com", - EnvToken: "", - }, - expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_TOKEN", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_SERVER_URL,ISPCONFIG_DDNS_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - serverURL string - token string - expected string - }{ - { - desc: "success", - serverURL: "https://example.com", - token: "secret", - }, - { - desc: "missing server URL", - serverURL: "", - token: "secret", - expected: "ispconfig (DDNS module): missing server URL", - }, - { - desc: "missing token", - serverURL: "https://example.com", - token: "", - expected: "ispconfig (DDNS module): missing token", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.ServerURL = test.serverURL - config.Token = test.token - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.HTTPClient = server.Client() - config.Token = "secret" - config.ServerURL = server.URL - - return NewDNSProviderConfig(config) - }, - servermock.CheckHeader(). - WithBasicAuth("anonymous", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /ddns/update.php", - servermock.DumpRequest(), - servermock.CheckQueryParameter().Strict(). - With("action", "add"). - With("zone", "example.com"). - With("type", "TXT"). - With("record", "_acme-challenge.example.com."). - With("data", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), - ). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /ddns/update.php", - servermock.DumpRequest(), - servermock.CheckQueryParameter().Strict(). - With("action", "delete"). - With("zone", "example.com"). - With("type", "TXT"). - With("record", "_acme-challenge.example.com."). - With("data", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), - ). - Build(t) - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/iwantmyname/iwantmyname.toml b/providers/dns/iwantmyname/iwantmyname.toml index a82c2b749..a138dee9e 100644 --- a/providers/dns/iwantmyname/iwantmyname.toml +++ b/providers/dns/iwantmyname/iwantmyname.toml @@ -11,7 +11,7 @@ Since = "v4.7.0" Example = ''' IWANTMYNAME_USERNAME=xxxxxxxx \ IWANTMYNAME_PASSWORD=xxxxxxxx \ -lego --dns iwantmyname -d '*.example.com' -d example.com run +lego --email you@example.com --dns iwantmyname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/jdcloud/fixtures/create_record-request.json b/providers/dns/jdcloud/fixtures/create_record-request.json deleted file mode 100644 index 581c00fea..000000000 --- a/providers/dns/jdcloud/fixtures/create_record-request.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "domainId": "20", - "regionId": "cn-north-1", - "req": { - "hostRecord": "_acme-challenge", - "hostValue": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "jcloudRes": null, - "mxPriority": null, - "port": null, - "ttl": 120, - "type": "TXT", - "viewValue": -1, - "weight": null - } -} diff --git a/providers/dns/jdcloud/fixtures/create_record.json b/providers/dns/jdcloud/fixtures/create_record.json deleted file mode 100644 index 08bd3db26..000000000 --- a/providers/dns/jdcloud/fixtures/create_record.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "requestId": "azerty", - "error": { - "code": 0, - "status": "", - "message": "" - }, - "result": { - "dataList": { - "id": 123, - "hostRecord": "_acme-challenge", - "hostValue": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "jcloudRes": false, - "mxPriority": 0, - "port": 0, - "ttl": 120, - "type": "TXT", - "weight": 0, - "viewValue": [ - 1, - 2 - ] - } - } -} diff --git a/providers/dns/jdcloud/fixtures/delete_record.json b/providers/dns/jdcloud/fixtures/delete_record.json deleted file mode 100644 index 20525751c..000000000 --- a/providers/dns/jdcloud/fixtures/delete_record.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "requestId": "azerty", - "error": { - "code": 0, - "status": "", - "message": "" - }, - "result": {} -} diff --git a/providers/dns/jdcloud/fixtures/describe_domains_page1.json b/providers/dns/jdcloud/fixtures/describe_domains_page1.json deleted file mode 100644 index cde6dcd6f..000000000 --- a/providers/dns/jdcloud/fixtures/describe_domains_page1.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "requestId": "azerty", - "error": { - "code": 0, - "status": "", - "message": "" - }, - "result": { - "dataList": [ - { - "id": 1, - "domainName": "1.example" - }, - { - "id": 2, - "domainName": "2.example" - }, - { - "id": 3, - "domainName": "3.example" - }, - { - "id": 4, - "domainName": "4.example" - }, - { - "id": 5, - "domainName": "5.example" - }, - { - "id": 6, - "domainName": "6.example" - }, - { - "id": 7, - "domainName": "7.example" - }, - { - "id": 8, - "domainName": "8.example" - }, - { - "id": 9, - "domainName": "9.example" - }, - { - "id": 10, - "domainName": "10.example" - } - ], - "currentCount": 10, - "totalCount": 20, - "totalPage": 2 - } -} diff --git a/providers/dns/jdcloud/fixtures/describe_domains_page2.json b/providers/dns/jdcloud/fixtures/describe_domains_page2.json deleted file mode 100644 index b1e1560ab..000000000 --- a/providers/dns/jdcloud/fixtures/describe_domains_page2.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "requestId": "azerty", - "error": { - "code": 0, - "status": "", - "message": "" - }, - "result": { - "dataList": [ - { - "id": 11, - "domainName": "11.example" - }, - { - "id": 12, - "domainName": "12.example" - }, - { - "id": 13, - "domainName": "13.example" - }, - { - "id": 14, - "domainName": "14.example" - }, - { - "id": 15, - "domainName": "15.example" - }, - { - "id": 16, - "domainName": "16.example" - }, - { - "id": 17, - "domainName": "17.example" - }, - { - "id": 18, - "domainName": "18.example" - }, - { - "id": 19, - "domainName": "19.example" - }, - { - "id": 20, - "domainName": "example.com" - } - ], - "currentCount": 10, - "totalCount": 20, - "totalPage": 2 - } -} diff --git a/providers/dns/jdcloud/jdcloud.go b/providers/dns/jdcloud/jdcloud.go deleted file mode 100644 index 7d9ad4e6b..000000000 --- a/providers/dns/jdcloud/jdcloud.go +++ /dev/null @@ -1,217 +0,0 @@ -// Package jdcloud implements a DNS provider for solving the DNS-01 challenge using JD Cloud. -package jdcloud - -import ( - "errors" - "fmt" - "strconv" - "sync" - "time" - - "github.com/go-acme/jdcloud-sdk-go/core" - "github.com/go-acme/jdcloud-sdk-go/services/domainservice/apis" - jdcclient "github.com/go-acme/jdcloud-sdk-go/services/domainservice/client" - domainservice "github.com/go-acme/jdcloud-sdk-go/services/domainservice/models" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" -) - -// Environment variables names. -const ( - envNamespace = "JDCLOUD_" - - EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID" - EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET" - EnvRegionID = envNamespace + "REGION_ID" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - AccessKeyID string - AccessKeySecret string - RegionID string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *jdcclient.DomainserviceClient - - recordIDs map[string]int - domainIDs map[string]int - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for JD Cloud. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAccessKeyID, EnvAccessKeySecret) - if err != nil { - return nil, fmt.Errorf("jdcloud: %w", err) - } - - config := NewDefaultConfig() - config.AccessKeyID = values[EnvAccessKeyID] - config.AccessKeySecret = values[EnvAccessKeySecret] - - // https://docs.jdcloud.com/en/common-declaration/api/introduction#Region%20Code - config.RegionID = env.GetOrDefaultString(EnvRegionID, "cn-north-1") - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for JD Cloud. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("jdcloud: the configuration of the DNS provider is nil") - } - - if config.AccessKeyID == "" || config.AccessKeySecret == "" { - return nil, errors.New("jdcloud: missing credentials") - } - - cred := core.NewCredentials(config.AccessKeyID, config.AccessKeySecret) - - client := jdcclient.NewDomainserviceClient(cred) - client.DisableLogger() - client.Config.SetTimeout(config.HTTPTimeout) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int), - domainIDs: make(map[string]int), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("jdcloud: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("jdcloud: %w", err) - } - - zone, err := d.findZone(dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("jdcloud: %w", err) - } - - // https://docs.jdcloud.com/cn/jd-cloud-dns/api/createresourcerecord - crrr := apis.NewCreateResourceRecordRequestWithAllParams( - d.config.RegionID, - strconv.Itoa(zone.Id), - &domainservice.AddRR{ - HostRecord: subDomain, - HostValue: info.Value, - Ttl: d.config.TTL, - Type: "TXT", - ViewValue: -1, - }, - ) - - record, err := jdcclient.CreateResourceRecord(d.client, crrr) - if err != nil { - return fmt.Errorf("jdcloud: create resource record: %w", err) - } - - d.recordIDsMu.Lock() - d.domainIDs[token] = zone.Id - d.recordIDs[token] = record.Result.DataList.Id - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - d.recordIDsMu.Lock() - recordID, recordOK := d.recordIDs[token] - domainID, domainOK := d.domainIDs[token] - d.recordIDsMu.Unlock() - - if !recordOK { - return fmt.Errorf("jdcloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - if !domainOK { - return fmt.Errorf("jdcloud: unknown domain ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - // https://docs.jdcloud.com/cn/jd-cloud-dns/api/deleteresourcerecord - drrr := apis.NewDeleteResourceRecordRequestWithAllParams( - d.config.RegionID, - strconv.Itoa(domainID), - strconv.Itoa(recordID), - ) - - _, err := jdcclient.DeleteResourceRecord(d.client, drrr) - if err != nil { - return fmt.Errorf("jdcloud: delete resource record: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) findZone(zone string) (*domainservice.DomainInfo, error) { - // https://docs.jdcloud.com/cn/jd-cloud-dns/api/describedomains - ddr := apis.NewDescribeDomainsRequestWithoutParam() - ddr.SetRegionId(d.config.RegionID) - ddr.SetPageNumber(1) - ddr.SetPageSize(10) - ddr.SetDomainName(zone) - - for { - response, err := jdcclient.DescribeDomains(d.client, ddr) - if err != nil { - return nil, fmt.Errorf("describe domains: %w", err) - } - - for _, d := range response.Result.DataList { - if d.DomainName == zone { - return &d, nil - } - } - - if len(response.Result.DataList) < ddr.PageSize || response.Result.TotalPage <= ddr.PageNumber { - break - } - - ddr.SetPageNumber(ddr.PageNumber + 1) - } - - return nil, errors.New("zone not found") -} diff --git a/providers/dns/jdcloud/jdcloud.toml b/providers/dns/jdcloud/jdcloud.toml deleted file mode 100644 index 7ab403822..000000000 --- a/providers/dns/jdcloud/jdcloud.toml +++ /dev/null @@ -1,27 +0,0 @@ -Name = "JD Cloud" -Description = '''''' -URL = "https://www.jdcloud.com/" -Code = "jdcloud" -Since = "v4.31.0" - -Example = ''' -JDCLOUD_ACCESS_KEY_ID="xxx" \ -JDCLOUD_ACCESS_KEY_SECRET="yyy" \ -lego --dns jdcloud -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - JDCLOUD_ACCESS_KEY_ID = "Access key ID" - JDCLOUD_ACCESS_KEY_SECRET = "Access key secret" - [Configuration.Additional] - JDCLOUD_REGION_ID = "Region ID (Default: cn-north-1)" - JDCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - JDCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - JDCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - JDCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://docs.jdcloud.com/cn/jd-cloud-dns/api/overview" - Common = "https://docs.jdcloud.com/en/common-declaration/api/introduction" - GoClient = "https://github.com/jdcloud-api/jdcloud-sdk-go" diff --git a/providers/dns/jdcloud/jdcloud_test.go b/providers/dns/jdcloud/jdcloud_test.go deleted file mode 100644 index 6b3368938..000000000 --- a/providers/dns/jdcloud/jdcloud_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package jdcloud - -import ( - "fmt" - "net" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvAccessKeyID, - EnvAccessKeySecret, - EnvRegionID, -).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAccessKeyID: "abc123", - EnvAccessKeySecret: "secret", - }, - }, - { - desc: "missing access key ID", - envVars: map[string]string{ - EnvAccessKeyID: "", - EnvAccessKeySecret: "secret", - }, - expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_ID", - }, - { - desc: "missing access key secret", - envVars: map[string]string{ - EnvAccessKeyID: "abc123", - EnvAccessKeySecret: "", - }, - expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_SECRET", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_ID,JDCLOUD_ACCESS_KEY_SECRET", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - accessKeyID string - accessKeySecret string - expected string - }{ - { - desc: "success", - accessKeyID: "abc123", - accessKeySecret: "secret", - }, - { - desc: "missing access key ID", - accessKeySecret: "secret", - expected: "jdcloud: missing credentials", - }, - { - desc: "missing access key secret", - accessKeyID: "abc123", - expected: "jdcloud: missing credentials", - }, - { - desc: "missing credentials", - expected: "jdcloud: missing credentials", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.AccessKeyID = test.accessKeyID - config.AccessKeySecret = test.accessKeySecret - config.RegionID = "cn-north-1" - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.AccessKeyID = "abc123" - config.AccessKeySecret = "secret" - config.RegionID = "cn-north-1" - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - serverURL, _ := url.Parse(server.URL) - - p.client.Config.SetEndpoint(net.JoinHostPort(serverURL.Hostname(), serverURL.Port())) - p.client.Config.SetScheme(serverURL.Scheme) - p.client.Config.SetTimeout(server.Client().Timeout) - - return p, nil - }, - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /v2/regions/cn-north-1/domain", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - pageNumber := req.URL.Query().Get("pageNumber") - - servermock.ResponseFromFixture( - fmt.Sprintf("describe_domains_page%s.json", pageNumber), - ).ServeHTTP(rw, req) - }), - servermock.CheckQueryParameter().Strict(). - With("domainName", "example.com"). - WithRegexp("pageNumber", `(1|2)`). - With("pageSize", "10"), - servermock.CheckHeader(). - WithRegexp("Authorization", - `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). - WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). - WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), - ). - Route("POST /v2/regions/cn-north-1/domain/20/ResourceRecord", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json"), - servermock.CheckHeader(). - WithRegexp("Authorization", - `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). - WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). - WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), - ). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) - - require.Len(t, provider.domainIDs, 1) - require.Len(t, provider.recordIDs, 1) - - assert.Equal(t, 20, provider.domainIDs["abc"]) - assert.Equal(t, 123, provider.recordIDs["abc"]) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /v2/regions/cn-north-1/domain/20/ResourceRecord/123", - servermock.ResponseFromFixture("delete_record.json"), - servermock.CheckHeader(). - WithRegexp("Authorization", - `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). - WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). - WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), - ). - Build(t) - - provider.domainIDs["abc"] = 20 - provider.recordIDs["abc"] = 123 - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/joker/joker.toml b/providers/dns/joker/joker.toml index 20e481a6d..35713df18 100644 --- a/providers/dns/joker/joker.toml +++ b/providers/dns/joker/joker.toml @@ -9,17 +9,17 @@ Example = ''' JOKER_API_MODE=SVC \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run # DMAPI JOKER_API_MODE=DMAPI \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run ## or JOKER_API_MODE=DMAPI \ JOKER_API_KEY= \ -lego --dns joker -d '*.example.com' -d example.com run +lego --email you@example.com --dns joker -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/keyhelp/keyhelp.toml b/providers/dns/keyhelp/keyhelp.toml index e622794ca..d6f84e34e 100644 --- a/providers/dns/keyhelp/keyhelp.toml +++ b/providers/dns/keyhelp/keyhelp.toml @@ -7,7 +7,7 @@ Since = "v4.26.0" Example = ''' KEYHELP_BASE_URL="https://keyhelp.example.com" \ KEYHELP_API_KEY="xxx" \ -lego --dns keyhelp -d '*.example.com' -d example.com run +lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run ''' [Configuration] 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..1259999a2 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -6,14 +6,13 @@ Since = "v4.10.0" Example = ''' LIARA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns liara -d '*.example.com' -d example.com run +lego --email you@example.com --dns liara -d '*.example.com' -d example.com run ''' [Configuration] [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/limacity/limacity.go b/providers/dns/limacity/limacity.go index 3291faf66..9e1f58f1a 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -193,10 +193,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("limacity: delete record (domain ID=%d, record ID=%d): %w", domainID, recordID, err) } - d.domainIDsMu.Lock() - delete(d.domainIDs, info.EffectiveFQDN) - d.domainIDsMu.Unlock() - return nil } diff --git a/providers/dns/limacity/limacity.toml b/providers/dns/limacity/limacity.toml index d236577d0..b9b9f0018 100644 --- a/providers/dns/limacity/limacity.toml +++ b/providers/dns/limacity/limacity.toml @@ -6,7 +6,7 @@ Since = "v4.18.0" Example = ''' LIMACITY_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns limacity -d '*.example.com' -d example.com run +lego --email you@example.com --dns limacity -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/linode/linode.toml b/providers/dns/linode/linode.toml index 9ea30b92b..f046d3f9b 100644 --- a/providers/dns/linode/linode.toml +++ b/providers/dns/linode/linode.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' LINODE_TOKEN=xxxxx \ -lego --dns linode -d '*.example.com' -d example.com run +lego --email you@example.com --dns linode -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go index 6e93e2a12..b56968fe3 100644 --- a/providers/dns/liquidweb/liquidweb.go +++ b/providers/dns/liquidweb/liquidweb.go @@ -62,9 +62,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *lw.API - + config *Config + client *lw.API recordIDs map[string]int recordIDsMu sync.Mutex } diff --git a/providers/dns/liquidweb/liquidweb.toml b/providers/dns/liquidweb/liquidweb.toml index 386b99cab..22789f41e 100644 --- a/providers/dns/liquidweb/liquidweb.toml +++ b/providers/dns/liquidweb/liquidweb.toml @@ -7,7 +7,7 @@ Since = "v3.1.0" Example = ''' LWAPI_USERNAME=someuser \ LWAPI_PASSWORD=somepass \ -lego --dns liquidweb -d '*.example.com' -d example.com run +lego --email you@example.com --dns liquidweb -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/liquidweb/liquidweb_test.go b/providers/dns/liquidweb/liquidweb_test.go index a34d19037..26dc5bdc0 100644 --- a/providers/dns/liquidweb/liquidweb_test.go +++ b/providers/dns/liquidweb/liquidweb_test.go @@ -27,16 +27,16 @@ func TestNewDNSProvider(t *testing.T) { { desc: "minimum-success", envVars: map[string]string{ - EnvUsername: "user", - EnvPassword: "secret", + EnvUsername: "blars", + EnvPassword: "tacoman", }, }, { desc: "set-everything", envVars: map[string]string{ - EnvURL: "https://storm.example", - EnvUsername: "user", - EnvPassword: "secret", + EnvURL: "https://storm.com", + EnvUsername: "blars", + EnvPassword: "tacoman", EnvZone: "blars.com", }, }, @@ -48,16 +48,16 @@ func TestNewDNSProvider(t *testing.T) { { desc: "missing username", envVars: map[string]string{ - EnvPassword: "secret", - EnvZone: "blars.example", + EnvPassword: "tacoman", + EnvZone: "blars.com", }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME", }, { desc: "missing password", envVars: map[string]string{ - EnvUsername: "user", - EnvZone: "blars.example", + EnvUsername: "blars", + EnvZone: "blars.com", }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_PASSWORD", }, @@ -148,13 +148,13 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { provider := mockProvider(t) - err := provider.Present("tacoman.example", "", "") + err := provider.Present("tacoman.com", "", "") require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { provider := mockProvider(t, network.DNSRecord{ - Name: "_acme-challenge.tacoman.example", + Name: "_acme-challenge.tacoman.com", RData: "123d==", Type: "TXT", TTL: 300, @@ -164,7 +164,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider.recordIDs["123d=="] = 1234567 - err := provider.CleanUp("tacoman.example.", "123d==", "") + err := provider.CleanUp("tacoman.com.", "123d==", "") require.NoError(t, err) } @@ -181,7 +181,7 @@ func TestDNSProvider(t *testing.T) { }{ { desc: "expected successful", - domain: "tacoman.example", + domain: "tacoman.com", token: "123", keyAuth: "456", present: true, @@ -189,7 +189,7 @@ func TestDNSProvider(t *testing.T) { }, { desc: "other successful", - domain: "banana.example", + domain: "banana.com", token: "123", keyAuth: "456", present: true, @@ -197,16 +197,16 @@ func TestDNSProvider(t *testing.T) { }, { desc: "zone not on account", - domain: "huckleberry.example", + domain: "huckleberry.com", token: "123", keyAuth: "456", present: true, - expPresentErr: "no valid zone in account for certificate '_acme-challenge.huckleberry.example'", + expPresentErr: "no valid zone in account for certificate '_acme-challenge.huckleberry.com'", cleanup: false, }, { desc: "ssl for domain", - domain: "sundae.cherry.example", + domain: "sundae.cherry.com", token: "5847953", keyAuth: "34872934", present: true, @@ -214,7 +214,7 @@ func TestDNSProvider(t *testing.T) { }, { desc: "complicated domain", - domain: "always.money.stand.banana.example", + domain: "always.money.stand.banana.com", token: "5847953", keyAuth: "there is always money in the banana stand", present: true, diff --git a/providers/dns/liquidweb/servermock_test.go b/providers/dns/liquidweb/servermock_test.go index 4886e17f1..f211e7253 100644 --- a/providers/dns/liquidweb/servermock_test.go +++ b/providers/dns/liquidweb/servermock_test.go @@ -26,14 +26,14 @@ func mockProvider(t *testing.T, initRecs ...network.DNSRecord) *DNSProvider { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.Username = "user" - config.Password = "secret" + config.Username = "blars" + config.Password = "tacoman" config.BaseURL = server.URL return NewDNSProviderConfig(config) }, servermock.CheckHeader(). - WithBasicAuth("user", "secret"), + WithBasicAuth("blars", "tacoman"), ). Route("/v1/Network/DNS/Record/delete", mockAPIDelete(recs)). Route("/v1/Network/DNS/Record/create", mockAPICreate(recs)). @@ -172,38 +172,38 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 1, - Name: "blars.example", + Name: "blars.com", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 2, - Name: "tacoman.example", + Name: "tacoman.com", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 3, - Name: "storm.example", + Name: "storm.com", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 4, - Name: "not-apple.example", + Name: "not-apple.com", Active: 1, DelegationStatus: "BAD_NAMESERVERS", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 5, Name: "example.com", Active: 1, DelegationStatus: "BAD_NAMESERVERS", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, }, }, @@ -211,38 +211,38 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 6, - Name: "banana.example", + Name: "banana.com", Active: 1, DelegationStatus: "NXDOMAIN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 7, - Name: "cherry.example", + Name: "cherry.com", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 8, - Name: "dates.example", + Name: "dates.com", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 9, - Name: "eggplant.example", + Name: "eggplant.com", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 10, - Name: "fig.example", + Name: "fig.com", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, }, }, @@ -250,31 +250,31 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 11, - Name: "grapes.example", + Name: "grapes.com", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 12, - Name: "money.banana.example", + Name: "money.banana.com", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 13, - Name: "money.stand.banana.example", + Name: "money.stand.banana.com", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, { ID: 14, - Name: "stand.banana.example", + Name: "stand.banana.com", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.example.org", + PrimaryNameserver: "ns.liquidweb.com", }, }, }, diff --git a/providers/dns/loopia/loopia.toml b/providers/dns/loopia/loopia.toml index a201852c9..4a127ec55 100644 --- a/providers/dns/loopia/loopia.toml +++ b/providers/dns/loopia/loopia.toml @@ -7,7 +7,7 @@ Since = "v4.2.0" Example = ''' LOOPIA_API_USER=xxxxxxxx \ LOOPIA_API_PASSWORD=yyyyyyyy \ -lego --dns loopia -d '*.example.com' -d example.com run +lego --email you@example.com --dns loopia -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/luadns/luadns.go b/providers/dns/luadns/luadns.go index 68b9c66b8..02108ce62 100644 --- a/providers/dns/luadns/luadns.go +++ b/providers/dns/luadns/luadns.go @@ -104,9 +104,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{ - config: config, - client: client, - records: make(map[string]*internal.DNSRecord), + config: config, + client: client, + recordsMu: sync.Mutex{}, + records: make(map[string]*internal.DNSRecord), }, nil } diff --git a/providers/dns/luadns/luadns.toml b/providers/dns/luadns/luadns.toml index e56fac0b6..c80929c21 100644 --- a/providers/dns/luadns/luadns.toml +++ b/providers/dns/luadns/luadns.toml @@ -7,7 +7,7 @@ Since = "v3.7.0" Example = ''' LUADNS_API_USERNAME=youremail \ LUADNS_API_TOKEN=xxxxxxxx \ -lego --dns luadns -d '*.example.com' -d example.com run +lego --email you@example.com --dns luadns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mailinabox/mailinabox.toml b/providers/dns/mailinabox/mailinabox.toml index 74d8aabbc..e0072ebdd 100644 --- a/providers/dns/mailinabox/mailinabox.toml +++ b/providers/dns/mailinabox/mailinabox.toml @@ -8,7 +8,7 @@ Example = ''' MAILINABOX_EMAIL=user@example.com \ MAILINABOX_PASSWORD=yyyy \ MAILINABOX_BASE_URL=https://box.example.com \ -lego --dns mailinabox -d '*.example.com' -d example.com run +lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/manageengine/manageengine.toml b/providers/dns/manageengine/manageengine.toml index 43a782841..7708fa74f 100644 --- a/providers/dns/manageengine/manageengine.toml +++ b/providers/dns/manageengine/manageengine.toml @@ -7,7 +7,7 @@ Since = "v4.21.0" Example = ''' MANAGEENGINE_CLIENT_ID="xxx" \ MANAGEENGINE_CLIENT_SECRET="yyy" \ -lego --dns manageengine -d '*.example.com' -d example.com run +lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/manual/manual.go b/providers/dns/manual/manual.go deleted file mode 100644 index 2985bc595..000000000 --- a/providers/dns/manual/manual.go +++ /dev/null @@ -1,13 +0,0 @@ -package manual - -import ( - "github.com/go-acme/lego/v4/challenge/dns01" -) - -// DNSProvider is an implementation of the ChallengeProvider interface. -type DNSProvider = dns01.DNSProviderManual - -// NewDNSProvider returns a DNSProvider instance. -func NewDNSProvider() (*DNSProvider, error) { - return &DNSProvider{}, nil -} diff --git a/providers/dns/metaname/metaname.go b/providers/dns/metaname/metaname.go index d6e962024..d5d87dc4d 100644 --- a/providers/dns/metaname/metaname.go +++ b/providers/dns/metaname/metaname.go @@ -153,10 +153,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("metaname: delete record: %w", err) } - d.recordsMu.Lock() - delete(d.records, token) - d.recordsMu.Unlock() - return nil } diff --git a/providers/dns/metaname/metaname.toml b/providers/dns/metaname/metaname.toml index 654dcaed0..4a147d043 100644 --- a/providers/dns/metaname/metaname.toml +++ b/providers/dns/metaname/metaname.toml @@ -7,7 +7,7 @@ Since = "v4.13.0" Example = ''' METANAME_ACCOUNT_REFERENCE=xxxx \ METANAME_API_KEY=yyyyyyy \ -lego --dns metaname -d '*.example.com' -d example.com run +lego --email you@example.com --dns metaname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/metaregistrar/metaregistrar.toml b/providers/dns/metaregistrar/metaregistrar.toml index e505e0ce2..952c7ea61 100644 --- a/providers/dns/metaregistrar/metaregistrar.toml +++ b/providers/dns/metaregistrar/metaregistrar.toml @@ -6,7 +6,7 @@ Since = "v4.23.0" Example = ''' METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns metaregistrar -d '*.example.com' -d example.com run +lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mijnhost/mijnhost.toml b/providers/dns/mijnhost/mijnhost.toml index 416fdde53..00152e132 100644 --- a/providers/dns/mijnhost/mijnhost.toml +++ b/providers/dns/mijnhost/mijnhost.toml @@ -6,7 +6,7 @@ Since = "v4.18.0" Example = ''' MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns mijnhost -d '*.example.com' -d example.com run +lego --email you@example.com --dns mijnhost -d '*.example.com' -d example.com run ''' [Configuration] 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/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index dcd882482..6292dd787 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -170,10 +170,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("mittwald: update/delete TXT record: %w", err) } - d.zoneIDsMu.Lock() - delete(d.zoneIDs, token) - d.zoneIDsMu.Unlock() - return nil } diff --git a/providers/dns/mittwald/mittwald.toml b/providers/dns/mittwald/mittwald.toml index 36a9f6c16..937b9c172 100644 --- a/providers/dns/mittwald/mittwald.toml +++ b/providers/dns/mittwald/mittwald.toml @@ -6,7 +6,7 @@ Since = "v1.48.0" Example = ''' MITTWALD_TOKEN=my-token \ -lego --dns mittwald -d '*.example.com' -d example.com run +lego --email you@example.com --dns mittwald -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/myaddr/myaddr.toml b/providers/dns/myaddr/myaddr.toml index 2f5fe6c1f..5ff306526 100644 --- a/providers/dns/myaddr/myaddr.toml +++ b/providers/dns/myaddr/myaddr.toml @@ -6,7 +6,7 @@ Since = "v4.22.0" Example = ''' MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ -lego --dns myaddr -d '*.example.com' -d example.com run +lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mydnsjp/mydnsjp.toml b/providers/dns/mydnsjp/mydnsjp.toml index eb9e73acc..ab842e37f 100644 --- a/providers/dns/mydnsjp/mydnsjp.toml +++ b/providers/dns/mydnsjp/mydnsjp.toml @@ -7,7 +7,7 @@ Since = "v1.2.0" Example = ''' MYDNSJP_MASTER_ID=xxxxx \ MYDNSJP_PASSWORD=xxxxx \ -lego --dns mydnsjp -d '*.example.com' -d example.com run +lego --email you@example.com --dns mydnsjp -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mythicbeasts/mythicbeasts.toml b/providers/dns/mythicbeasts/mythicbeasts.toml index cada3041d..011abba1f 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.toml +++ b/providers/dns/mythicbeasts/mythicbeasts.toml @@ -7,7 +7,7 @@ Since = "v0.3.7" Example = ''' MYTHICBEASTS_USERNAME=myuser \ MYTHICBEASTS_PASSWORD=mypass \ -lego --dns mythicbeasts -d '*.example.com' -d example.com run +lego --email you@example.com --dns mythicbeasts -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index 54640f8e0..cf8520546 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -76,8 +76,7 @@ func NewDefaultConfig() *Config { PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, time.Hour), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), - Transport: defaultTransport(envNamespace), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), }, } } diff --git a/providers/dns/namecheap/namecheap.toml b/providers/dns/namecheap/namecheap.toml index b0f92a1bd..3a5be870c 100644 --- a/providers/dns/namecheap/namecheap.toml +++ b/providers/dns/namecheap/namecheap.toml @@ -14,7 +14,7 @@ More information in the section [Enabling API Access](https://www.namecheap.com/ Example = ''' NAMECHEAP_API_USER=user \ NAMECHEAP_API_KEY=key \ -lego --dns namecheap -d '*.example.com' -d example.com run +lego --email you@example.com --dns namecheap -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/namecheap/transport.go b/providers/dns/namecheap/transport.go deleted file mode 100644 index 584dc6e50..000000000 --- a/providers/dns/namecheap/transport.go +++ /dev/null @@ -1,71 +0,0 @@ -package namecheap - -import ( - "net/http" - "net/url" - "strings" - "sync" - - "github.com/go-acme/lego/v4/platform/config/env" - "golang.org/x/net/http/httpproxy" -) - -const ( - envHTTPProxy = "HTTP_PROXY" - envHTTPProxyLower = "http_proxy" - envHTTPSProxy = "HTTPS_PROXY" - envHTTPSProxyLower = "https_proxy" - envNoProxy = "NO_PROXY" - envNoProxyLower = "no_proxy" - envRequestMethod = "REQUEST_METHOD" -) - -// Allows lazy loading of the proxy. -var ( - envProxyOnce sync.Once - envProxyFuncValue func(*url.URL) (*url.URL, error) -) - -func defaultTransport(namespace string) http.RoundTripper { - tr, ok := http.DefaultTransport.(*http.Transport) - if !ok { - return nil - } - - clone := tr.Clone() - clone.Proxy = proxyFromEnvironment(namespace) - - return clone -} - -// Inspired by: -// - https://pkg.go.dev/net/http#ProxyFromEnvironment -// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment -func envProxyFunc(namespace string) func(*url.URL) (*url.URL, error) { - envProxyOnce.Do(func() { - cfg := &httpproxy.Config{ - HTTPProxy: getEnv(namespace, envHTTPProxy, envHTTPProxyLower), - HTTPSProxy: getEnv(namespace, envHTTPSProxy, envHTTPSProxyLower), - NoProxy: getEnv(namespace, envNoProxy, envNoProxyLower), - CGI: env.GetOneWithFallback(namespace+envRequestMethod, "", env.ParseString, envRequestMethod) != "", - } - - envProxyFuncValue = cfg.ProxyFunc() - }) - - return envProxyFuncValue -} - -// Inspired by: -// - https://pkg.go.dev/net/http#ProxyFromEnvironment -// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment -func proxyFromEnvironment(namespace string) func(req *http.Request) (*url.URL, error) { - return func(req *http.Request) (*url.URL, error) { - return envProxyFunc(namespace)(req.URL) - } -} - -func getEnv(namespace, baseEnvName, baseEnvNameLower string) string { - return env.GetOneWithFallback(namespace+baseEnvName, "", env.ParseString, - strings.ToLower(namespace)+baseEnvNameLower, baseEnvName, baseEnvNameLower) -} diff --git a/providers/dns/namecheap/transport_test.go b/providers/dns/namecheap/transport_test.go deleted file mode 100644 index cd3e9ff17..000000000 --- a/providers/dns/namecheap/transport_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package namecheap - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_defaultTransport(t *testing.T) { - client := servermock.NewBuilder( - func(server *httptest.Server) (*http.Client, error) { - cl := server.Client() - - t.Setenv("NAMECHEAP_HTTP_PROXY", server.URL) - - cl.Transport = defaultTransport(envNamespace) - - return cl, nil - }). - Route("/", - servermock.Noop().WithStatusCode(http.StatusTeapot)). - Build(t) - - req, err := http.NewRequest(http.MethodGet, "http://example.com", nil) - require.NoError(t, err) - - resp, err := client.Do(req) - require.NoError(t, err) - - t.Cleanup(func() { - _ = resp.Body.Close() - }) - - assert.Equal(t, http.StatusTeapot, resp.StatusCode) -} diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 04c8b5967..3d1f33af1 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -116,10 +116,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - if info.EffectiveFQDN != info.FQDN { - domain = dns01.UnFqdn(info.EffectiveFQDN) - } - + // TODO(ldez) replace domain by FQDN to follow CNAME. domainDetails, err := d.client.GetDomain(&namecom.GetDomainRequest{DomainName: domain}) if err != nil { return fmt.Errorf("namedotcom: API call failed: %w", err) @@ -130,6 +127,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("namedotcom: %w", err) } + // TODO(ldez) replace domain by FQDN to follow CNAME. request := &namecom.Record{ DomainName: domain, Host: subDomain, @@ -150,10 +148,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - if info.EffectiveFQDN != info.FQDN { - domain = dns01.UnFqdn(info.EffectiveFQDN) - } - + // TODO(ldez) replace domain by FQDN to follow CNAME. records, err := d.getRecords(domain) if err != nil { return fmt.Errorf("namedotcom: %w", err) @@ -161,6 +156,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { for _, rec := range records { if rec.Fqdn == info.EffectiveFQDN && rec.Type == "TXT" { + // TODO(ldez) replace domain by FQDN to follow CNAME. request := &namecom.DeleteRecordRequest{ DomainName: domain, ID: rec.ID, diff --git a/providers/dns/namedotcom/namedotcom.toml b/providers/dns/namedotcom/namedotcom.toml index 3651c424b..e6de796d1 100644 --- a/providers/dns/namedotcom/namedotcom.toml +++ b/providers/dns/namedotcom/namedotcom.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' NAMECOM_USERNAME=foo.bar \ NAMECOM_API_TOKEN=a379a6f6eeafb9a55e378c118034e2751e682fab \ -lego --dns namedotcom -d '*.example.com' -d example.com run +lego --email you@example.com --dns namedotcom -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/namesilo/namesilo.toml b/providers/dns/namesilo/namesilo.toml index 113ddb5c5..bab7905bf 100644 --- a/providers/dns/namesilo/namesilo.toml +++ b/providers/dns/namesilo/namesilo.toml @@ -6,7 +6,7 @@ Since = "v2.7.0" Example = ''' NAMESILO_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --dns namesilo -d '*.example.com' -d example.com run +lego --email you@example.com --dns namesilo -d '*.example.com' -d example.com run ''' [Configuration] 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/nearlyfreespeech/nearlyfreespeech.toml b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml index 3a1e25942..80d4fd6bc 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech.toml +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml @@ -7,7 +7,7 @@ Since = "v4.8.0" Example = ''' NEARLYFREESPEECH_API_KEY=xxxxxx \ NEARLYFREESPEECH_LOGIN=xxxx \ -lego --dns nearlyfreespeech -d '*.example.com' -d example.com run +lego --email you@example.com --dns nearlyfreespeech -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/neodigit/neodigit.go b/providers/dns/neodigit/neodigit.go deleted file mode 100644 index d41846307..000000000 --- a/providers/dns/neodigit/neodigit.go +++ /dev/null @@ -1,103 +0,0 @@ -// Package neodigit implements a DNS provider for solving the DNS-01 challenge using Neodigit DNS. -package neodigit - -import ( - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica" -) - -// Environment variables names. -const ( - envNamespace = "NEODIGIT_" - - EnvToken = envNamespace + "TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultBaseURL = "https://api.neodigit.net/v1" - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config = tecnocratica.Config - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - prv challenge.ProviderTimeout -} - -// NewDNSProvider returns a DNSProvider instance configured for Neodigit. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvToken) - if err != nil { - return nil, fmt.Errorf("neodigit: %w", err) - } - - config := NewDefaultConfig() - config.Token = values[EnvToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Neodigit. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("neodigit: the configuration of the DNS provider is nil") - } - - provider, err := tecnocratica.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("neodigit: %w", err) - } - - return &DNSProvider{prv: provider}, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("neodigit: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("neodigit: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/neodigit/neodigit.toml b/providers/dns/neodigit/neodigit.toml deleted file mode 100644 index 91b3cfb07..000000000 --- a/providers/dns/neodigit/neodigit.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Neodigit" -Description = '''''' -URL = "https://www.neodigit.net" -Code = "neodigit" -Since = "v4.30.0" - -Example = ''' -NEODIGIT_TOKEN=xxxxxx \ -lego --dns neodigit -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - NEODIGIT_TOKEN = "API token" - [Configuration.Additional] - NEODIGIT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" - NEODIGIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" - NEODIGIT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - NEODIGIT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://developers.neodigit.net/#dns" diff --git a/providers/dns/neodigit/neodigit_test.go b/providers/dns/neodigit/neodigit_test.go deleted file mode 100644 index 39f67c59c..000000000 --- a/providers/dns/neodigit/neodigit_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package neodigit - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvToken: "secret", - }, - }, - { - desc: "missing credentials: token", - envVars: map[string]string{ - EnvToken: "", - }, - expected: "neodigit: some credentials information are missing: NEODIGIT_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - token string - expected string - }{ - { - desc: "success", - token: "secret", - }, - { - desc: "missing token", - expected: "neodigit: missing credentials", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Token = test.token - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/netcup/netcup.toml b/providers/dns/netcup/netcup.toml index 4ef8688c6..0df09b0df 100644 --- a/providers/dns/netcup/netcup.toml +++ b/providers/dns/netcup/netcup.toml @@ -8,7 +8,7 @@ Example = ''' NETCUP_CUSTOMER_NUMBER=xxxx \ NETCUP_API_KEY=yyyy \ NETCUP_API_PASSWORD=zzzz \ -lego --dns netcup -d '*.example.com' -d example.com run +lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/netlify/netlify.toml b/providers/dns/netlify/netlify.toml index 9d3c0f6b5..c5cb670f9 100644 --- a/providers/dns/netlify/netlify.toml +++ b/providers/dns/netlify/netlify.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' NETLIFY_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns netlify -d '*.example.com' -d example.com run +lego --email you@example.com --dns netlify -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/nicmanager/nicmanager.toml b/providers/dns/nicmanager/nicmanager.toml index d5921de5a..7fdf296c4 100644 --- a/providers/dns/nicmanager/nicmanager.toml +++ b/providers/dns/nicmanager/nicmanager.toml @@ -13,7 +13,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --dns nicmanager -d '*.example.com' -d example.com run +lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run ## Login using account name + username @@ -24,7 +24,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --dns nicmanager -d '*.example.com' -d example.com run +lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/nicru/nicru.toml b/providers/dns/nicru/nicru.toml index f955511a2..6bffe74a5 100644 --- a/providers/dns/nicru/nicru.toml +++ b/providers/dns/nicru/nicru.toml @@ -9,7 +9,7 @@ NICRU_USER="" \ NICRU_PASSWORD="" \ NICRU_SERVICE_ID="" \ NICRU_SECRET="" \ -lego --dns nicru -d '*.example.com' -d example.com run +lego --dns nicru --domains "*.example.com" --email you@example.com run ''' Additional = ''' diff --git a/providers/dns/nifcloud/nifcloud.toml b/providers/dns/nifcloud/nifcloud.toml index 3c43b1dc0..b692bb9d3 100644 --- a/providers/dns/nifcloud/nifcloud.toml +++ b/providers/dns/nifcloud/nifcloud.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' NIFCLOUD_ACCESS_KEY_ID=xxxx \ NIFCLOUD_SECRET_ACCESS_KEY=yyyy \ -lego --dns nifcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns nifcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/njalla/njalla.toml b/providers/dns/njalla/njalla.toml index ff4750b7d..ef1fe158e 100644 --- a/providers/dns/njalla/njalla.toml +++ b/providers/dns/njalla/njalla.toml @@ -6,7 +6,7 @@ Since = "v4.3.0" Example = ''' NJALLA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns njalla -d '*.example.com' -d example.com run +lego --email you@example.com --dns njalla -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/nodion/nodion.go b/providers/dns/nodion/nodion.go index 4bc887568..e34d7db28 100644 --- a/providers/dns/nodion/nodion.go +++ b/providers/dns/nodion/nodion.go @@ -208,9 +208,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("regru: failed to remove TXT records [domain: %s]: %w", dns01.UnFqdn(authZone), err) } - d.zoneIDsMu.Lock() - delete(d.zoneIDs, token) - d.zoneIDsMu.Unlock() - return nil } diff --git a/providers/dns/nodion/nodion.toml b/providers/dns/nodion/nodion.toml index c9db46e61..0888f96c3 100644 --- a/providers/dns/nodion/nodion.toml +++ b/providers/dns/nodion/nodion.toml @@ -6,7 +6,7 @@ Since = "v4.11.0" Example = ''' NODION_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns nodion -d '*.example.com' -d example.com run +lego --email you@example.com --dns nodion -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ns1/ns1.toml b/providers/dns/ns1/ns1.toml index 829663bf5..2a6b10deb 100644 --- a/providers/dns/ns1/ns1.toml +++ b/providers/dns/ns1/ns1.toml @@ -6,7 +6,7 @@ Since = "v0.4.0" Example = ''' NS1_API_KEY=xxxx \ -lego --dns ns1 -d '*.example.com' -d example.com run +lego --email you@example.com --dns ns1 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go index 6032dcce1..af469f5ed 100644 --- a/providers/dns/octenium/octenium.go +++ b/providers/dns/octenium/octenium.go @@ -169,10 +169,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { break } - d.domainIDsMu.Lock() - delete(d.domainIDs, token) - d.domainIDsMu.Unlock() - return nil } diff --git a/providers/dns/octenium/octenium.toml b/providers/dns/octenium/octenium.toml index e3c9d894f..5084526fd 100644 --- a/providers/dns/octenium/octenium.toml +++ b/providers/dns/octenium/octenium.toml @@ -6,7 +6,7 @@ Since = "v4.27.0" Example = ''' OCTENIUM_API_KEY="xxx" \ -lego --dns octenium -d '*.example.com' -d example.com run +lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index f6155052e..f13cb1e1e 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -13,13 +13,13 @@ OCI_USER_OCID="ocid1.user.oc1..secret" \ OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --dns oraclecloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run # Using Instance Principal authentication (when running on OCI compute instances): # https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm OCI_AUTH_TYPE="instance_principal" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --dns oraclecloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 74ee06eac..c646e90f2 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -61,7 +61,7 @@ func TestNewDNSProvider(t *testing.T) { { desc: "success file", envVars: map[string]string{ - EnvPrivKeyFile: mustGeneratePrivateKeyFile(t, "secret1"), + EnvPrivKeyFile: mustGeneratePrivateKeyFile("secret1"), EnvPrivKeyPass: "secret1", EnvTenancyOCID: "ocid1.tenancy.oc1..secret", EnvUserOCID: "ocid1.user.oc1..secret", @@ -383,21 +383,21 @@ func mustGeneratePrivateKey(pwd string) string { return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(block)) } -func mustGeneratePrivateKeyFile(t *testing.T, pwd string) string { - t.Helper() - +func mustGeneratePrivateKeyFile(pwd string) string { block, err := generatePrivateKey(pwd) - require.NoError(t, err) + if err != nil { + panic(err) + } - file, err := os.CreateTemp(t.TempDir(), "lego_oci_*.pem") - require.NoError(t, err) - - defer func() { - _ = file.Close() - }() + file, err := os.CreateTemp("", "lego_oci_*.pem") + if err != nil { + panic(err) + } err = pem.Encode(file, block) - require.NoError(t, err) + if err != nil { + panic(err) + } return file.Name() } diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index e63077fda..91f9f5455 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -9,7 +9,7 @@ OTC_DOMAIN_NAME=domain_name \ OTC_USER_NAME=user_name \ OTC_PASSWORD=password \ OTC_PROJECT_NAME=project_name \ -lego --dns otc -d '*.example.com' -d example.com run +lego --email you@example.com --dns otc -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index a8d12d819..b7e522540 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -102,9 +102,8 @@ func (c *Config) hasAppKeyAuth() bool { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *ovh.Client - + config *Config + client *ovh.Client recordIDs map[string]int64 recordIDsMu sync.Mutex } diff --git a/providers/dns/ovh/ovh.toml b/providers/dns/ovh/ovh.toml index abf22bd7a..95162185b 100644 --- a/providers/dns/ovh/ovh.toml +++ b/providers/dns/ovh/ovh.toml @@ -11,20 +11,20 @@ OVH_APPLICATION_KEY=1234567898765432 \ OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \ OVH_CONSUMER_KEY=256vfsd347245sdfg \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run # Or Access Token: OVH_ACCESS_TOKEN=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run # Or OAuth2: OVH_CLIENT_ID=yyy \ OVH_CLIENT_SECRET=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --dns ovh -d '*.example.com' -d example.com run +lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/pdns/pdns.toml b/providers/dns/pdns/pdns.toml index a83d80922..53b5547b9 100644 --- a/providers/dns/pdns/pdns.toml +++ b/providers/dns/pdns/pdns.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' PDNS_API_URL=http://pdns-server:80/ \ PDNS_API_KEY=xxxx \ -lego --dns pdns -d '*.example.com' -d example.com run +lego --email you@example.com --dns pdns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/plesk/plesk.go b/providers/dns/plesk/plesk.go index 5f07dcb50..b764dff33 100644 --- a/providers/dns/plesk/plesk.go +++ b/providers/dns/plesk/plesk.go @@ -173,9 +173,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("plesk: failed to delete record (%d): %w", recordID, err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/plesk/plesk.toml b/providers/dns/plesk/plesk.toml index 0ef89d6b7..5fb4ce073 100644 --- a/providers/dns/plesk/plesk.toml +++ b/providers/dns/plesk/plesk.toml @@ -8,7 +8,7 @@ Example = ''' PLESK_SERVER_BASE_URL="https://plesk.myserver.com:8443" \ PLESK_USERNAME=xxxxxx \ PLESK_PASSWORD=yyyyyy \ -lego --dns plesk -d '*.example.com' -d example.com run +lego --email you@example.com --dns plesk -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go index 2f999ebcc..dc9efb013 100644 --- a/providers/dns/porkbun/porkbun.go +++ b/providers/dns/porkbun/porkbun.go @@ -171,10 +171,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("porkbun: failed to delete record: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/porkbun/porkbun.toml b/providers/dns/porkbun/porkbun.toml index 9ae036da6..d7ed3aedc 100644 --- a/providers/dns/porkbun/porkbun.toml +++ b/providers/dns/porkbun/porkbun.toml @@ -8,7 +8,7 @@ Since = "v4.4.0" Example = ''' PORKBUN_SECRET_API_KEY=xxxxxx \ PORKBUN_API_KEY=yyyyyy \ -lego --dns porkbun -d '*.example.com' -d example.com run +lego --email you@example.com --dns porkbun -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rackspace/rackspace.toml b/providers/dns/rackspace/rackspace.toml index 0a4a80ffc..7ca2c3b7a 100644 --- a/providers/dns/rackspace/rackspace.toml +++ b/providers/dns/rackspace/rackspace.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' RACKSPACE_USER=xxxx \ RACKSPACE_API_KEY=yyyy \ -lego --dns rackspace -d '*.example.com' -d example.com run +lego --email you@example.com --dns rackspace -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rainyun/rainyun.toml b/providers/dns/rainyun/rainyun.toml index fe2b3c07d..cca16cffe 100644 --- a/providers/dns/rainyun/rainyun.toml +++ b/providers/dns/rainyun/rainyun.toml @@ -6,7 +6,7 @@ Since = "v4.21.0" Example = ''' RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns rainyun -d '*.example.com' -d example.com run +lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rcodezero/rcodezero.toml b/providers/dns/rcodezero/rcodezero.toml index c2a4a1e7b..bba5588da 100644 --- a/providers/dns/rcodezero/rcodezero.toml +++ b/providers/dns/rcodezero/rcodezero.toml @@ -6,7 +6,7 @@ Since = "v4.13" Example = ''' RCODEZERO_API_TOKEN= \ -lego --dns rcodezero -d '*.example.com' -d example.com run +lego --email you@example.com --dns rcodezero -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/regfish/regfish.toml b/providers/dns/regfish/regfish.toml index fbaacbde4..9869ed96e 100644 --- a/providers/dns/regfish/regfish.toml +++ b/providers/dns/regfish/regfish.toml @@ -6,7 +6,7 @@ Since = "v4.20.0" Example = ''' REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns regfish -d '*.example.com' -d example.com run +lego --email you@example.com --dns regfish -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/regru/regru.toml b/providers/dns/regru/regru.toml index 728bb2bf7..2ccf3a58f 100644 --- a/providers/dns/regru/regru.toml +++ b/providers/dns/regru/regru.toml @@ -7,7 +7,7 @@ Since = "v3.5.0" Example = ''' REGRU_USERNAME=xxxxxx \ REGRU_PASSWORD=yyyyyy \ -lego --dns regru -d '*.example.com' -d example.com run +lego --email you@example.com --dns regru -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rfc2136/rfc2136.toml b/providers/dns/rfc2136/rfc2136.toml index 6b5bbe599..9243440a4 100644 --- a/providers/dns/rfc2136/rfc2136.toml +++ b/providers/dns/rfc2136/rfc2136.toml @@ -9,7 +9,7 @@ RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ -lego --dns rfc2136 -d '*.example.com' -d example.com run +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ## --- @@ -17,7 +17,7 @@ keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_FILE="$keyfile" \ -lego --dns rfc2136 -d '*.example.com' -d example.com run +lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rimuhosting/rimuhosting.go b/providers/dns/rimuhosting/rimuhosting.go index 7a7e99f60..08d7ad413 100644 --- a/providers/dns/rimuhosting/rimuhosting.go +++ b/providers/dns/rimuhosting/rimuhosting.go @@ -2,6 +2,7 @@ package rimuhosting import ( + "context" "errors" "fmt" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -28,12 +30,19 @@ const ( var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = rimuhosting.Config +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, rimuhosting.DefaultTTL), + TTL: env.GetOrDefaultInt(EnvTTL, 3600), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -44,7 +53,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *rimuhosting.Client } // NewDNSProvider returns a DNSProvider instance configured for RimuHosting. @@ -67,19 +77,50 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("rimuhosting: the configuration of the DNS provider is nil") } - provider, err := rimuhosting.NewDNSProviderConfig(config, "") - if err != nil { - return nil, fmt.Errorf("rimuhosting: %w", err) + if config.APIKey == "" { + return nil, errors.New("rimuhosting: incomplete credentials, missing API key") } - return &DNSProvider{prv: provider}, nil + client := rimuhosting.NewClient(config.APIKey) + client.BaseURL = rimuhosting.DefaultRimuHostingBaseURL + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { - return fmt.Errorf("rimuhosting: %w", err) + return fmt.Errorf("rimuhosting: failed to find record(s) for %s: %w", domain, err) + } + + actions := []rimuhosting.ActionParameter{ + rimuhosting.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), + } + + for _, record := range records { + actions = append(actions, rimuhosting.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) + } + + _, err = d.client.DoActions(ctx, actions...) + if err != nil { + return fmt.Errorf("rimuhosting: failed to add record(s) for %s: %w", domain, err) } return nil @@ -87,16 +128,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + action := rimuhosting.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) + + _, err := d.client.DoActions(context.Background(), action) if err != nil { - return fmt.Errorf("rimuhosting: %w", err) + return fmt.Errorf("rimuhosting: failed to delete record for %s: %w", domain, err) } return nil } - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/rimuhosting/rimuhosting.toml b/providers/dns/rimuhosting/rimuhosting.toml index c1994e2cc..0a4f983e2 100644 --- a/providers/dns/rimuhosting/rimuhosting.toml +++ b/providers/dns/rimuhosting/rimuhosting.toml @@ -6,7 +6,7 @@ Since = "v0.3.5" Example = ''' RIMUHOSTING_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns rimuhosting -d '*.example.com' -d example.com run +lego --email you@example.com --dns rimuhosting -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rimuhosting/rimuhosting_test.go b/providers/dns/rimuhosting/rimuhosting_test.go index 878ec14da..d8b086e25 100644 --- a/providers/dns/rimuhosting/rimuhosting_test.go +++ b/providers/dns/rimuhosting/rimuhosting_test.go @@ -46,7 +46,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) } else { require.EqualError(t, err, test.expected) } @@ -84,7 +84,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/route53/route53.toml b/providers/dns/route53/route53.toml index 607d9ef31..9e3b049a6 100644 --- a/providers/dns/route53/route53.toml +++ b/providers/dns/route53/route53.toml @@ -9,7 +9,7 @@ AWS_ACCESS_KEY_ID=your_key_id \ AWS_SECRET_ACCESS_KEY=your_secret_access_key \ AWS_REGION=aws-region \ AWS_HOSTED_ZONE_ID=your_hosted_zone_id \ -lego --dns route53 -d '*.example.com' -d example.com run +lego --email you@example.com --dns route53 -d '*.example.com' -d example.com run ''' Additional = ''' 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..dcc7bc90e 100644 --- a/providers/dns/safedns/safedns.toml +++ b/providers/dns/safedns/safedns.toml @@ -1,12 +1,12 @@ -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" Example = ''' SAFEDNS_AUTH_TOKEN=xxxxxx \ -lego --dns safedns -d '*.example.com' -d example.com run +lego --email you@example.com --dns safedns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/sakuracloud/sakuracloud.toml b/providers/dns/sakuracloud/sakuracloud.toml index a197cd27c..f754e0c89 100644 --- a/providers/dns/sakuracloud/sakuracloud.toml +++ b/providers/dns/sakuracloud/sakuracloud.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' SAKURACLOUD_ACCESS_TOKEN=xxxxx \ SAKURACLOUD_ACCESS_TOKEN_SECRET=yyyyy \ -lego --dns sakuracloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns sakuracloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/scaleway/scaleway.toml b/providers/dns/scaleway/scaleway.toml index 8b556e8b1..212cea295 100644 --- a/providers/dns/scaleway/scaleway.toml +++ b/providers/dns/scaleway/scaleway.toml @@ -6,7 +6,7 @@ Since = "v3.4.0" Example = ''' SCW_SECRET_KEY=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \ -lego --dns scaleway -d '*.example.com' -d example.com run +lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index 63ddd81ac..804ef04d5 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -4,14 +4,17 @@ package selectel import ( + "context" "errors" "fmt" "net/http" + "net/url" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -28,16 +31,25 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const minTTL = 60 + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = selectel.Config +type Config struct { + BaseURL string + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, ""), - TTL: env.GetOrDefaultInt(EnvTTL, selectel.MinTTL), + BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultSelectelBaseURL), + TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -48,7 +60,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *selectel.Client } // NewDNSProvider returns a DNSProvider instance configured for Selectel Domains API. @@ -71,36 +84,95 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("selectel: the configuration of the DNS provider is nil") } - provider, err := selectel.NewDNSProviderConfig(config) + if config.Token == "" { + return nil, errors.New("selectel: credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("selectel: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := selectel.NewClient(config.Token) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + var err error + + client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("selectel: %w", err) } - return &DNSProvider{prv: provider}, nil + 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 { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("selectel: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("selectel: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Timeout returns the Timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill DNS-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("selectel: %w", err) + } + + txtRecord := selectel.Record{ + Type: "TXT", + TTL: d.config.TTL, + Name: info.EffectiveFQDN, + Content: info.Value, + } + + _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) + if err != nil { + return fmt.Errorf("selectel: %w", err) + } + + return nil +} + +// CleanUp removes a TXT record used for DNS-01 challenge. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + recordName := dns01.UnFqdn(info.EffectiveFQDN) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("selectel: %w", err) + } + + records, err := d.client.ListRecords(ctx, domainObj.ID) + if err != nil { + return fmt.Errorf("selectel: %w", err) + } + + // Delete records with specific FQDN + var lastErr error + + for _, record := range records { + if record.Name == recordName { + err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) + if err != nil { + lastErr = fmt.Errorf("selectel: %w", err) + } + } + } + + return lastErr } diff --git a/providers/dns/selectel/selectel.toml b/providers/dns/selectel/selectel.toml index 087c97b5b..f9add7ea9 100644 --- a/providers/dns/selectel/selectel.toml +++ b/providers/dns/selectel/selectel.toml @@ -6,7 +6,7 @@ Since = "v1.2.0" Example = ''' SELECTEL_API_TOKEN=xxxxx \ -lego --dns selectel -d '*.example.com' -d example.com run +lego --email you@example.com --dns selectel -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selectel/selectel_test.go b/providers/dns/selectel/selectel_test.go index a456f1358..e3c36e226 100644 --- a/providers/dns/selectel/selectel_test.go +++ b/providers/dns/selectel/selectel_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/providers/dns/internal/selectel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -47,7 +46,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.prv) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -77,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { desc: "bad TTL value", token: "123", ttl: 59, - expected: fmt.Sprintf("selectel: invalid TTL, TTL (59) must be greater than %d", selectel.MinTTL), + expected: fmt.Sprintf("selectel: invalid TTL, TTL (59) must be greater than %d", minTTL), }, } @@ -92,7 +92,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.prv) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 1fcb48583..6e3c1f42c 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -297,10 +297,10 @@ func (w *clientWrapper) getZone(ctx context.Context, name string) (*selectelapi. return nil, fmt.Errorf("zone '%s' for challenge has not been found", name) } - // after is always defined since if no dots present we exit above. - _, after, _ := strings.Cut(name, ".") + // -1 can not be returned since if no dots present we exit above + i := strings.Index(name, ".") - return w.getZone(ctx, after) + return w.getZone(ctx, name[i+1:]) } func (w *clientWrapper) getRRset(ctx context.Context, name, zoneID string) (*selectelapi.RRSet, error) { diff --git a/providers/dns/selectelv2/selectelv2.toml b/providers/dns/selectelv2/selectelv2.toml index 480c7756e..fd8dbda9f 100644 --- a/providers/dns/selectelv2/selectelv2.toml +++ b/providers/dns/selectelv2/selectelv2.toml @@ -9,7 +9,7 @@ SELECTELV2_USERNAME=trex \ SELECTELV2_PASSWORD=xxxxx \ SELECTELV2_ACCOUNT_ID=1234567 \ SELECTELV2_PROJECT_ID=111a11111aaa11aa1a11aaa11111aa1a \ -lego --dns selectelv2 -d '*.example.com' -d example.com run +lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selfhostde/selfhostde.go b/providers/dns/selfhostde/selfhostde.go index 035cd5363..bb475deea 100644 --- a/providers/dns/selfhostde/selfhostde.go +++ b/providers/dns/selfhostde/selfhostde.go @@ -186,9 +186,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("selfhostde: emptied DNS TXT record (id=%s): %w", recordID, err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/selfhostde/selfhostde.toml b/providers/dns/selfhostde/selfhostde.toml index bd22c6c41..619f2cae8 100644 --- a/providers/dns/selfhostde/selfhostde.toml +++ b/providers/dns/selfhostde/selfhostde.toml @@ -8,7 +8,7 @@ Example = ''' SELFHOSTDE_USERNAME=xxx \ SELFHOSTDE_PASSWORD=yyy \ SELFHOSTDE_RECORDS_MAPPING=my.example.com:123 \ -lego --dns selfhostde -d '*.example.com' -d example.com run +lego --email you@example.com --dns selfhostde -d '*.example.com' -d example.com run ''' Additional = """ diff --git a/providers/dns/servercow/servercow.toml b/providers/dns/servercow/servercow.toml index 5cbacbb88..de9727163 100644 --- a/providers/dns/servercow/servercow.toml +++ b/providers/dns/servercow/servercow.toml @@ -7,7 +7,7 @@ Since = "v3.4.0" Example = ''' SERVERCOW_USERNAME=xxxxxxxx \ SERVERCOW_PASSWORD=xxxxxxxx \ -lego --dns servercow -d '*.example.com' -d example.com run +lego --email you@example.com --dns servercow -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index 0cd33e19a..5a3a1f6de 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -172,10 +172,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("shellrent: delete record: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/shellrent/shellrent.toml b/providers/dns/shellrent/shellrent.toml index 05b6517fc..48a5b9ad9 100644 --- a/providers/dns/shellrent/shellrent.toml +++ b/providers/dns/shellrent/shellrent.toml @@ -7,7 +7,7 @@ Since = "v4.16.0" Example = ''' SHELLRENT_USERNAME=xxxx \ SHELLRENT_TOKEN=yyyy \ -lego --dns shellrent -d '*.example.com' -d example.com run +lego --email you@example.com --dns shellrent -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/simply/simply.toml b/providers/dns/simply/simply.toml index a838e245a..c586e0db5 100644 --- a/providers/dns/simply/simply.toml +++ b/providers/dns/simply/simply.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' SIMPLY_ACCOUNT_NAME=xxxxxx \ SIMPLY_API_KEY=yyyyyy \ -lego --dns simply -d '*.example.com' -d example.com run +lego --email you@example.com --dns simply -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/sonic/sonic.toml b/providers/dns/sonic/sonic.toml index cb501e923..921fe4988 100644 --- a/providers/dns/sonic/sonic.toml +++ b/providers/dns/sonic/sonic.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' SONIC_USER_ID=12345 \ SONIC_API_KEY=4d6fbf2f9ab0fa11697470918d37625851fc0c51 \ -lego --dns sonic -d '*.example.com' -d example.com run +lego --email you@example.com --dns sonic -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/spaceship/spaceship.toml b/providers/dns/spaceship/spaceship.toml index e9abcd408..645abd171 100644 --- a/providers/dns/spaceship/spaceship.toml +++ b/providers/dns/spaceship/spaceship.toml @@ -7,7 +7,7 @@ Since = "v4.22.0" Example = ''' SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns spaceship -d '*.example.com' -d example.com run +lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/stackpath/stackpath.toml b/providers/dns/stackpath/stackpath.toml index b50e7035f..cc14cdfba 100644 --- a/providers/dns/stackpath/stackpath.toml +++ b/providers/dns/stackpath/stackpath.toml @@ -8,7 +8,7 @@ Example = ''' STACKPATH_CLIENT_ID=xxxxx \ STACKPATH_CLIENT_SECRET=yyyyy \ STACKPATH_STACK_ID=zzzzz \ -lego --dns stackpath -d '*.example.com' -d example.com run +lego --email you@example.com --dns stackpath -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/syse/internal/client.go b/providers/dns/syse/internal/client.go deleted file mode 100644 index 8cb801469..000000000 --- a/providers/dns/syse/internal/client.go +++ /dev/null @@ -1,131 +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://www.syse.no/api" - -// Client the Syse API client. -type Client struct { - credentials map[string]string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(credentials map[string]string) (*Client, error) { - if len(credentials) == 0 { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - credentials: credentials, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) CreateRecord(ctx context.Context, zone string, record Record) (*Record, error) { - endpoint := c.BaseURL.JoinPath("dns", zone) - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return nil, err - } - - req.SetBasicAuth(zone, c.credentials[zone]) - - result := new(Record) - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) DeleteRecord(ctx context.Context, zone, recordID string) error { - endpoint := c.BaseURL.JoinPath("dns", zone, recordID) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - req.SetBasicAuth(zone, c.credentials[zone]) - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} diff --git a/providers/dns/syse/internal/client_test.go b/providers/dns/syse/internal/client_test.go deleted file mode 100644 index 88416aa88..000000000 --- a/providers/dns/syse/internal/client_test.go +++ /dev/null @@ -1,102 +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(map[string]string{ - "example.com": "secret", - }) - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(), - ) -} - -func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /dns/example.com", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := Record{ - Type: "TXT", - Prefix: "_acme-challenge", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - Active: true, - TTL: 120, - } - - result, err := client.CreateRecord(t.Context(), "example.com", record) - require.NoError(t, err) - - expected := &Record{ - ID: "1234", - Type: "TXT", - Prefix: "_acme-challenge", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - Active: true, - TTL: 120, - } - - assert.Equal(t, expected, result) -} - -func TestClient_CreateRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /dns/example.com", - servermock.RawStringResponse(http.StatusText(http.StatusUnauthorized)). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - record := Record{ - Type: "TXT", - Prefix: "_acme-challenge", - Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - Active: true, - TTL: 120, - } - - _, err := client.CreateRecord(t.Context(), "example.com", record) - require.EqualError(t, err, "unexpected status code: [status code: 401] body: Unauthorized") -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /dns/example.com/1234", - servermock.Noop()). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", "1234") - require.NoError(t, err) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /dns/example.com/1234", - servermock.RawStringResponse(http.StatusText(http.StatusUnauthorized)). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", "1234") - require.EqualError(t, err, "unexpected status code: [status code: 401] body: Unauthorized") -} diff --git a/providers/dns/syse/internal/fixtures/create_record-request.json b/providers/dns/syse/internal/fixtures/create_record-request.json deleted file mode 100644 index 549a0f60f..000000000 --- a/providers/dns/syse/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "active": true, - "ttl": 120, - "prefix": "_acme-challenge", - "type": "TXT" -} diff --git a/providers/dns/syse/internal/fixtures/create_record.json b/providers/dns/syse/internal/fixtures/create_record.json deleted file mode 100644 index b598779c6..000000000 --- a/providers/dns/syse/internal/fixtures/create_record.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "1234", - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "active": true, - "ttl": 120, - "prefix": "_acme-challenge", - "type": "TXT" -} diff --git a/providers/dns/syse/internal/types.go b/providers/dns/syse/internal/types.go deleted file mode 100644 index 4b90205e1..000000000 --- a/providers/dns/syse/internal/types.go +++ /dev/null @@ -1,11 +0,0 @@ -package internal - -type Record struct { - ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Prefix string `json:"prefix,omitempty"` - Content string `json:"content,omitempty"` - Priority int `json:"prio,omitempty"` - TTL int `json:"ttl,omitempty"` - Active bool `json:"active,omitempty"` -} diff --git a/providers/dns/syse/syse.go b/providers/dns/syse/syse.go deleted file mode 100644 index 29633280c..000000000 --- a/providers/dns/syse/syse.go +++ /dev/null @@ -1,186 +0,0 @@ -// Package syse implements a DNS provider for solving the DNS-01 challenge using Syse. -package syse - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/syse/internal" -) - -// Environment variables names. -const ( - envNamespace = "SYSE_" - - EnvCredentials = envNamespace + "CREDENTIALS" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Credentials map[string]string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 1200*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Syse. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvCredentials) - if err != nil { - return nil, fmt.Errorf("syse: %w", err) - } - - config := NewDefaultConfig() - - credentials, err := env.ParsePairs(values[EnvCredentials]) - if err != nil { - return nil, fmt.Errorf("syse: credentials: %w", err) - } - - config.Credentials = credentials - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Syse. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("syse: the configuration of the DNS provider is nil") - } - - if len(config.Credentials) == 0 { - return nil, errors.New("syse: missing credentials") - } - - for domain, password := range config.Credentials { - if domain == "" { - return nil, fmt.Errorf(`syse: missing domain: "%s:%s"`, domain, password) - } - - if password == "" { - return nil, fmt.Errorf(`syse: missing password: "%s:%s"`, domain, password) - } - } - - client, err := internal.NewClient(config.Credentials) - if err != nil { - return nil, fmt.Errorf("syse: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("syse: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("syse: %w", err) - } - - record := internal.Record{ - Type: "TXT", - Prefix: subDomain, - Content: info.Value, - TTL: d.config.TTL, - Active: true, - } - - newRecord, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) - if err != nil { - return fmt.Errorf("syse: create record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = newRecord.ID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("syse: could not find zone for domain %q: %w", domain, err) - } - - // gets the record's unique ID from when we created it - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("syse: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("syse: delete record: %w", err) - } - - 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/syse/syse.toml b/providers/dns/syse/syse.toml deleted file mode 100644 index b5b1fdf47..000000000 --- a/providers/dns/syse/syse.toml +++ /dev/null @@ -1,25 +0,0 @@ -Name = "Syse" -Description = '''''' -URL = "https://www.syse.no/" -Code = "syse" -Since = "v4.30.0" - -Example = ''' -SYSE_CREDENTIALS=example.com:password \ -lego --dns syse -d '*.example.com' -d example.com run - -SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ -lego --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com -''' - -[Configuration] - [Configuration.Credentials] - SYSE_CREDENTIALS = "Comma-separated list of `zone:password` credential pairs" - [Configuration.Additional] - SYSE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" - SYSE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" - SYSE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - SYSE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://www.syse.no/api/dns" diff --git a/providers/dns/syse/syse_test.go b/providers/dns/syse/syse_test.go deleted file mode 100644 index a4472aa7c..000000000 --- a/providers/dns/syse/syse_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package syse - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvCredentials).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvCredentials: "example.org:123", - }, - }, - { - desc: "success multiple domains", - envVars: map[string]string{ - EnvCredentials: "example.org:123,example.com:456,example.net:789", - }, - }, - { - desc: "invalid credentials", - envVars: map[string]string{ - EnvCredentials: ",", - }, - expected: `syse: credentials: incorrect pair: `, - }, - { - desc: "missing password", - envVars: map[string]string{ - EnvCredentials: "example.org:", - }, - expected: `syse: missing password: "example.org:"`, - }, - { - desc: "missing domain", - envVars: map[string]string{ - EnvCredentials: ":123", - }, - expected: `syse: missing domain: ":123"`, - }, - { - desc: "invalid credentials, partial", - envVars: map[string]string{ - EnvCredentials: "example.org:123,example.net", - }, - expected: "syse: credentials: incorrect pair: example.net", - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvCredentials: "", - }, - expected: "syse: some credentials information are missing: SYSE_CREDENTIALS", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - creds map[string]string - expected string - }{ - { - desc: "success", - creds: map[string]string{"example.org": "123"}, - }, - { - desc: "success multiple domains", - creds: map[string]string{ - "example.org": "123", - "example.com": "456", - "example.net": "789", - }, - }, - { - desc: "missing credentials", - expected: "syse: missing credentials", - }, - { - desc: "missing domain", - creds: map[string]string{"": "123"}, - expected: `syse: missing domain: ":123"`, - }, - { - desc: "missing password", - creds: map[string]string{"example.org": ""}, - expected: `syse: missing password: "example.org:"`, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Credentials = test.creds - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.Credentials = map[string]string{ - "example.org": "secret", - } - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("/", servermock.DumpRequest()). - Route("POST /dns/example.com", - servermock.ResponseFromInternal("create_record.json"), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /dns/example.com/1234", - servermock.Noop()). - Build(t) - - provider.recordIDs["abc"] = "1234" - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/technitium/technitium.toml b/providers/dns/technitium/technitium.toml index ac1fc6466..13b40c304 100644 --- a/providers/dns/technitium/technitium.toml +++ b/providers/dns/technitium/technitium.toml @@ -7,7 +7,7 @@ Since = "v4.20.0" Example = ''' TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \ TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns technitium -d '*.example.com' -d example.com run +lego --email you@example.com --dns technitium -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/tencentcloud/tencentcloud.toml b/providers/dns/tencentcloud/tencentcloud.toml index 50f4ee9d5..7f06d9386 100644 --- a/providers/dns/tencentcloud/tencentcloud.toml +++ b/providers/dns/tencentcloud/tencentcloud.toml @@ -7,7 +7,7 @@ Since = "v4.6.0" Example = ''' TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \ TENCENTCLOUD_SECRET_KEY=your-secret-key \ -lego --dns tencentcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns tencentcloud -d '*.example.com' -d example.com run ''' [Configuration] 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/timewebcloud/timewebcloud.toml b/providers/dns/timewebcloud/timewebcloud.toml index c8bde636a..8c20b37b9 100644 --- a/providers/dns/timewebcloud/timewebcloud.toml +++ b/providers/dns/timewebcloud/timewebcloud.toml @@ -6,7 +6,7 @@ Since = "v4.20.0" Example = ''' TIMEWEBCLOUD_AUTH_TOKEN=xxxxxx \ -lego --dns timewebcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns timewebcloud -d '*.example.com' -d example.com run ''' [Configuration] 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/transip/transip.toml b/providers/dns/transip/transip.toml index bf7d58ee3..a894cc3e3 100644 --- a/providers/dns/transip/transip.toml +++ b/providers/dns/transip/transip.toml @@ -7,7 +7,7 @@ Since = "v2.0.0" Example = ''' TRANSIP_ACCOUNT_NAME = "Account name" \ TRANSIP_PRIVATE_KEY_PATH = "transip.key" \ -lego --dns transip -d '*.example.com' -d example.com run +lego --email you@example.com --dns transip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ultradns/ultradns.toml b/providers/dns/ultradns/ultradns.toml index 4c3dbbe72..a403a3dcf 100644 --- a/providers/dns/ultradns/ultradns.toml +++ b/providers/dns/ultradns/ultradns.toml @@ -7,7 +7,7 @@ Since = "v4.10.0" Example = ''' ULTRADNS_USERNAME=username \ ULTRADNS_PASSWORD=password \ -lego --dns ultradns -d '*.example.com' -d example.com run +lego --email you@example.com --dns ultradns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/uniteddomains/uniteddomains.go b/providers/dns/uniteddomains/uniteddomains.go deleted file mode 100644 index 683cab1fe..000000000 --- a/providers/dns/uniteddomains/uniteddomains.go +++ /dev/null @@ -1,105 +0,0 @@ -// Package uniteddomains implements a DNS provider for solving the DNS-01 challenge using United-Domains. -package uniteddomains - -import ( - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/ionos" -) - -// Environment variables names. -const ( - envNamespace = "UNITEDDOMAINS_" - - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultBaseURL = "https://dnsapi.united-domains.de/dns" - -const minTTL = 300 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config = ionos.Config - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - prv challenge.ProviderTimeout -} - -// NewDNSProvider returns a DNSProvider instance configured for United-Domains. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("uniteddomains: %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for United-Domains. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("uniteddomains: the configuration of the DNS provider is nil") - } - - provider, err := ionos.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("uniteddomains: %w", err) - } - - return &DNSProvider{prv: provider}, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("uniteddomains: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("uniteddomains: %w", err) - } - - return nil -} diff --git a/providers/dns/uniteddomains/uniteddomains.toml b/providers/dns/uniteddomains/uniteddomains.toml deleted file mode 100644 index fe8b9e574..000000000 --- a/providers/dns/uniteddomains/uniteddomains.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "United-Domains" -Description = '''''' -URL = "https://www.united-domains.de/" -Code = "uniteddomains" -Since = "v4.29.0" - -Example = ''' -UNITEDDOMAINS_API_KEY=xxxxxxxx \ -lego --dns uniteddomains -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - UNITEDDOMAINS_API_KEY = "API key `.` https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/" - [Configuration.Additional] - UNITEDDOMAINS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - UNITEDDOMAINS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 900)" - UNITEDDOMAINS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" - UNITEDDOMAINS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://www.united-domains.de/dns-apidoc/" diff --git a/providers/dns/uniteddomains/uniteddomains_test.go b/providers/dns/uniteddomains/uniteddomains_test.go deleted file mode 100644 index 93afb01ab..000000000 --- a/providers/dns/uniteddomains/uniteddomains_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package uniteddomains - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIKey: "123", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvAPIKey: "", - }, - expected: "uniteddomains: some credentials information are missing: UNITEDDOMAINS_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - tll int - expected string - }{ - { - desc: "success", - apiKey: "123", - tll: minTTL, - }, - { - desc: "missing credentials", - tll: minTTL, - expected: "uniteddomains: credentials missing", - }, - { - desc: "invalid TTL", - apiKey: "123", - tll: 30, - expected: "uniteddomains: invalid TTL, TTL (30) must be greater than 300", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - config.TTL = test.tll - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 2d12fd975..90ac70a05 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -180,10 +180,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("variomedia: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/variomedia/variomedia.toml b/providers/dns/variomedia/variomedia.toml index 8390d1922..fe6f2797a 100644 --- a/providers/dns/variomedia/variomedia.toml +++ b/providers/dns/variomedia/variomedia.toml @@ -6,7 +6,7 @@ Since = "v4.8.0" Example = ''' VARIOMEDIA_API_TOKEN=xxxx \ -lego --dns variomedia -d '*.example.com' -d example.com run +lego --email you@example.com --dns variomedia -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vercel/vercel.toml b/providers/dns/vercel/vercel.toml index 4700d6d78..2545b9c48 100644 --- a/providers/dns/vercel/vercel.toml +++ b/providers/dns/vercel/vercel.toml @@ -6,7 +6,7 @@ Since = "v4.7.0" Example = ''' VERCEL_API_TOKEN=xxxxxx \ -lego --dns vercel -d '*.example.com' -d example.com run +lego --email you@example.com --dns vercel -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/versio/versio.toml b/providers/dns/versio/versio.toml index 733947095..33f7125c8 100644 --- a/providers/dns/versio/versio.toml +++ b/providers/dns/versio/versio.toml @@ -7,7 +7,7 @@ Since = "v2.7.0" Example = ''' VERSIO_USERNAME= \ VERSIO_PASSWORD= \ -lego --dns versio -d '*.example.com' -d example.com run +lego --email you@example.com --dns versio -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index d6dd5810e..5789d10ab 100644 --- a/providers/dns/vinyldns/vinyldns.toml +++ b/providers/dns/vinyldns/vinyldns.toml @@ -8,7 +8,7 @@ Example = ''' VINYLDNS_ACCESS_KEY=xxxxxx \ VINYLDNS_SECRET_KEY=yyyyy \ VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \ -lego --dns vinyldns -d '*.example.com' -d example.com run +lego --email you@example.com --dns vinyldns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/virtualname/virtualname.go b/providers/dns/virtualname/virtualname.go deleted file mode 100644 index 34637d280..000000000 --- a/providers/dns/virtualname/virtualname.go +++ /dev/null @@ -1,103 +0,0 @@ -// Package virtualname implements a DNS provider for solving the DNS-01 challenge using Virtualname DNS. -package virtualname - -import ( - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica" -) - -// Environment variables names. -const ( - envNamespace = "VIRTUALNAME_" - - EnvToken = envNamespace + "TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultBaseURL = "https://api.virtualname.net/v1" - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config = tecnocratica.Config - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - prv challenge.ProviderTimeout -} - -// NewDNSProvider returns a DNSProvider instance configured for Virtualname. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvToken) - if err != nil { - return nil, fmt.Errorf("virtualname: %w", err) - } - - config := NewDefaultConfig() - config.Token = values[EnvToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Virtualname. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("virtualname: the configuration of the DNS provider is nil") - } - - provider, err := tecnocratica.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("virtualname: %w", err) - } - - return &DNSProvider{prv: provider}, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("virtualname: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("virtualname: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/virtualname/virtualname.toml b/providers/dns/virtualname/virtualname.toml deleted file mode 100644 index 881f09797..000000000 --- a/providers/dns/virtualname/virtualname.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Virtualname" -Description = '''''' -URL = "https://www.virtualname.es/" -Code = "virtualname" -Since = "v4.30.0" - -Example = ''' -VIRTUALNAME_TOKEN=xxxxxx \ -lego --dns virtualname -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - VIRTUALNAME_TOKEN = "API token" - [Configuration.Additional] - VIRTUALNAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" - VIRTUALNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" - VIRTUALNAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - VIRTUALNAME_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://developers.virtualname.net/#dns" diff --git a/providers/dns/virtualname/virtualname_test.go b/providers/dns/virtualname/virtualname_test.go deleted file mode 100644 index da5867e86..000000000 --- a/providers/dns/virtualname/virtualname_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package virtualname - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvToken: "secret", - }, - }, - { - desc: "missing credentials: token", - envVars: map[string]string{ - EnvToken: "", - }, - expected: "virtualname: some credentials information are missing: VIRTUALNAME_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - token string - expected string - }{ - { - desc: "success", - token: "secret", - }, - { - desc: "missing token", - expected: "virtualname: missing credentials", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Token = test.token - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.prv) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/vkcloud/vkcloud.toml b/providers/dns/vkcloud/vkcloud.toml index 04f57fea3..366039694 100644 --- a/providers/dns/vkcloud/vkcloud.toml +++ b/providers/dns/vkcloud/vkcloud.toml @@ -8,7 +8,7 @@ Example = ''' VK_CLOUD_PROJECT_ID="" \ VK_CLOUD_USERNAME="" \ VK_CLOUD_PASSWORD="" \ -lego --dns vkcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns vkcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index 765d38adb..9a5886e6d 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -171,10 +171,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("volcengine: delete record: %w", err) } - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } diff --git a/providers/dns/volcengine/volcengine.toml b/providers/dns/volcengine/volcengine.toml index ceedcb18a..cb7c219f7 100644 --- a/providers/dns/volcengine/volcengine.toml +++ b/providers/dns/volcengine/volcengine.toml @@ -7,7 +7,7 @@ Since = "v4.19.0" Example = ''' VOLC_ACCESSKEY=xxx \ VOLC_SECRETKEY=yyy \ -lego --dns volcengine -d '*.example.com' -d example.com run +lego --email you@example.com --dns volcengine -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index a159db307..01fae946d 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -4,14 +4,17 @@ package vscale import ( + "context" "errors" "fmt" "net/http" + "net/url" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -28,18 +31,25 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const defaultBaseURL = "https://api.vscale.io/v1/domains" +const minTTL = 60 var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = selectel.Config +type Config struct { + BaseURL string + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), - TTL: env.GetOrDefaultInt(EnvTTL, selectel.MinTTL), + BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultVScaleBaseURL), + TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -50,7 +60,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *selectel.Client } // NewDNSProvider returns a DNSProvider instance configured for Vscale Domains API. @@ -73,40 +84,95 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vscale: the configuration of the DNS provider is nil") } - if config.BaseURL == "" { - config.BaseURL = defaultBaseURL + if config.Token == "" { + return nil, errors.New("vscale: credentials missing") } - provider, err := selectel.NewDNSProviderConfig(config) + if config.TTL < minTTL { + return nil, fmt.Errorf("vscale: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := selectel.NewClient(config.Token) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + var err error + + client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("vscale: %w", err) } - return &DNSProvider{prv: provider}, nil + 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 { - err := d.prv.Present(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("vscale: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) - if err != nil { - return fmt.Errorf("vscale: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Timeout returns the Timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill DNS-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("vscale: %w", err) + } + + txtRecord := selectel.Record{ + Type: "TXT", + TTL: d.config.TTL, + Name: info.EffectiveFQDN, + Content: info.Value, + } + + _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) + if err != nil { + return fmt.Errorf("vscale: %w", err) + } + + return nil +} + +// CleanUp removes a TXT record used for DNS-01 challenge. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + recordName := dns01.UnFqdn(info.EffectiveFQDN) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("vscale: %w", err) + } + + records, err := d.client.ListRecords(ctx, domainObj.ID) + if err != nil { + return fmt.Errorf("vscale: %w", err) + } + + // Delete records with specific FQDN + var lastErr error + + for _, record := range records { + if record.Name == recordName { + err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) + if err != nil { + lastErr = fmt.Errorf("vscale: %w", err) + } + } + } + + return lastErr } diff --git a/providers/dns/vscale/vscale.toml b/providers/dns/vscale/vscale.toml index f7dc0d943..78b6c99e5 100644 --- a/providers/dns/vscale/vscale.toml +++ b/providers/dns/vscale/vscale.toml @@ -6,7 +6,7 @@ Since = "v2.0.0" Example = ''' VSCALE_API_TOKEN=xxxxx \ -lego --dns vscale -d '*.example.com' -d example.com run +lego --email you@example.com --dns vscale -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vscale/vscale_test.go b/providers/dns/vscale/vscale_test.go index 9012c7563..f3bc15531 100644 --- a/providers/dns/vscale/vscale_test.go +++ b/providers/dns/vscale/vscale_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/providers/dns/internal/selectel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -47,7 +46,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.prv) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -77,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { desc: "bad TTL value", token: "123", ttl: 59, - expected: fmt.Sprintf("vscale: invalid TTL, TTL (59) must be greater than %d", selectel.MinTTL), + expected: fmt.Sprintf("vscale: invalid TTL, TTL (59) must be greater than %d", minTTL), }, } @@ -92,7 +92,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.prv) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index f97a321c1..2064cee19 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -107,7 +107,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("vultr: %w", err) } - req := govultr.DomainRecordCreateReq{ + req := govultr.DomainRecordReq{ Name: subDomain, Type: "TXT", Data: `"` + info.Value + `"`, diff --git a/providers/dns/vultr/vultr.toml b/providers/dns/vultr/vultr.toml index 78e878bea..7d31bd52b 100644 --- a/providers/dns/vultr/vultr.toml +++ b/providers/dns/vultr/vultr.toml @@ -6,7 +6,7 @@ Since = "v0.3.1" Example = ''' VULTR_API_KEY=xxxxx \ -lego --dns vultr -d '*.example.com' -d example.com run +lego --email you@example.com --dns vultr -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/webnames/webnames.toml b/providers/dns/webnames/webnames.toml index b038deaf5..04dea25c5 100644 --- a/providers/dns/webnames/webnames.toml +++ b/providers/dns/webnames/webnames.toml @@ -7,7 +7,7 @@ Since = "v4.15.0" Example = ''' WEBNAMESRU_API_KEY=xxxxxx \ -lego --dns webnamesru -d '*.example.com' -d example.com run +lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/webnamesca/webnamesca.toml b/providers/dns/webnamesca/webnamesca.toml index ab68a04a0..c7d30751b 100644 --- a/providers/dns/webnamesca/webnamesca.toml +++ b/providers/dns/webnamesca/webnamesca.toml @@ -7,7 +7,7 @@ Since = "v4.28.0" Example = ''' WEBNAMESCA_API_USER="xxx" \ WEBNAMESCA_API_KEY="yyy" \ -lego --dns webnamesca -d '*.example.com' -d example.com run +lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go index 4187ba32b..7f93653c9 100644 --- a/providers/dns/websupport/websupport.go +++ b/providers/dns/websupport/websupport.go @@ -2,15 +2,17 @@ package websupport import ( + "context" "errors" "fmt" "net/http" + "strconv" "time" - "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "websupport.sk" @@ -29,7 +31,15 @@ const ( ) // Config is used to configure the creation of the DNSProvider. -type Config = active24.Config +type Config struct { + APIKey string + Secret string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -45,7 +55,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *active24.Client } // NewDNSProvider returns a DNSProvider instance configured for Websupport. @@ -69,29 +80,83 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("websupport: the configuration of the DNS provider is nil") } - provider, err := active24.NewDNSProviderConfig(config, baseAPIDomain) + client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) if err != nil { return nil, fmt.Errorf("websupport: %w", err) } - return &DNSProvider{prv: provider}, nil + 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 { - err := d.prv.Present(domain, token, keyAuth) + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("websupport: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { return fmt.Errorf("websupport: %w", err) } + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("websupport: find service ID: %w", err) + } + + record := active24.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) + if err != nil { + return fmt.Errorf("websupport: create record: %w", err) + } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("websupport: %w", err) + return fmt.Errorf("websupport: could not find zone for domain %q: %w", domain, err) + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("websupport: find service ID: %w", err) + } + + recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) + if err != nil { + return fmt.Errorf("websupport: find record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) + if err != nil { + return fmt.Errorf("websupport: delete record %w", err) } return nil @@ -100,5 +165,58 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { + services, err := d.client.GetServices(ctx) + if err != nil { + return 0, fmt.Errorf("get services: %w", err) + } + + for _, service := range services { + if service.ServiceName != "domain" { + continue + } + + if service.Name != domain { + continue + } + + return service.ID, nil + } + + return 0, fmt.Errorf("service not found for domain: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { + // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. + filter := active24.RecordFilter{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: []string{"TXT"}, + Content: info.Value, + } + + records, err := d.client.GetRecords(ctx, serviceID, filter) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" { + continue + } + + if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { + continue + } + + if record.Content != info.Value { + continue + } + + return record.ID, nil + } + + return 0, errors.New("no record found") } diff --git a/providers/dns/websupport/websupport.toml b/providers/dns/websupport/websupport.toml index 4908f0235..1f34b431b 100644 --- a/providers/dns/websupport/websupport.toml +++ b/providers/dns/websupport/websupport.toml @@ -7,7 +7,7 @@ Since = "v4.10.0" Example = ''' WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \ -lego --dns websupport -d '*.example.com' -d example.com run +lego --email you@example.com --dns websupport -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/websupport/websupport_test.go b/providers/dns/websupport/websupport_test.go index 196c9bab8..c7b8572b5 100644 --- a/providers/dns/websupport/websupport_test.go +++ b/providers/dns/websupport/websupport_test.go @@ -60,7 +60,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -109,7 +110,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/wedos/wedos.toml b/providers/dns/wedos/wedos.toml index 89abfc16c..2ed351a2d 100644 --- a/providers/dns/wedos/wedos.toml +++ b/providers/dns/wedos/wedos.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' WEDOS_USERNAME=xxxxxxxx \ WEDOS_WAPI_PASSWORD=xxxxxxxx \ -lego --dns wedos -d '*.example.com' -d example.com run +lego --email you@example.com --dns wedos -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/internal/westcn/internal/client.go b/providers/dns/westcn/internal/client.go similarity index 98% rename from providers/dns/internal/westcn/internal/client.go rename to providers/dns/westcn/internal/client.go index 621c7865f..bfed159ae 100644 --- a/providers/dns/internal/westcn/internal/client.go +++ b/providers/dns/westcn/internal/client.go @@ -30,7 +30,7 @@ type Client struct { encoder *encoding.Encoder - BaseURL *url.URL + baseURL *url.URL HTTPClient *http.Client } @@ -46,7 +46,7 @@ func NewClient(username, password string) (*Client, error) { username: username, password: password, encoder: simplifiedchinese.GBK.NewEncoder(), - BaseURL: baseURL, + baseURL: baseURL, HTTPClient: &http.Client{Timeout: 10 * time.Second}, }, nil } @@ -116,7 +116,7 @@ func (c *Client) newRequest(ctx context.Context, p, act string, form url.Values) return nil, err } - endpoint := c.BaseURL.JoinPath(p, "/") + endpoint := c.baseURL.JoinPath(p, "/") query := endpoint.Query() query.Set("act", act) diff --git a/providers/dns/internal/westcn/internal/client_test.go b/providers/dns/westcn/internal/client_test.go similarity index 98% rename from providers/dns/internal/westcn/internal/client_test.go rename to providers/dns/westcn/internal/client_test.go index 53fd6ed8f..f7bdac5c0 100644 --- a/providers/dns/internal/westcn/internal/client_test.go +++ b/providers/dns/westcn/internal/client_test.go @@ -21,7 +21,7 @@ func mockBuilder() *servermock.Builder[*Client] { } client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) + client.baseURL, _ = url.Parse(server.URL) return client, nil }, @@ -69,8 +69,7 @@ func TestClientAddRecord_error(t *testing.T) { servermock.ResponseFromFixture("error.json"). WithHeader("Content-Type", "application/json", "Charset=gb2312"), servermock.CheckQueryParameter().Strict(). - With("act", "adddnsrecord"), - ). + With("act", "adddnsrecord")). Build(t) record := Record{ diff --git a/providers/dns/internal/westcn/internal/fixtures/adddnsrecord.json b/providers/dns/westcn/internal/fixtures/adddnsrecord.json similarity index 100% rename from providers/dns/internal/westcn/internal/fixtures/adddnsrecord.json rename to providers/dns/westcn/internal/fixtures/adddnsrecord.json diff --git a/providers/dns/internal/westcn/internal/fixtures/deldnsrecord.json b/providers/dns/westcn/internal/fixtures/deldnsrecord.json similarity index 100% rename from providers/dns/internal/westcn/internal/fixtures/deldnsrecord.json rename to providers/dns/westcn/internal/fixtures/deldnsrecord.json diff --git a/providers/dns/internal/westcn/internal/fixtures/error.json b/providers/dns/westcn/internal/fixtures/error.json similarity index 100% rename from providers/dns/internal/westcn/internal/fixtures/error.json rename to providers/dns/westcn/internal/fixtures/error.json diff --git a/providers/dns/internal/westcn/internal/types.go b/providers/dns/westcn/internal/types.go similarity index 100% rename from providers/dns/internal/westcn/internal/types.go rename to providers/dns/westcn/internal/types.go diff --git a/providers/dns/westcn/westcn.go b/providers/dns/westcn/westcn.go index 1906f9737..c641f661d 100644 --- a/providers/dns/westcn/westcn.go +++ b/providers/dns/westcn/westcn.go @@ -2,14 +2,18 @@ package westcn import ( + "context" "errors" "fmt" "net/http" + "sync" "time" "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/westcn" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/westcn/internal" ) // Environment variables names. @@ -25,12 +29,18 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const defaultBaseURL = "https://api.west.cn/api/v2" - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = westcn.Config +type Config struct { + Username string + Password string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -46,7 +56,11 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *internal.Client + + recordIDs map[string]int + recordIDsMu sync.Mutex } // NewDNSProvider returns a DNSProvider instance configured for West.cn/西部数码. @@ -69,36 +83,91 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("westcn: the configuration of the DNS provider is nil") } - provider, err := westcn.NewDNSProviderConfig(config, defaultBaseURL) + client, err := internal.NewClient(config.Username, config.Password) if err != nil { return nil, fmt.Errorf("westcn: %w", err) } - return &DNSProvider{prv: provider}, nil + 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 { - err := d.prv.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { return fmt.Errorf("westcn: %w", err) } + record := internal.Record{ + Domain: dns01.UnFqdn(authZone), + Host: subDomain, + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + } + + recordID, err := d.client.AddRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("westcn: add record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + d.recordIDsMu.Unlock() + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("westcn: %w", err) + return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) } + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("westcn: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("westcn: delete record: %w", err) + } + + // deletes record ID from map + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() + return d.config.PropagationTimeout, d.config.PollingInterval } diff --git a/providers/dns/westcn/westcn.toml b/providers/dns/westcn/westcn.toml index 1b0cb0a7a..acf3a4e49 100644 --- a/providers/dns/westcn/westcn.toml +++ b/providers/dns/westcn/westcn.toml @@ -7,7 +7,7 @@ Since = "v4.21.0" Example = ''' WESTCN_USERNAME="xxx" \ WESTCN_PASSWORD="yyy" \ -lego --dns westcn -d '*.example.com' -d example.com run +lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/westcn/westcn_test.go b/providers/dns/westcn/westcn_test.go index a546d518e..36827fd06 100644 --- a/providers/dns/westcn/westcn_test.go +++ b/providers/dns/westcn/westcn_test.go @@ -60,7 +60,8 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } @@ -107,7 +108,8 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) + require.NotNil(t, p.client) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/yandex/yandex.toml b/providers/dns/yandex/yandex.toml index a36df069e..78da1444d 100644 --- a/providers/dns/yandex/yandex.toml +++ b/providers/dns/yandex/yandex.toml @@ -7,7 +7,7 @@ Since = "v3.7.0" Example = ''' YANDEX_PDD_TOKEN= \ -lego --dns yandex -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandex -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/yandex360/yandex360.toml b/providers/dns/yandex360/yandex360.toml index 444b1cc38..69ea02a28 100644 --- a/providers/dns/yandex360/yandex360.toml +++ b/providers/dns/yandex360/yandex360.toml @@ -8,7 +8,7 @@ Since = "v4.14.0" Example = ''' YANDEX360_OAUTH_TOKEN= \ YANDEX360_ORG_ID= \ -lego --dns yandex360 -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandex360 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/yandexcloud/yandexcloud.toml b/providers/dns/yandexcloud/yandexcloud.toml index d4b40bb1d..a4bf3ebbb 100644 --- a/providers/dns/yandexcloud/yandexcloud.toml +++ b/providers/dns/yandexcloud/yandexcloud.toml @@ -7,7 +7,7 @@ Since = "v4.9.0" Example = ''' YANDEX_CLOUD_IAM_TOKEN= \ YANDEX_CLOUD_FOLDER_ID= \ -lego --dns yandexcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run # --- @@ -20,7 +20,7 @@ YANDEX_CLOUD_IAM_TOKEN=$(echo '{ \ "private_key": "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----" \ }' | base64) \ YANDEX_CLOUD_FOLDER_ID= \ -lego --dns yandexcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/zoneedit/zoneedit.toml b/providers/dns/zoneedit/zoneedit.toml index cdc53b33a..d3c547c23 100644 --- a/providers/dns/zoneedit/zoneedit.toml +++ b/providers/dns/zoneedit/zoneedit.toml @@ -7,7 +7,7 @@ Since = "v4.25.0" Example = ''' ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns zoneedit -d '*.example.com' -d example.com run +lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/zoneee/zoneee.toml b/providers/dns/zoneee/zoneee.toml index ab7133180..75ccabbda 100644 --- a/providers/dns/zoneee/zoneee.toml +++ b/providers/dns/zoneee/zoneee.toml @@ -7,7 +7,7 @@ Since = "v2.1.0" Example = ''' ZONEEE_API_USER=xxxxx \ ZONEEE_API_KEY=yyyyy \ -lego --dns zoneee -d '*.example.com' -d example.com run +lego --email you@example.com --dns zoneee -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/zonomi/zonomi.go b/providers/dns/zonomi/zonomi.go index fe54b80fc..e6eae08de 100644 --- a/providers/dns/zonomi/zonomi.go +++ b/providers/dns/zonomi/zonomi.go @@ -2,6 +2,7 @@ package zonomi import ( + "context" "errors" "fmt" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -25,17 +27,22 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const defaultBaseURL = "https://zonomi.com/app/dns/dyndns.jsp" - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config = rimuhosting.Config +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, rimuhosting.DefaultTTL), + TTL: env.GetOrDefaultInt(EnvTTL, 3600), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -46,7 +53,8 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - prv challenge.ProviderTimeout + config *Config + client *rimuhosting.Client } // NewDNSProvider returns a DNSProvider instance configured for Zonomi. @@ -69,19 +77,50 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("zonomi: the configuration of the DNS provider is nil") } - provider, err := rimuhosting.NewDNSProviderConfig(config, defaultBaseURL) - if err != nil { - return nil, fmt.Errorf("zonomi: %w", err) + if config.APIKey == "" { + return nil, errors.New("zonomi: incomplete credentials, missing API key") } - return &DNSProvider{prv: provider}, nil + client := rimuhosting.NewClient(config.APIKey) + client.BaseURL = rimuhosting.DefaultZonomiBaseURL + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - err := d.prv.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { - return fmt.Errorf("zonomi: %w", err) + return fmt.Errorf("zonomi: failed to find record(s) for %s: %w", domain, err) + } + + actions := []rimuhosting.ActionParameter{ + rimuhosting.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), + } + + for _, record := range records { + actions = append(actions, rimuhosting.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) + } + + _, err = d.client.DoActions(ctx, actions...) + if err != nil { + return fmt.Errorf("zonomi: failed to add record(s) for %s: %w", domain, err) } return nil @@ -89,16 +128,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - err := d.prv.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + action := rimuhosting.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) + + _, err := d.client.DoActions(context.Background(), action) if err != nil { - return fmt.Errorf("zonomi: %w", err) + return fmt.Errorf("zonomi: failed to delete record for %s: %w", domain, err) } return nil } - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.prv.Timeout() -} diff --git a/providers/dns/zonomi/zonomi.toml b/providers/dns/zonomi/zonomi.toml index b91bcaac6..a5577999a 100644 --- a/providers/dns/zonomi/zonomi.toml +++ b/providers/dns/zonomi/zonomi.toml @@ -6,7 +6,7 @@ Since = "v3.5.0" Example = ''' ZONOMI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --dns zonomi -d '*.example.com' -d example.com run +lego --email you@example.com --dns zonomi -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/zonomi/zonomi_test.go b/providers/dns/zonomi/zonomi_test.go index 2e13e937e..0583f4a1c 100644 --- a/providers/dns/zonomi/zonomi_test.go +++ b/providers/dns/zonomi/zonomi_test.go @@ -46,7 +46,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) } else { require.EqualError(t, err, test.expected) } @@ -84,7 +84,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.prv) + require.NotNil(t, p.config) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 9c4bc9e61..32de816a3 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -6,14 +6,12 @@ import ( "fmt" "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/providers/dns/acmedns" "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" - "github.com/go-acme/lego/v4/providers/dns/aliesa" "github.com/go-acme/lego/v4/providers/dns/allinkl" - "github.com/go-acme/lego/v4/providers/dns/alwaysdata" "github.com/go-acme/lego/v4/providers/dns/anexia" - "github.com/go-acme/lego/v4/providers/dns/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 +24,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" @@ -37,20 +34,16 @@ import ( "github.com/go-acme/lego/v4/providers/dns/cloudns" "github.com/go-acme/lego/v4/providers/dns/cloudru" "github.com/go-acme/lego/v4/providers/dns/cloudxns" - "github.com/go-acme/lego/v4/providers/dns/com35" "github.com/go-acme/lego/v4/providers/dns/conoha" "github.com/go-acme/lego/v4/providers/dns/conohav3" "github.com/go-acme/lego/v4/providers/dns/constellix" "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" @@ -63,13 +56,10 @@ import ( "github.com/go-acme/lego/v4/providers/dns/dyndnsfree" "github.com/go-acme/lego/v4/providers/dns/dynu" "github.com/go-acme/lego/v4/providers/dns/easydns" - "github.com/go-acme/lego/v4/providers/dns/edgecenter" "github.com/go-acme/lego/v4/providers/dns/edgedns" "github.com/go-acme/lego/v4/providers/dns/edgeone" "github.com/go-acme/lego/v4/providers/dns/efficientip" "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" @@ -78,15 +68,12 @@ import ( "github.com/go-acme/lego/v4/providers/dns/gandiv5" "github.com/go-acme/lego/v4/providers/dns/gcloud" "github.com/go-acme/lego/v4/providers/dns/gcore" - "github.com/go-acme/lego/v4/providers/dns/gigahostno" "github.com/go-acme/lego/v4/providers/dns/glesys" "github.com/go-acme/lego/v4/providers/dns/godaddy" "github.com/go-acme/lego/v4/providers/dns/googledomains" - "github.com/go-acme/lego/v4/providers/dns/gravity" "github.com/go-acme/lego/v4/providers/dns/hetzner" "github.com/go-acme/lego/v4/providers/dns/hostingde" "github.com/go-acme/lego/v4/providers/dns/hostinger" - "github.com/go-acme/lego/v4/providers/dns/hostingnl" "github.com/go-acme/lego/v4/providers/dns/hosttech" "github.com/go-acme/lego/v4/providers/dns/httpnet" "github.com/go-acme/lego/v4/providers/dns/httpreq" @@ -101,15 +88,10 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internetbs" "github.com/go-acme/lego/v4/providers/dns/inwx" "github.com/go-acme/lego/v4/providers/dns/ionos" - "github.com/go-acme/lego/v4/providers/dns/ionoscloud" "github.com/go-acme/lego/v4/providers/dns/ipv64" - "github.com/go-acme/lego/v4/providers/dns/ispconfig" - "github.com/go-acme/lego/v4/providers/dns/ispconfigddns" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" - "github.com/go-acme/lego/v4/providers/dns/jdcloud" "github.com/go-acme/lego/v4/providers/dns/joker" "github.com/go-acme/lego/v4/providers/dns/keyhelp" - "github.com/go-acme/lego/v4/providers/dns/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" @@ -119,7 +101,6 @@ import ( "github.com/go-acme/lego/v4/providers/dns/luadns" "github.com/go-acme/lego/v4/providers/dns/mailinabox" "github.com/go-acme/lego/v4/providers/dns/manageengine" - "github.com/go-acme/lego/v4/providers/dns/manual" "github.com/go-acme/lego/v4/providers/dns/metaname" "github.com/go-acme/lego/v4/providers/dns/metaregistrar" "github.com/go-acme/lego/v4/providers/dns/mijnhost" @@ -130,9 +111,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/namecheap" "github.com/go-acme/lego/v4/providers/dns/namedotcom" "github.com/go-acme/lego/v4/providers/dns/namesilo" - "github.com/go-acme/lego/v4/providers/dns/namesurfer" "github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech" - "github.com/go-acme/lego/v4/providers/dns/neodigit" "github.com/go-acme/lego/v4/providers/dns/netcup" "github.com/go-acme/lego/v4/providers/dns/netlify" "github.com/go-acme/lego/v4/providers/dns/nicmanager" @@ -168,20 +147,16 @@ import ( "github.com/go-acme/lego/v4/providers/dns/sonic" "github.com/go-acme/lego/v4/providers/dns/spaceship" "github.com/go-acme/lego/v4/providers/dns/stackpath" - "github.com/go-acme/lego/v4/providers/dns/syse" "github.com/go-acme/lego/v4/providers/dns/technitium" "github.com/go-acme/lego/v4/providers/dns/tencentcloud" "github.com/go-acme/lego/v4/providers/dns/timewebcloud" - "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" "github.com/go-acme/lego/v4/providers/dns/variomedia" "github.com/go-acme/lego/v4/providers/dns/vegadns" "github.com/go-acme/lego/v4/providers/dns/vercel" "github.com/go-acme/lego/v4/providers/dns/versio" "github.com/go-acme/lego/v4/providers/dns/vinyldns" - "github.com/go-acme/lego/v4/providers/dns/virtualname" "github.com/go-acme/lego/v4/providers/dns/vkcloud" "github.com/go-acme/lego/v4/providers/dns/volcengine" "github.com/go-acme/lego/v4/providers/dns/vscale" @@ -202,22 +177,18 @@ import ( // NewDNSChallengeProviderByName Factory for DNS providers. func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { switch name { + case "manual": + return dns01.NewDNSProviderManual() case "acme-dns", "acmedns": return acmedns.NewDNSProvider() case "active24": return active24.NewDNSProvider() case "alidns": return alidns.NewDNSProvider() - case "aliesa": - return aliesa.NewDNSProvider() case "allinkl": return allinkl.NewDNSProvider() - case "alwaysdata": - return alwaysdata.NewDNSProvider() case "anexia": return anexia.NewDNSProvider() - case "artfiles": - return artfiles.NewDNSProvider() case "arvancloud": return arvancloud.NewDNSProvider() case "auroradns": @@ -242,8 +213,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": @@ -264,8 +233,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return cloudru.NewDNSProvider() case "cloudxns": return cloudxns.NewDNSProvider() - case "com35": - return com35.NewDNSProvider() case "conoha": return conoha.NewDNSProvider() case "conohav3": @@ -276,10 +243,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 +253,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": @@ -316,8 +277,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return dynu.NewDNSProvider() case "easydns": return easydns.NewDNSProvider() - case "edgecenter": - return edgecenter.NewDNSProvider() case "edgedns", "fastdns": return edgedns.NewDNSProvider() case "edgeone": @@ -326,10 +285,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": @@ -346,24 +301,18 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return gcloud.NewDNSProvider() case "gcore": return gcore.NewDNSProvider() - case "gigahostno": - return gigahostno.NewDNSProvider() case "glesys": return glesys.NewDNSProvider() case "godaddy": return godaddy.NewDNSProvider() case "googledomains": return googledomains.NewDNSProvider() - case "gravity": - return gravity.NewDNSProvider() case "hetzner": return hetzner.NewDNSProvider() case "hostingde": return hostingde.NewDNSProvider() case "hostinger": return hostinger.NewDNSProvider() - case "hostingnl": - return hostingnl.NewDNSProvider() case "hosttech": return hosttech.NewDNSProvider() case "httpnet": @@ -392,24 +341,14 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return inwx.NewDNSProvider() case "ionos": return ionos.NewDNSProvider() - case "ionoscloud": - return ionoscloud.NewDNSProvider() case "ipv64": return ipv64.NewDNSProvider() - case "ispconfig": - return ispconfig.NewDNSProvider() - case "ispconfigddns": - return ispconfigddns.NewDNSProvider() case "iwantmyname": return iwantmyname.NewDNSProvider() - case "jdcloud": - return jdcloud.NewDNSProvider() case "joker": return joker.NewDNSProvider() case "keyhelp": return keyhelp.NewDNSProvider() - case "leaseweb": - return leaseweb.NewDNSProvider() case "liara": return liara.NewDNSProvider() case "lightsail": @@ -428,8 +367,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return mailinabox.NewDNSProvider() case "manageengine": return manageengine.NewDNSProvider() - case "manual": - return manual.NewDNSProvider() case "metaname": return metaname.NewDNSProvider() case "metaregistrar": @@ -450,12 +387,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return namedotcom.NewDNSProvider() case "namesilo": return namesilo.NewDNSProvider() - case "namesurfer": - return namesurfer.NewDNSProvider() case "nearlyfreespeech": return nearlyfreespeech.NewDNSProvider() - case "neodigit": - return neodigit.NewDNSProvider() case "netcup": return netcup.NewDNSProvider() case "netlify": @@ -526,22 +459,16 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return spaceship.NewDNSProvider() case "stackpath": return stackpath.NewDNSProvider() - case "syse": - return syse.NewDNSProvider() case "technitium": return technitium.NewDNSProvider() case "tencentcloud": return tencentcloud.NewDNSProvider() case "timewebcloud": return timewebcloud.NewDNSProvider() - case "todaynic": - return todaynic.NewDNSProvider() case "transip": return transip.NewDNSProvider() case "ultradns": return ultradns.NewDNSProvider() - case "uniteddomains": - return uniteddomains.NewDNSProvider() case "variomedia": return variomedia.NewDNSProvider() case "vegadns": @@ -552,8 +479,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return versio.NewDNSProvider() case "vinyldns": return vinyldns.NewDNSProvider() - case "virtualname": - return virtualname.NewDNSProvider() case "vkcloud": return vkcloud.NewDNSProvider() case "volcengine":