diff --git a/.gitattributes b/.gitattributes index ae17ee40c..a91e62484 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ **/zz_gen_*.* linguist-generated -docs/data/zz_cli_help.toml linguist-generated diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ea3fd9a3a..f8591e2a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,9 +7,9 @@ body: attributes: label: Welcome options: - - label: Yes, I'm using a binary release within the two latest releases. + - label: Yes, I'm using a binary release within 2 latest releases. required: true - - label: Yes, I've searched for similar issues on GitHub and didn't find any. + - label: Yes, I've searched similar issues on GitHub and didn't find any. required: true - label: Yes, I've included all information below (version, config, etc). required: true @@ -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..b29c0d9f5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -6,7 +6,7 @@ body: attributes: label: Welcome options: - - label: Yes, I've searched for similar issues on GitHub and didn't find any. + - label: Yes, I've searched similar issues on GitHub and didn't find any. required: true - type: dropdown @@ -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..f310e9815 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -8,21 +8,15 @@ body: attributes: label: Welcome options: - - label: Yes, I've searched for similar issues on GitHub and didn't find any. + - label: Yes, I've searched similar issues on GitHub and didn't find any. required: true - label: Yes, the DNS provider exposes a public API. 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,10 @@ 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..46f7f6730 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,16 +12,20 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - HUGO_VERSION: 0.148.2 + HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 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..6930ce70a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,44 +13,59 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.10 - HUGO_VERSION: 0.148.2 + GOLANGCI_LINT_VERSION: v2.2.1 + HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI MEMCACHED_HOSTS: localhost:11211 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: Setup /etc/hosts + run: | + echo "127.0.0.1 acme.wtf" | sudo tee -a /etc/hosts + echo "127.0.0.1 lego.wtf" | sudo tee -a /etc/hosts + echo "127.0.0.1 acme.lego.wtf" | sudo tee -a /etc/hosts + echo "127.0.0.1 légô.wtf" | sudo tee -a /etc/hosts + echo "127.0.0.1 xn--lg-bja9b.wtf" | sudo tee -a /etc/hosts - name: Make run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a0d3b703..ee3ea21dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,11 +5,6 @@ on: tags: - v* -permissions: - # Allow the workflow to write attestations. - id-token: write - attestations: write - jobs: release: @@ -42,11 +37,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,21 +64,11 @@ jobs: # https://goreleaser.com/ci/actions/ - name: Run GoReleaser - id: goreleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.13.0 + version: v2.8.1 args: release -p 1 --clean --timeout=90m env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} AUR_KEY: ${{ secrets.AUR_KEY }} - - - uses: actions/attest-build-provenance@v3 - with: - subject-checksums: ./dist/lego_${{ fromJSON(steps.goreleaser.outputs.metadata).version }}_checksums.txt - github-token: ${{ secrets.GH_TOKEN_REPO }} - - uses: actions/attest-build-provenance@v3 - with: - subject-checksums: ./dist/digests.txt - github-token: ${{ secrets.GH_TOKEN_REPO }} diff --git a/.golangci.yml b/.golangci.yml index b6ab51ccc..66f3fd9d0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -50,8 +50,11 @@ linters: - tagliatelle - testpackage # not relevant - tparallel # not relevant + - usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96 - varnamelen # not relevant - wrapcheck + - wsl_v5 # should be enabled the future. + - embeddedstructfieldcheck # should be enabled the future. settings: depguard: @@ -180,12 +183,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: @@ -198,8 +195,8 @@ linters: text: dnsTimeout is a global variable linters: - gochecknoglobals - - path: challenge/dns01/precheck.go - text: defaultNameserverPort is a global variable + - path: challenge/dns01/nameserver_test.go + text: findXByFqdnTestCases is a global variable linters: - gochecknoglobals - path: challenge/http01/domain_matcher.go @@ -222,7 +219,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 +231,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 +266,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..9bf101420 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -42,10 +42,6 @@ builds: goarch: 386 - goos: openbsd goarch: arm - # Deprecated in go1.25, Removed in go1.26 - # https://go.dev/doc/go1.25#windows - - goos: windows - goarch: arm changelog: sort: asc @@ -55,20 +51,6 @@ changelog: - '(?i)^Detach v[\d|.]+' - '(?i)^Prepare release v[\d|.]+' -release: - skip_upload: false - github: - owner: 'go-acme' - name: 'lego' - header: | - lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ - - Everybody thinks that the others will donate, but in the end, nobody does. - - So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). - - For key updates, see the [changelog](https://github.com/go-acme/lego/blob/HEAD/CHANGELOG.md#v{{ .Major }}{{ .Minor }}{{ .Patch }}). - archives: - id: lego name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' @@ -80,29 +62,87 @@ archives: - LICENSE - CHANGELOG.md -dockers_v2: - - images: - - 'goacme/lego' +docker_manifests: + - name_template: 'goacme/lego:{{ .Tag }}' + image_templates: + - 'goacme/lego:{{ .Tag }}-amd64' + - 'goacme/lego:{{ .Tag }}-arm64' + - 'goacme/lego:{{ .Tag }}-armv7' + - name_template: 'goacme/lego:latest' + image_templates: + - 'goacme/lego:{{ .Tag }}-amd64' + - 'goacme/lego:{{ .Tag }}-arm64' + - 'goacme/lego:{{ .Tag }}-armv7' + - name_template: 'goacme/lego:v{{ .Major }}.{{ .Minor }}' + image_templates: + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-amd64' + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-arm64' + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-armv7' + +dockers: + - use: buildx + goos: linux + goarch: amd64 dockerfile: buildx.Dockerfile - platforms: - - linux/amd64 - - linux/arm64 - - linux/arm/v7 - tags: - - 'latest' - - 'v{{ .Major }}' - - 'v{{ .Major }}.{{ .Minor }}' - - '{{ .Tag }}' - labels: + image_templates: + - 'goacme/lego:latest-amd64' + - 'goacme/lego:{{ .Tag }}-amd64' + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-amd64' + build_flag_templates: + - '--pull' # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys - 'org.opencontainers.image.title': '{{.ProjectName}}' - 'org.opencontainers.image.description': 'Lets Encrypt/ACME client and library written in Go' - 'org.opencontainers.image.source': '{{.GitURL}}' - 'org.opencontainers.image.url': '{{.GitURL}}' - 'org.opencontainers.image.documentation': 'https://go-acme.github.io/lego' - 'org.opencontainers.image.created': '{{.Date}}' - 'org.opencontainers.image.revision': '{{.FullCommit}}' - 'org.opencontainers.image.version': '{{.Version}}' + - '--label=org.opencontainers.image.title={{.ProjectName}}' + - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' + - '--label=org.opencontainers.image.source={{.GitURL}}' + - '--label=org.opencontainers.image.url={{.GitURL}}' + - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' + - '--label=org.opencontainers.image.created={{.Date}}' + - '--label=org.opencontainers.image.revision={{.FullCommit}}' + - '--label=org.opencontainers.image.version={{.Version}}' + - '--platform=linux/amd64' + + - use: buildx + goos: linux + goarch: arm64 + dockerfile: buildx.Dockerfile + image_templates: + - 'goacme/lego:latest-arm64' + - 'goacme/lego:{{ .Tag }}-arm64' + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-arm64' + build_flag_templates: + - '--pull' + # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys + - '--label=org.opencontainers.image.title={{.ProjectName}}' + - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' + - '--label=org.opencontainers.image.source={{.GitURL}}' + - '--label=org.opencontainers.image.url={{.GitURL}}' + - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' + - '--label=org.opencontainers.image.created={{.Date}}' + - '--label=org.opencontainers.image.revision={{.FullCommit}}' + - '--label=org.opencontainers.image.version={{.Version}}' + - '--platform=linux/arm64' + + - use: buildx + goos: linux + goarch: arm + goarm: '7' + dockerfile: buildx.Dockerfile + image_templates: + - 'goacme/lego:latest-armv7' + - 'goacme/lego:{{ .Tag }}-armv7' + - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-armv7' + build_flag_templates: + - '--pull' + # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys + - '--label=org.opencontainers.image.title={{.ProjectName}}' + - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' + - '--label=org.opencontainers.image.source={{.GitURL}}' + - '--label=org.opencontainers.image.url={{.GitURL}}' + - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' + - '--label=org.opencontainers.image.created={{.Date}}' + - '--label=org.opencontainers.image.revision={{.FullCommit}}' + - '--label=org.opencontainers.image.version={{.Version}}' + - '--platform=linux/arm/v7' snapcrafts: - name_template: "{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73f70f3..98c7a97ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,221 +1,12 @@ # Changelog -lego is an independent, free, open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ - -Everybody thinks that the others will donate, but in the end, nobody does. - -So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). - -## v4.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 -- Tag: [v4.28.0](https://github.com/go-acme/lego/releases/tag/v4.28.0) - -### Added - -- **[dnsprovider]** Add DNS provider for Anexia -- **[dnsprovider]** Add DNS provider for webnames.ca -- **[dnsprovider]** webnames: rename to webnamesru to avoid ambiguity with webnamesca - -### Changed - -- **[dnsprovider,log]** hetzner: add deprecation logs -- **[dnsprovider]** iwantmyname: provider deprecation -- **[cli]** improve retryable HTTP client error handling - -### Fixed - -- **[dnsprovider]** hostinger: fix record update - -## v4.27.0 - -- Release date: 2025-10-17 -- Tag: [v4.27.0](https://github.com/go-acme/lego/releases/tag/v4.27.0) - -### Added - -- **[dnsprovider]** Add DNS provider for Octenium -- **[dnsprovider]** Add DNS provider for Hostinger -- **[dnsprovider]** Add DNS provider for Beget.com - -### Changed - -- **[cli]** support `--private-key` with a PKCS#8 keypair -- **[dnsprovider]** hetzner: update to new API -- **[dnsprovider]** otc: adds option to use private zone - -### Fixed - -- **[lib]** fix: deduplicate order identifiers - -## v4.26.0 - -- Release date: 2025-09-13 -- Tag: [v4.26.0](https://github.com/go-acme/lego/releases/tag/v4.26.0) - -### Added - -- **[dnsprovider]** Add DNS provider for KeyHelp -- **[dnsprovider]** Add DNS provider for Binary Lane -- **[dnsprovider]** Add DNS provider for Tencent EdgeOne -- **[dnsprovider]** azuredns: pipeline credential support -- **[dnsprovider]** oraclecloud: handle instance_principal authentication - -### Changed - -- **[dnsprovider]** oraclecloud: add env var aliases -- **[dnsprovider]** simply: update to API v2 -- **[lib,cli]** EAB: fallback to base64.URLEncoding - -### Fixed - -- **[dnsprovider]** selectelv2: add missing options - -## v4.25.2 - -- Release date: 2025-08-06 -- Tag: [v4.25.2](https://github.com/go-acme/lego/releases/tag/v4.25.2) - -### Changed - -- **[cli,log]** log when dynamic renew date not yet reached - -### Fixed - -- **[cli]** fix: remove wrong env var -- **[lib,cli]** fix: enforce HTTPS to the ACME server - -## v4.25.1 - -- Release date: 2025-07-21 -- Tag: [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) +## [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) (2025-07-21) ### Fixed - **[cli]** fix: wrong CLI flag type -## v4.25.0 - -- Release date: 2025-07-21 -- Tag: [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) +## [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) (2025-07-21) The binary size of this release is about ~50% smaller compared to previous releases. @@ -228,7 +19,7 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[lib,cli]** Add an option to disable common name in CSR ### Changed - +- - **[dnsprovider]** vinyldns: add an option to add quotes around the TXT record value - **[dnsprovider]** ionos: increase default propagation timeout @@ -236,10 +27,7 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[cli]** fix: enforce domain into renewal command -## v4.24.0 - -- Release date: 2025-07-07 -- Tag: [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) +## [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) (2025-07-07) ### Added @@ -262,19 +50,13 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[dnsprovider]** nicmanager: fix mode env var name and value - **[lib,cli]** Check order identifiers difference between client and server -## v4.23.1 - -- Release date: 2025-04-16 -- Tag: [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) +## [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) (2025-04-16) Due to an error related to Snapcraft, some artifacts of the v4.23.0 release have not been published. This release contains the same things as v4.23.0. -## v4.23.0 - -- Release date: 2025-04-16 -- Tag: [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) +## [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) (2025-04-16) ### Added @@ -305,19 +87,13 @@ This release contains the same things as v4.23.0. - **[dnsprovider]** pdns: fix TXT record cleanup for wildcard domains - **[dnsprovider]** allinkl: remove `ReturnInfo` -## v4.22.2 - -- Release date: 2025-02-17 -- Tag: [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) +## [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) (2025-02-17) ### Fixed - **[dnsprovider]** acme-dns: use new registred account -## v4.22.1 - -- Release date: 2025-02-17 -- Tag: [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) +## [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) (2025-02-17) ### Fixed @@ -325,10 +101,7 @@ This release contains the same things as v4.23.0. ### Added -## v4.22.0 - -- Release date: 2025-02-17 -- Tag: [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) +## [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) (2025-02-17) ### Added @@ -356,10 +129,7 @@ This release contains the same things as v4.23.0. - **[cli,log]** remove extra debug logs -## v4.21.0 - -- Release date: 2024-12-20 -- Tag: [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) +## [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) (2024-12-20) ### Added @@ -380,17 +150,11 @@ This release contains the same things as v4.23.0. - **[dnsprovider]** netcup: increase default propagation values - **[dnsprovider]** otc: use default transport -## v4.20.4 - -- Release date: 2024-11-21 -- Tag: [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) +## [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) (2024-11-21) Publish the Snap to the Snapcraft stable channel. -## v4.20.3 - -- Release date: 2024-11-21 -- Tag: [v4.20.3](https://github.com/go-acme/lego/releases/tag/v4.20.3) +## [v4.20.3](https://github.com/go-acme/lego/releases/tag/v4.20.3) (2024-11-21) ### Fixed @@ -398,10 +162,7 @@ Publish the Snap to the Snapcraft stable channel. - **[dnsprovider]** directadmin: fix timeout configuration - **[httpprovider]** fix: HTTP server IPv6 matching -## v4.20.2 - -- Release date: 2024-11-11 -- Tag: [v4.20.2](https://github.com/go-acme/lego/releases/tag/v4.20.2) +## [v4.20.2](https://github.com/go-acme/lego/releases/tag/v4.20.2) (2024-11-11) ### Added @@ -429,41 +190,28 @@ Publish the Snap to the Snapcraft stable channel. - **[dnsprovider]** volcengine: set API information within the default configuration - **[log]** Parse printf verbs in log line output -## v4.20.1 - -- Release date: 2024-11-11 +## v4.20.1 (2024-11-11) Cancelled due to CI failure. -## v4.20.0 - -- Release date: 2024-11-11 +## v4.20.0 (2024-11-11) Cancelled due to CI failure. -## v4.19.2 - -- Release date: 2024-10-06 -- Tag: [v4.19.2](https://github.com/go-acme/lego/releases/tag/v4.19.2) +## [v4.19.2](https://github.com/go-acme/lego/releases/tag/v4.19.2) (2024-10-06) ### Fixed - **[lib]** go1.22 compatibility -## v4.19.1 - -- Release date: 2024-10-06 -- Tag: [v4.19.1](https://github.com/go-acme/lego/releases/tag/v4.19.1) +## [v4.19.1](https://github.com/go-acme/lego/releases/tag/v4.19.1) (2024-10-06) ### Fixed - **[dnsprovider]** selectelv2: use baseURL from configuration - **[dnsprovider]** epik: add User-Agent -## v4.19.0 - -- Release date: 2024-10-03 -- Tag: [v4.19.0](https://github.com/go-acme/lego/releases/tag/v4.19.0) +## [v4.19.0](https://github.com/go-acme/lego/releases/tag/v4.19.0) (2024-10-03) ### Added @@ -485,10 +233,7 @@ Cancelled due to CI failure. - **[dnsprovider]** namesilo: restrict CleanUp - **[dnsprovider]** godaddy: fix cleanup -## v4.18.0 - -- Release date: 2024-08-30 -- Tag: [v4.18.0](https://github.com/go-acme/lego/releases/tag/v4.18.0) +## [v4.18.0](https://github.com/go-acme/lego/releases/tag/v4.18.0) (2024-08-30) ### Added @@ -510,19 +255,13 @@ Cancelled due to CI failure. - **[ari]** fix: avoid Int63n panic in ShouldRenewAt() -## v4.17.4 - -- Release date: 2024-06-12 -- Tag: [v4.17.4](https://github.com/go-acme/lego/releases/tag/v4.17.4) +## [v4.17.4](https://github.com/go-acme/lego/releases/tag/v4.17.4) (2024-06-12) ### Fixed - **[dnsprovider]** Update dependencies -## v4.17.3 - -- Release date: 2024-05-28 -- Tag: [v4.17.3](https://github.com/go-acme/lego/releases/tag/v4.17.3) +## [v4.17.3](https://github.com/go-acme/lego/releases/tag/v4.17.3) (2024-05-28) ### Added @@ -550,17 +289,13 @@ Cancelled due to CI failure. - **[dnsprovider]** pdns: reconstruct zone URLs to enable non-root folder API endpoints - **[dnsprovider]** alidns: fix link to API documentation -## v4.17.2 - -- Release date: 2024-05-28 +## v4.17.2 (2024-05-28) Canceled due to a release failure related to Snapcraft. The Snapcraft release are disabled for now. -## v4.17.1 - -- Release date: 2024-05-28 +## v4.17.1 (2024-05-28) Canceled due to a release failure related to oci-go-sdk. @@ -569,25 +304,17 @@ The module `github.com/oracle/oci-go-sdk/v65` uses `github.com/gofrs/flock` but Due to that we will remove the Solaris build. -## v4.17.0 - -- Release date: 2024-05-28 +## v4.17.0 (2024-05-28) Canceled due to a release failure related to Snapcraft. -## v4.16.1 - -- Release date: 2024-03-10 -- Tag: [v4.16.1](https://github.com/go-acme/lego/releases/tag/v4.16.1) +## [v4.16.1](https://github.com/go-acme/lego/releases/tag/v4.16.1) (2024-03-10) ### Fixed - **[cli,ari]** fix: don't generate ARI cert ID if ARI is not enable -## v4.16.0 - -- Release date: 2024-03-09 -- Tag: [v4.16.0](https://github.com/go-acme/lego/releases/tag/v4.16.0) +## [v4.16.0](https://github.com/go-acme/lego/releases/tag/v4.16.0) (2024-03-09) ### Added @@ -608,10 +335,7 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** easydns: fix zone detection - **[dnsprovider]** ns1: fix record creation -## v4.15.0 - -- Release date: 2024-01-28 -- Tag: [v4.15.0](https://github.com/go-acme/lego/releases/tag/v4.15.0) +## [v4.15.0](https://github.com/go-acme/lego/releases/tag/v4.15.0) (2024-01-28) ### Added @@ -649,10 +373,7 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** nifcloud: fix API requests - **[dnsprovider]** otc: sequential challenge -## v4.14.1 - -- Release date: 2023-09-20 -- Tag: [v4.14.1](https://github.com/go-acme/lego/releases/tag/v4.14.1) +## [v4.14.1](https://github.com/go-acme/lego/releases/tag/v4.14.1) (2023-09-20) ### Fixed @@ -660,16 +381,11 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** bunny: use NRDCG fork - **[dnsprovider]** ovh: update client to v1.4.2 -## v4.14.1 - -- Release date: 2023-09-19 +## v4.14.1 (2023-09-19) Cancelled due to CI failure. -## v4.14.0 - -- Release date: 2023-08-20 -- Tag: [v4.14.0](https://github.com/go-acme/lego/releases/tag/v4.14.0) +## [v4.14.0](https://github.com/go-acme/lego/releases/tag/v4.14.0) (2023-08-20) ### Added @@ -688,29 +404,20 @@ Cancelled due to CI failure. - **[dnsprovider]** pdns: fix notify - **[dnsprovider]** route53: avoid unexpected records deletion -## v4.13.3 - -- Release date: 2023-07-25 -- Tag: [v4.13.3](https://github.com/go-acme/lego/releases/tag/v4.13.3) +## [v4.13.3](https://github.com/go-acme/lego/releases/tag/v4.13.3) (2023-07-25) ### Fixed - **[dnsprovider]** azuredns: fix configuration from env vars - **[dnsprovider]** gcore: change API domain -## v4.13.2 - -- Release date: 2023-07-21 -- Tag: [v4.13.2](https://github.com/go-acme/lego/releases/tag/v4.13.2) +## [v4.13.2](https://github.com/go-acme/lego/releases/tag/v4.13.2) (2023-07-21) ### Fixed - **[dnsprovider]** servercow: fix regression -## v4.13.1 - -- Release date: 2023-07-20 -- Tag: [v4.13.1](https://github.com/go-acme/lego/releases/tag/v4.13.1) +## [v4.13.1](https://github.com/go-acme/lego/releases/tag/v4.13.1) (2023-07-20) ### Added @@ -731,35 +438,24 @@ Cancelled due to CI failure. - **[cli]** fix: list command - **[lib]** fix: ARI explanationURL -## v4.13.0 - -- Release date: 2023-07-20 +## v4.13.0 (2023-07-20) Cancelled due to a CI issue (no space left on device). -## v4.12.2 - -- Release date: 2023-06-19 -- Tag: [v4.12.2](https://github.com/go-acme/lego/releases/tag/v4.12.2) +## [v4.12.2](https://github.com/go-acme/lego/releases/tag/v4.12.2) (2023-06-19) ### Fixed - **[dnsprovider]** dnsmadeeasy: fix DeleteRecord - **[lib]** fix: read status code from response -## v4.12.1 - -- Release date: 2023-06-06 -- Tag: [v4.12.1](https://github.com/go-acme/lego/releases/tag/v4.12.1) +## [v4.12.1](https://github.com/go-acme/lego/releases/tag/v4.12.1) (2023-06-06) ### Fixed - **[dnsprovider]** pdns: fix record value -## v4.12.0 - -- Release date: 2023-05-28 -- Tag: [v4.12.0](https://github.com/go-acme/lego/releases/tag/v4.12.0) +## [v4.12.0](https://github.com/go-acme/lego/releases/tag/v4.12.0) (2023-05-28) ### Added @@ -777,10 +473,7 @@ Cancelled due to a CI issue (no space left on device). - **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used - **[cli]** fix: archive only domain-related files on revoke -## v4.11.0 - -- Release date: 2023-05-02 -- Tag: [v4.11.0](https://github.com/go-acme/lego/releases/tag/v4.11.0) +## [v4.11.0](https://github.com/go-acme/lego/releases/tag/v4.11.0) (2023-05-02) ### Added @@ -802,27 +495,18 @@ Cancelled due to a CI issue (no space left on device). - **[dnsprovider]** rimuhosting: fix API base URL -## v4.10.2 - -- Release date: 2023-02-26 -- Tag: [v4.10.2](https://github.com/go-acme/lego/releases/tag/v4.10.2) +## [v4.10.2](https://github.com/go-acme/lego/releases/tag/v4.10.2) (2023-02-26) Fix Docker image builds. -## v4.10.1 - -- Release date: 2023-02-25 -- Tag: [v4.10.1](https://github.com/go-acme/lego/releases/tag/v4.10.1) +## [v4.10.1](https://github.com/go-acme/lego/releases/tag/v4.10.1) (2023-02-25) ### Fixed - **[dnsprovider,cname]** acmedns: fix CNAME support - **[dnsprovider]** dynu: fix subdomain support -## v4.10.0 - -- Release date: 2023-02-10 -- Tag: [v4.10.0](https://github.com/go-acme/lego/releases/tag/v4.10.0) +## [v4.10.0](https://github.com/go-acme/lego/releases/tag/v4.10.0) (2023-02-10) ### Added @@ -848,10 +532,7 @@ Fix Docker image builds. - **[dnsprovider]** pdns: fix usage of notify only when zone kind is Master or Slave - **[dnsprovider]** return an error when extracting record name -## v4.9.1 - -- Release date: 2022-11-25 -- Tag: [v4.9.1](https://github.com/go-acme/lego/releases/tag/v4.9.1) +## [v4.9.1](https://github.com/go-acme/lego/releases/tag/v4.9.1) (2022-11-25) ### Changed @@ -866,10 +547,7 @@ Fix Docker image builds. - **[dnsprovider]** hurricane: fix CNAME support - **[lib,cname]** cname: stop trying to traverse cname if none have been found -## v4.9.0 - -- Release date: 2022-10-03 -- Tag: [v4.9.0](https://github.com/go-acme/lego/releases/tag/v4.9.0) +## [v4.9.0](https://github.com/go-acme/lego/releases/tag/v4.9.0) (2022-10-03) ### Added @@ -899,10 +577,7 @@ Fix Docker image builds. - **[dnsprovider]** njalla: fix record id unmarshal error - **[dnsprovider]** tencentcloud: fix subdomain error -## v4.8.0 - -- Release date: 2022-06-30 -- Tag: [v4.8.0](https://github.com/go-acme/lego/releases/tag/v4.8.0) +## [v4.8.0](https://github.com/go-acme/lego/releases/tag/v4.8.0) (2022-06-30) ### Added @@ -918,10 +593,7 @@ Fix Docker image builds. - **[dnsprovider]** hetzner: set min TTL to 60s - **[docs]** refactoring and cleanup -## v4.7.0 - -- Release date: 2022-05-27 -- Tag: [v4.7.0](https://github.com/go-acme/lego/releases/tag/v4.7.0) +## [v4.7.0](https://github.com/go-acme/lego/releases/tag/v4.7.0) (2022-05-27) ### Added @@ -943,10 +615,7 @@ Fix Docker image builds. - **[dnsprovider]** tencentcloud: fix InvalidParameter.DomainInvalid error when using DNS challenges - **[lib]** fix: panic in certcrypto.ParsePEMPrivateKey -## v4.6.0 - -- Release date: 2022-01-18 -- Tag: [v4.6.0](https://github.com/go-acme/lego/releases/tag/v4.6.0) +## [v4.6.0](https://github.com/go-acme/lego/releases/tag/v4.6.0) (2022-01-18) ### Added @@ -968,19 +637,13 @@ Fix Docker image builds. - **[dnsprovider]** mythicbeasts: fix token expiration - **[dnsprovider]** rackspace: change zone ID to string -## v4.5.3 - -- Release date: 2021-09-06 -- Tag: [v4.5.3](https://github.com/go-acme/lego/releases/tag/v4.5.3) +## [v4.5.3](https://github.com/go-acme/lego/releases/tag/v4.5.3) (2021-09-06) ### Fixed - **[lib,cli]** fix: missing preferred chain param for renew request -## v4.5.2 - -- Release date: 2021-09-01 -- Tag: [v4.5.2](https://github.com/go-acme/lego/releases/tag/v4.5.2) +## [v4.5.2](https://github.com/go-acme/lego/releases/tag/v4.5.2) (2021-09-01) ### Added @@ -1010,22 +673,15 @@ Fix Docker image builds. - **[lib]** lib: use permanent error instead of context cancellation - **[dnsprovider]** desec: bump to v0.6.0 -## v4.5.1 - -- Release date: 2021-10-01 +## v4.5.1 (2021-09-01) Cancelled due to a CI issue, replaced by v4.5.2. -## v4.5.0 - -- Release date: 2021-09-30 +## v4.5.0 (2021-09-30) Cancelled due to a CI issue, replaced by v4.5.2. -## v4.4.0 - -- Release date: 2021-06-08 -- Tag: [v4.4.0](https://github.com/go-acme/lego/releases/tag/v4.4.0) +## [v4.4.0](https://github.com/go-acme/lego/releases/tag/v4.4.0) (2021-06-08) ### Added @@ -1053,19 +709,13 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** nifcloud: Get zone info from dns01.FindZoneByFqdn - **[cli,lib]** csr: Support the type `NEW CERTIFICATE REQUEST` -## v4.3.1 - -- Release date: 2021-03-12 -- Tag: [v4.3.1](https://github.com/go-acme/lego/releases/tag/v4.3.1) +## [v4.3.1](https://github.com/go-acme/lego/releases/tag/v4.3.1) (2021-03-12) ### Fixed - **[dnsprovider]** exoscale: fix dependency version. -## v4.3.0 - -- Release date: 2021-03-10 -- Tag: [v4.3.0](https://github.com/go-acme/lego/releases/tag/v4.3.0) +## [v4.3.0](https://github.com/go-acme/lego/releases/tag/v4.3.0) (2021-03-10) ### Added @@ -1089,10 +739,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** Increase HTTP client timeouts - **[lib]** preferred chain only match root name -## v4.2.0 - -- Release date: 2021-01-24 -- Tag: [v4.2.0](https://github.com/go-acme/lego/releases/tag/v4.2.0) +## [v4.2.0](https://github.com/go-acme/lego/releases/tag/v4.2.0) (2021-01-24) ### Added @@ -1112,38 +759,26 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** pdns: URL request creation. - **[lib]** errors: Fix instance not being printed -## v4.1.3 - -- Release date: 2020-11-25 -- Tag: [v4.1.3](https://github.com/go-acme/lego/releases/tag/v4.1.3) +## [v4.1.3](https://github.com/go-acme/lego/releases/tag/v4.1.3) (2020-11-25) ### Fixed - **[dnsprovider]** azure: fix error handling. -## v4.1.2 - -- Release date: 2020-11-21 -- Tag: [v4.1.2](https://github.com/go-acme/lego/releases/tag/v4.1.2) +## [v4.1.2](https://github.com/go-acme/lego/releases/tag/v4.1.2) (2020-11-21) ### Fixed - **[lib]** fix: preferred chain support. -## v4.1.1 - -- Release date: 2020-11-19 -- Tag: [v4.1.1](https://github.com/go-acme/lego/releases/tag/v4.1.1) +## [v4.1.1](https://github.com/go-acme/lego/releases/tag/v4.1.1) (2020-11-19) ### Fixed - **[dnsprovider]** otc: select correct zone if multiple returned - **[dnsprovider]** azure: fix target must be a non-nil pointer -## v4.1.0 - -- Release date: 2020-11-06 -- Tag: [v4.1.0](https://github.com/go-acme/lego/releases/tag/v4.1.0) +## [v4.1.0](https://github.com/go-acme/lego/releases/tag/v4.1.0) (2020-11-06) ### Added @@ -1161,19 +796,13 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** acme/api: use postAsGet instead of post for AccountService.Get - **[lib]** fix: use http.Header.Set method instead of Add. -## v4.0.1 - -- Release date: 2020-09-03 -- Tag: [v4.0.1](https://github.com/go-acme/lego/releases/tag/v4.0.1) +## [v4.0.1](https://github.com/go-acme/lego/releases/tag/v4.0.1) (2020-09-03) ### Fixed - **[dnsprovider]** exoscale: change dependency version. -## v4.0.0 - -- Release date: 2020-09-02 -- Tag: [v4.0.0](https://github.com/go-acme/lego/releases/tag/v4.0.0) +## [v4.0.0](https://github.com/go-acme/lego/releases/tag/v4.0.0) (2020-09-02) ### Added @@ -1190,10 +819,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** Removes old Linode provider - **[lib]** Removes `AddPreCheck` function -## v3.9.0 - -- Release date: 2020-09-01 -- Tag: [v3.9.0](https://github.com/go-acme/lego/releases/tag/v3.9.0) +## [v3.9.0](https://github.com/go-acme/lego/releases/tag/v3.9.0) (2020-09-01) ### Added @@ -1210,10 +836,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** namesilo: fix cleanup. -## v3.8.0 - -- Release date: 2020-07-02 -- Tag: [v3.8.0](https://github.com/go-acme/lego/releases/tag/v3.8.0) +## [v3.8.0](https://github.com/go-acme/lego/releases/tag/v3.8.0) (2020-07-02) ### Added @@ -1237,10 +860,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** hetzner: fix record name. - **[lib]** Registrar.ResolveAccountByKey: Fix malformed request -## v3.7.0 - -- Release date: 2020-05-11 -- Tag: [v3.7.0](https://github.com/go-acme/lego/releases/tag/v3.7.0) +## [v3.7.0](https://github.com/go-acme/lego/releases/tag/v3.7.0) (2020-05-11) ### Added @@ -1263,10 +883,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[cli]** fix: renew path information. - **[cli]** Fix account storage location warning message -## v3.6.0 - -- Release date: 2020-04-24 -- Tag: [v3.6.0](https://github.com/go-acme/lego/releases/tag/v3.6.0) +## [v3.6.0](https://github.com/go-acme/lego/releases/tag/v3.6.0) (2020-04-24) ### Added @@ -1290,10 +907,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** ns1: fix missing domain in log - **[dnsprovider]** rimuhosting: use HTTP client from config. -## v3.5.0 - -- Release date: 2020-03-15 -- Tag: [v3.5.0](https://github.com/go-acme/lego/releases/tag/v3.5.0) +## [v3.5.0](https://github.com/go-acme/lego/releases/tag/v3.5.0) (2020-03-15) ### Added @@ -1316,10 +930,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** gcloud: fixes issues when used with GKE Workload Identity - **[dnsprovider]** oraclecloud: fix subdomain support -## v3.4.0 - -- Release date: 2020-02-25 -- Tag: [v3.4.0](https://github.com/go-acme/lego/releases/tag/v3.4.0) +## [v3.4.0](https://github.com/go-acme/lego/releases/tag/v3.4.0) (2020-02-25) ### Added @@ -1344,10 +955,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** crypto: Treat CommonName as optional - **[lib]** chore: update cenkalti/backoff to v4. -## v3.3.0 - -- Release date: 2020-01-08 -- Tag: [v3.3.0](https://github.com/go-acme/lego/releases/tag/v3.3.0) +## [v3.3.0](https://github.com/go-acme/lego/releases/tag/v3.3.0) (2020-01-08) ### Added @@ -1363,10 +971,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** Update dnspod, because of API breaking changes. -## v3.2.0 - -- Release date: 2019-11-10 -- Tag: [v3.2.0](https://github.com/go-acme/lego/releases/tag/v3.2.0) +## [v3.2.0](https://github.com/go-acme/lego/releases/tag/v3.2.0) (2019-11-10) ### Added @@ -1382,10 +987,7 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** use token as unique ID. -## v3.1.0 - -- Release date: 2019-10-07 -- Tag: [v3.1.0](https://github.com/go-acme/lego/releases/tag/v3.1.0) +## [v3.1.0](https://github.com/go-acme/lego/releases/tag/v3.1.0) (2019-10-07) ### Added @@ -1403,54 +1005,36 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** ovh: fix int overflow. - **[dnsprovider]** bindman: fix client version. -## v3.0.2 - -- Release date: 2019-08-15 -- Tag: [v3.0.2](https://github.com/go-acme/lego/releases/tag/v3.0.2) +## [v3.0.2](https://github.com/go-acme/lego/releases/tag/v3.0.2) (2019-08-15) ### Fixed - Invalid pseudo version (related to Cloudflare client). -## v3.0.1 - -- Release date: 2019-08-14 -- Tag: [v3.0.1](https://github.com/go-acme/lego/releases/tag/v3.0.1) +## [v3.0.1](https://github.com/go-acme/lego/releases/tag/v3.0.1) (2019-08-14) There was a problem when creating the tag v3.0.1, this tag has been invalidated. -## v3.0.0 - -- Release date: 2019-08-05 -- Tag: [v3.0.0](https://github.com/go-acme/lego/releases/tag/v3.0.0) +## [v3.0.0](https://github.com/go-acme/lego/releases/tag/v3.0.0) (2019-08-05) ### Changed - migrate to go module (new import github.com/go-acme/lego/v3/) - update DNS clients -## v2.7.2 - -- Release date: 2019-07-30 -- Tag: [v2.7.2](https://github.com/go-acme/lego/releases/tag/v2.7.2) +## [v2.7.2](https://github.com/go-acme/lego/releases/tag/v2.7.2) (2019-07-30) ### Fixed - **[dnsprovider]** vultr: quote TXT record -## v2.7.1 - -- Release date: 2019-07-22 -- Tag: [v2.7.1](https://github.com/go-acme/lego/releases/tag/v2.7.1) +## [v2.7.1](https://github.com/go-acme/lego/releases/tag/v2.7.1) (2019-07-22) ### Fixed - **[dnsprovider]** vultr: invalid record type. -## v2.7.0 - -- Release date: 2019-07-17 -- Tag: [v2.7.0](https://github.com/go-acme/lego/releases/tag/v2.7.0) +## [v2.7.0](https://github.com/go-acme/lego/releases/tag/v2.7.0) (2019-07-17) ### Added @@ -1467,10 +1051,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** otc: Prevent sending empty body. -## v2.6.0 - -- Release date: 2019-05-27 -- Tag: [v2.6.0](https://github.com/go-acme/lego/releases/tag/v2.6.0) +## [v2.6.0](https://github.com/go-acme/lego/releases/tag/v2.6.0) (2019-05-27) ### Added @@ -1492,10 +1073,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[cli]** fix: cli disable-cp option. - **[dnsprovider]** gcloud: fix zone visibility. -## v2.5.0 - -- Release date: 2019-04-17 -- Tag: [v2.5.0](https://github.com/go-acme/lego/releases/tag/v2.5.0) +## [v2.5.0](https://github.com/go-acme/lego/releases/tag/v2.5.0) (2019-04-17) ### Added @@ -1514,12 +1092,9 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** Disable authz when solve fail. - Add tzdata to the Docker image. -## v2.4.0 +## [v2.4.0](https://github.com/go-acme/lego/releases/tag/v2.4.0) (2019-03-25) -- Release date: 2019-03-25 -- Tag: [v2.4.0](https://github.com/go-acme/lego/releases/tag/v2.4.0) - -Migrate from xenolf/lego to go-acme/lego. +- Migrate from xenolf/lego to go-acme/lego. ### Added @@ -1532,10 +1107,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** hostingde: Use provided ZoneName instead of domain - **[dnsprovider]** pdns: fix wildcard with SANs -## v2.3.0 - -- Release date: 2019-03-11 -- Tag: [v2.3.0](https://github.com/go-acme/lego/releases/tag/v2.3.0) +## [v2.3.0](https://github.com/go-acme/lego/releases/tag/v2.3.0) (2019-03-11) ### Added @@ -1559,10 +1131,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** vscale: fix TXT records clean up - **[dnsprovider]** selectel: fix TXT records clean up -## v2.2.0 - -- Release date: 2019-02-08 -- Tag: [v2.2.0](https://github.com/go-acme/lego/releases/tag/v2.2.0) +## [v2.2.0](https://github.com/go-acme/lego/releases/tag/v2.2.0) (2019-02-08) ### Added @@ -1582,10 +1151,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** fastdns: Do not overwrite existing TXT records - Log wildcard domain correctly in validation -## v2.1.0 - -- Release date: 2019-01-24 -- Tag: [v2.1.0](https://github.com/go-acme/lego/releases/tag/v2.1.0) +## [v2.1.0](https://github.com/go-acme/lego/releases/tag/v2.1.0) (2019-01-24) ### Added @@ -1602,10 +1168,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** alicloud: fix pagination. - **[dnsprovider]** namecheap: fix panic. -## v2.0.0 - -- Release date: 2019-01-09 -- Tag: [v2.0.0](https://github.com/go-acme/lego/releases/tag/v2.0.0) +## [v2.0.0](https://github.com/go-acme/lego/releases/tag/v2.0.0) (2019-01-09) ### Added @@ -1657,10 +1220,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** Azure: Do not overwrite existing TXT records - **[dnsprovider]** fix: Cloudflare error. -## v1.2.0 - -- Release date: 2018-11-04 -- Tag: [v1.2.0](https://github.com/go-acme/lego/releases/tag/v1.2.0) +## [v1.2.0](https://github.com/go-acme/lego/releases/tag/v1.2.0) (2018-11-04) ### Added @@ -1681,10 +1241,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[lib]** Do not send a JWS body when POSTing challenges. - **[lib]** Support POST-as-GET. -## v1.1.0 - -- Release date: 2018-10-16 -- Tag: [v1.1.0](https://github.com/go-acme/lego/releases/tag/v1.1.0) +## [v1.1.0](https://github.com/go-acme/lego/releases/tag/v1.1.0) (2018-10-16) ### Added @@ -1720,10 +1277,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[lib]** Submit all dns records up front, then validate serially -## v1.0.0 - -- Release date: 2018-05-30 -- Tag: [v1.0.0](https://github.com/go-acme/lego/releases/tag/v1.0.0) +## [v1.0.0](https://github.com/go-acme/lego/releases/tag/v1.0.0) (2018-05-30) ### Changed @@ -1732,10 +1286,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** Modified Google Cloud provider `gcloud.NewDNSProviderServiceAccount` function to extract the project id directly from the service account file. - **[dnsprovider]** Made errors more verbose for the Cloudflare provider. -## v0.5.0 - -- Release date: 2018-05-29 -- Tag: [v0.5.0](https://github.com/go-acme/lego/releases/tag/v0.5.0) +## [v0.5.0](https://github.com/go-acme/lego/releases/tag/v0.5.0) (2018-05-29) ### Added @@ -1769,10 +1320,7 @@ Migrate from xenolf/lego to go-acme/lego. - **[dnsprovider]** Exoscale: update to latest egoscale version. - **[dnsprovider]** Route53: Use NewSessionWithOptions instead of deprecated New. -## 0.4.1 - -- Release date: 2017-09-26 -- Tag: [0.4.1](https://github.com/go-acme/lego/releases/tag/0.4.1) +## [0.4.1](https://github.com/go-acme/lego/releases/tag/0.4.1) (2017-09-26) ### Added @@ -1785,10 +1333,7 @@ Migrate from xenolf/lego to go-acme/lego. - lib: Fixed an authentication issue with the latest Azure SDK. -## 0.4.0 - -- Release date: 2017-07-13 -- Tag: [0.4.0](https://github.com/go-acme/lego/releases/tag/0.4.0) +## [0.4.0](https://github.com/go-acme/lego/releases/tag/0.4.0) (2017-07-13) ### Added @@ -1841,10 +1386,7 @@ Migrate from xenolf/lego to go-acme/lego. - lib: Fixed a condition where we could stall due to an early error condition. - lib: Fixed an issue where Authz object could end up in an active state after an error condition. -## 0.3.1 - -- Release date: 2016-04-19 -- Tag: [0.3.1](https://github.com/go-acme/lego/releases/tag/0.3.1) +## [0.3.1](https://github.com/go-acme/lego/releases/tag/0.3.1) (2016-04-19) ### Added @@ -1856,10 +1398,7 @@ Migrate from xenolf/lego to go-acme/lego. - lib: handleHTTPError should only try to JSON decode error messages with the right content type. - lib: The propagation checker for the DNS challenge would not retry on send errors. -## 0.3.0 - -- Release date: 2016-03-19 -- Tag: [0.3.0](https://github.com/go-acme/lego/releases/tag/0.3.0) +## [0.3.0](https://github.com/go-acme/lego/releases/tag/0.3.0) (2016-03-19) ### Added @@ -1894,10 +1433,7 @@ Migrate from xenolf/lego to go-acme/lego. - lib: Fixed an issue where status codes on ACME challenge responses could lead to no action being taken. - lib: Fixed a regression when calling the Renew function with a SAN certificate. -## 0.2.0 - -- Release date: 2016-01-09 -- Tag: [0.2.0](https://github.com/go-acme/lego/releases/tag/0.2.0) +## [0.2.0](https://github.com/go-acme/lego/releases/tag/0.2.0) (2016-01-09) ### Added @@ -1927,10 +1463,7 @@ Migrate from xenolf/lego to go-acme/lego. - CLI: Fix logic using the `--days` parameter for renew -## 0.1.1 - -- Release date: 2015-12-18 -- Tag: [0.1.1](https://github.com/go-acme/lego/releases/tag/0.1.1) +## [0.1.1](https://github.com/go-acme/lego/releases/tag/0.1.1) (2015-12-18) ### Added @@ -1950,9 +1483,6 @@ Migrate from xenolf/lego to go-acme/lego. - lib: Fix possible DOS on GetOCSPForCert -## 0.1.0 +## [0.1.0](https://github.com/go-acme/lego/releases/tag/0.1.0) (2015-12-03) -- Release date: 2015-12-03 -- Tag: [0.1.0](https://github.com/go-acme/lego/releases/tag/0.1.0) - -Initial release +- Initial release 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/Makefile b/Makefile index 8536dfc40..28cb33908 100644 --- a/Makefile +++ b/Makefile @@ -54,10 +54,10 @@ detach: .PHONY: docs-build docs-serve docs-themes docs-build: generate-dns - @make -C ./docs build + @make -C ./docs hugo-build docs-serve: generate-dns - @make -C ./docs serve + @make -C ./docs hugo docs-themes: @make -C ./docs hugo-themes diff --git a/README.md b/README.md index e90e94962..59b62a8de 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,20 @@ # 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) [![Docker Pulls](https://img.shields.io/docker/pulls/goacme/lego.svg)](https://hub.docker.com/r/goacme/lego/) -lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ - -Everybody thinks that the others will donate, but in the end, nobody does. - -So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). - ## Features - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - - Support [draft-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) + - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension +- Comes with about [150 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates @@ -56,72 +50,55 @@ 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). - - - - - + - - - - - + - + - - - - - + - + - + - - - - + - - + @@ -136,34 +113,24 @@ If your DNS provider is not supported, please open an [issue](https://github.com - - - - - + - - - - - - + + - - @@ -182,54 +149,44 @@ If your DNS provider is not supported, please open an [issue](https://github.com - - - - - - + - - - + - + - + - + - - - + - + - @@ -262,44 +219,39 @@ 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-inklAlwaysdata
Amazon Lightsail
Amazon Route 53Anexia CloudDNSANS SafeDNSArtFiles
ArvanCloud Aurora DNS
Autodns Axelname
Azion Azure (deprecated)
Azure DNS Baidu Cloud
Beget.comBinary 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 ClouddeSEC.io
deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdminDNS Made Easy
DNSExitDNS Made Easy dnsHome.de DNSimple DNSPod (deprecated)Dynu EasyDNS
EdgeCenter Efficient IP EpikEuroDNS
Excedo Exoscale External programF5 XC
F5 XC freemyip.comFusionLayer NameSurfer G-Core Gandi
Gandi Live DNS (v5)Gigahost.no Glesys Go Daddy
Google CloudGoogle DomainsGravityHetzner
Google DomainsHetzner Hosting.deHosting.nlHostinger Hosttech
HTTP requestINWX
IonosIonos Cloud IPv64ISPConfig 3
ISPConfig 3 - Dynamic DNS (DDNS) Moduleiwantmyname (Deprecated)JD Cloudiwantmyname Joker
Joohoi's ACME-DNSKeyHelpLeaseweb 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 NamesiloNearlyFreeSpeech.NETNeodigit
NearlyFreeSpeech.NET Netcup Netlify NicmanagerNIFCloud
NIFCloud Njalla Nodion NS1Octenium
Open Telekom Cloud Oracle CloudSpaceship
StackpathSyse Technitium Tencent Cloud DNS
Tencent EdgeOne Timeweb CloudTodayNIC/时代互联
TransIP
UKFast SafeDNS UltradnsUnited-Domains VariomediaVegaDNS
VegaDNS Vercel Versio.[nl|eu|uk] VinylDNSVirtualname
VK Cloud Volcano Engine/火山引擎 Vscale Vultr
webnames.cawebnames.ruWebnames Websupport WEDOS
West.cn/西部数码
Yandex 360 Yandex Cloud Yandex PDD
Zone.ee
ZoneEdit Zonomi
diff --git a/acme/api/account.go b/acme/api/account.go index 62e5ef9a6..85de84ef3 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -13,7 +13,6 @@ type AccountService service // New Creates a new account. func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) { var account acme.Account - resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account) location := getLocation(resp) @@ -30,9 +29,9 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) { // NewEAB Creates a new account with an External Account Binding. func (a *AccountService) NewEAB(accMsg acme.Account, kid, hmacEncoded string) (acme.ExtendedAccount, error) { - hmac, err := decodeEABHmac(hmacEncoded) + hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded) if err != nil { - return acme.ExtendedAccount{}, err + return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %w", err) } eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac) @@ -52,12 +51,10 @@ func (a *AccountService) Get(accountURL string) (acme.Account, error) { } var account acme.Account - _, err := a.core.postAsGet(accountURL, &account) if err != nil { return acme.Account{}, err } - return account, nil } @@ -68,7 +65,6 @@ func (a *AccountService) Update(accountURL string, req acme.Account) (acme.Accou } var account acme.Account - _, err := a.core.post(accountURL, req, &account) if err != nil { return acme.Account{}, err @@ -85,20 +81,5 @@ func (a *AccountService) Deactivate(accountURL string) error { req := acme.Account{Status: acme.StatusDeactivated} _, err := a.core.post(accountURL, req, nil) - return err } - -func decodeEABHmac(hmacEncoded string) ([]byte, error) { - hmac, errRaw := base64.RawURLEncoding.DecodeString(hmacEncoded) - if errRaw == nil { - return hmac, nil - } - - hmac, err := base64.URLEncoding.DecodeString(hmacEncoded) - if err == nil { - return hmac, nil - } - - return nil, fmt.Errorf("acme: could not decode hmac key: %w", errors.Join(errRaw, err)) -} diff --git a/acme/api/account_test.go b/acme/api/account_test.go deleted file mode 100644 index 16bd80741..000000000 --- a/acme/api/account_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package api - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_decodeEABHmac(t *testing.T) { - testCases := []struct { - desc string - hmac string - }{ - { - desc: "RawURLEncoding", - hmac: "BAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHx", - }, - { - desc: "URLEncoding", - hmac: "nKTo9Hu8fpCqWPXx-25LVbZrJWxcHISsr4qHrRR0j5U=", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - v, err := decodeEABHmac(test.hmac) - require.NoError(t, err) - - assert.NotEmpty(t, v) - }) - } -} diff --git a/acme/api/api.go b/acme/api/api.go index da1c94d1b..a4e6398ea 100644 --- a/acme/api/api.go +++ b/acme/api/api.go @@ -2,7 +2,6 @@ package api import ( "bytes" - "context" "crypto" "encoding/json" "errors" @@ -10,7 +9,7 @@ import ( "net/http" "time" - "github.com/cenkalti/backoff/v5" + "github.com/cenkalti/backoff/v4" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/nonces" "github.com/go-acme/lego/v4/acme/api/internal/secure" @@ -77,36 +76,39 @@ func (a *Core) postAsGet(uri string, response any) (*http.Response, error) { } func (a *Core) retrievablePost(uri string, content []byte, response any) (*http.Response, error) { - ctx := context.Background() - // during tests, allow to support ~90% of bad nonce with a minimum of attempts. bo := backoff.NewExponentialBackOff() bo.InitialInterval = 200 * time.Millisecond bo.MaxInterval = 5 * time.Second + bo.MaxElapsedTime = 20 * time.Second - operation := func() (*http.Response, error) { - resp, err := a.signedPost(uri, content, response) + var resp *http.Response + operation := func() error { + var err error + resp, err = a.signedPost(uri, content, response) if err != nil { // Retry if the nonce was invalidated var e *acme.NonceError if errors.As(err, &e) { - return resp, err + return err } - return resp, backoff.Permanent(err) + return backoff.Permanent(err) } - return resp, nil + return nil } notify := func(err error, duration time.Duration) { log.Infof("retry due to: %v", err) } - return backoff.Retry(ctx, operation, - backoff.WithBackOff(bo), - backoff.WithMaxElapsedTime(20*time.Second), - backoff.WithNotify(notify)) + err := backoff.RetryNotify(operation, bo, notify) + if err != nil { + return resp, err + } + + return resp, nil } func (a *Core) signedPost(uri string, content []byte, response any) (*http.Response, error) { @@ -155,7 +157,6 @@ func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) { if dir.NewAccountURL == "" { return dir, errors.New("directory missing new registration URL") } - if dir.NewOrderURL == "" { return dir, errors.New("directory missing new order URL") } diff --git a/acme/api/authorization.go b/acme/api/authorization.go index 4195bd1fe..a9972aa94 100644 --- a/acme/api/authorization.go +++ b/acme/api/authorization.go @@ -15,12 +15,10 @@ func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) } var authz acme.Authorization - _, err := c.core.postAsGet(authzURL, &authz) if err != nil { return acme.Authorization{}, err } - return authz, nil } @@ -31,8 +29,6 @@ func (c *AuthorizationService) Deactivate(authzURL string) error { } var disabledAuth acme.Authorization - _, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth) - return err } diff --git a/acme/api/certificate.go b/acme/api/certificate.go index b42296768..5f31968cf 100644 --- a/acme/api/certificate.go +++ b/acme/api/certificate.go @@ -2,12 +2,15 @@ package api import ( "bytes" + "crypto/x509" "encoding/pem" "errors" "io" "net/http" "github.com/go-acme/lego/v4/acme" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/log" ) // maxBodySize is the maximum size of body that we will read. @@ -74,22 +77,62 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific return nil, resp.Header, err } - cert := c.getCertificateChain(data, bundle) + cert := c.getCertificateChain(data, resp.Header, bundle, certURL) return cert, resp.Header, err } // getCertificateChain Returns the certificate and the issuer certificate. -func (c *CertificateService) getCertificateChain(cert []byte, bundle bool) *acme.RawCertificate { +func (c *CertificateService) getCertificateChain(cert []byte, headers http.Header, bundle bool, certURL string) *acme.RawCertificate { // Get issuerCert from bundled response from Let's Encrypt // See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962 _, issuer := pem.Decode(cert) + if issuer != nil { + // If bundle is false, we want to return a single certificate. + // To do this, we remove the issuer cert(s) from the issued cert. + if !bundle { + cert = bytes.TrimSuffix(cert, issuer) + } + return &acme.RawCertificate{Cert: cert, Issuer: issuer} + } - // If bundle is false, we want to return a single certificate. - // To do this, we remove the issuer cert(s) from the issued cert. - if !bundle { - cert = bytes.TrimSuffix(cert, issuer) + // The issuer certificate link may be supplied via an "up" link + // in the response headers of a new certificate. + // See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2 + up := getLink(headers, "up") + + issuer, err := c.getIssuerFromLink(up) + if err != nil { + // If we fail to acquire the issuer cert, return the issued certificate - do not fail. + log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err) + } else if len(issuer) > 0 { + // If bundle is true, we want to return a certificate bundle. + // To do this, we append the issuer cert to the issued cert. + if bundle { + cert = append(cert, issuer...) + } } return &acme.RawCertificate{Cert: cert, Issuer: issuer} } + +// getIssuerFromLink requests the issuer certificate. +func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) { + if up == "" { + return nil, nil + } + + log.Infof("acme: Requesting issuer cert from %s", up) + + cert, _, err := c.get(up, false) + if err != nil { + return nil, err + } + + _, err = x509.ParseCertificate(cert.Cert) + if err != nil { + return nil, err + } + + return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert.Cert)), nil +} diff --git a/acme/api/certificate_test.go b/acme/api/certificate_test.go index 7220ca1b9..9776cccc5 100644 --- a/acme/api/certificate_test.go +++ b/acme/api/certificate_test.go @@ -3,10 +3,11 @@ package api import ( "crypto/rand" "crypto/rsa" + "encoding/pem" + "net/http" "testing" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -73,34 +74,56 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { + p, _ := pem.Decode([]byte(issuerMock)) + _, err := w.Write(p.Bytes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", key) + core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) - cert, issuer, err := core.Certificates.Get(server.URL+"/certificate", true) + cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) require.NoError(t, err) assert.Equal(t, certResponseMock, string(cert), "Certificate") assert.Equal(t, issuerMock, string(issuer), "IssuerCertificate") } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", key) + core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) - cert, issuer, err := core.Certificates.Get(server.URL+"/certificate", true) + cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) require.NoError(t, err) assert.Equal(t, certResponseMock, string(cert), "Certificate") assert.Equal(t, issuerMock, string(issuer), "IssuerCertificate") diff --git a/acme/api/challenge.go b/acme/api/challenge.go index 2af55fc1a..875dede6e 100644 --- a/acme/api/challenge.go +++ b/acme/api/challenge.go @@ -17,7 +17,6 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) { // Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`. // We use an empty struct instance as the postJSON payload here to achieve this result. var chlng acme.ExtendedChallenge - resp, err := c.core.post(chlgURL, struct{}{}, &chlng) if err != nil { return acme.ExtendedChallenge{}, err @@ -25,7 +24,6 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) { chlng.AuthorizationURL = getLink(resp.Header, "up") chlng.RetryAfter = getRetryAfter(resp) - return chlng, nil } @@ -36,7 +34,6 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) { } var chlng acme.ExtendedChallenge - resp, err := c.core.postAsGet(chlgURL, &chlng) if err != nil { return acme.ExtendedChallenge{}, err @@ -44,6 +41,5 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) { chlng.AuthorizationURL = getLink(resp.Header, "up") chlng.RetryAfter = getRetryAfter(resp) - return chlng, nil } diff --git a/acme/api/identifier.go b/acme/api/identifier.go index 245ed8515..27337ccba 100644 --- a/acme/api/identifier.go +++ b/acme/api/identifier.go @@ -2,36 +2,11 @@ package api import ( "cmp" - "net" "slices" "github.com/go-acme/lego/v4/acme" ) -func createIdentifiers(domains []string) []acme.Identifier { - uniqIdentifiers := make(map[string]struct{}) - - var identifiers []acme.Identifier - - for _, domain := range domains { - if _, ok := uniqIdentifiers[domain]; ok { - continue - } - - ident := acme.Identifier{Value: domain, Type: "dns"} - - if net.ParseIP(domain) != nil { - ident.Type = "ip" - } - - identifiers = append(identifiers, ident) - - uniqIdentifiers[domain] = struct{}{} - } - - return identifiers -} - // compareIdentifiers compares 2 slices of [acme.Identifier]. func compareIdentifiers(a, b []acme.Identifier) int { // Clones slices to avoid modifying original slices. diff --git a/acme/api/internal/nonces/nonce_manager.go b/acme/api/internal/nonces/nonce_manager.go index 04a4ac620..d089cf07c 100644 --- a/acme/api/internal/nonces/nonce_manager.go +++ b/acme/api/internal/nonces/nonce_manager.go @@ -11,11 +11,10 @@ import ( // Manager Manages nonces. type Manager struct { - sync.Mutex - do *sender.Doer nonceURL string nonces []string + sync.Mutex } // NewManager Creates a new Manager. @@ -37,7 +36,6 @@ func (n *Manager) Pop() (string, bool) { nonce := n.nonces[len(n.nonces)-1] n.nonces = n.nonces[:len(n.nonces)-1] - return nonce, true } @@ -45,7 +43,6 @@ func (n *Manager) Pop() (string, bool) { func (n *Manager) Push(nonce string) { n.Lock() defer n.Unlock() - n.nonces = append(n.nonces, nonce) } @@ -54,7 +51,6 @@ func (n *Manager) Nonce() (string, error) { if nonce, ok := n.Pop(); ok { return nonce, nil } - return n.getNonce() } diff --git a/acme/api/internal/nonces/nonce_manager_test.go b/acme/api/internal/nonces/nonce_manager_test.go index 4490165df..a172a0b69 100644 --- a/acme/api/internal/nonces/nonce_manager_test.go +++ b/acme/api/internal/nonces/nonce_manager_test.go @@ -8,52 +8,45 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/platform/tester" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - manager := servermock.NewBuilder( - func(server *httptest.Server) (*Manager, error) { - doer := sender.NewDoer(server.Client(), "lego-test") - - return NewManager(doer, server.URL), nil - }). - Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - time.Sleep(250 * time.Millisecond) - - rw.Header().Set("Replay-Nonce", "12345") - rw.Header().Set("Retry-After", "0") - - servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) - })). - BuildHTTPS(t) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + time.Sleep(250 * time.Millisecond) + w.Header().Set("Replay-Nonce", "12345") + w.Header().Set("Retry-After", "0") + err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + })) + t.Cleanup(server.Close) + doer := sender.NewDoer(http.DefaultClient, "lego-test") + j := NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) - go func() { - _, errN := manager.Nonce() + _, errN := j.Nonce() if errN != nil { t.Log(errN) } - ch <- true }() go func() { - _, errN := manager.Nonce() + _, errN := j.Nonce() if errN != nil { t.Log(errN) } - ch <- true }() go func() { <-ch <-ch - resultCh <- true }() - select { case <-resultCh: case <-time.After(500 * time.Millisecond): diff --git a/acme/api/internal/secure/jws.go b/acme/api/internal/secure/jws.go index 8cd598663..7aa6c4c46 100644 --- a/acme/api/internal/secure/jws.go +++ b/acme/api/internal/secure/jws.go @@ -36,7 +36,6 @@ func (j *JWS) SetKid(kid string) { // SignContent Signs a content with the JWS. func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) { var alg jose.SignatureAlgorithm - switch k := j.privKey.(type) { case *rsa.PrivateKey: alg = jose.RS256 @@ -73,14 +72,12 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e if err != nil { return nil, fmt.Errorf("failed to sign content: %w", err) } - return signed, nil } // SignEABContent Signs an external account binding content with the JWS. func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) { jwk := jose.JSONWebKey{Key: j.privKey} - jwkJSON, err := jwk.Public().MarshalJSON() if err != nil { return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err) @@ -111,7 +108,6 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu // GetKeyAuthorization Gets the key authorization for a token. func (j *JWS) GetKeyAuthorization(token string) (string, error) { var publicKey crypto.PublicKey - switch k := j.privKey.(type) { case *ecdsa.PrivateKey: publicKey = k.Public() diff --git a/acme/api/internal/secure/jws_test.go b/acme/api/internal/secure/jws_test.go index d033cb0c4..2e625f24f 100644 --- a/acme/api/internal/secure/jws_test.go +++ b/acme/api/internal/secure/jws_test.go @@ -9,52 +9,45 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/nonces" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/platform/tester" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - manager := servermock.NewBuilder( - func(server *httptest.Server) (*nonces.Manager, error) { - doer := sender.NewDoer(server.Client(), "lego-test") - - return nonces.NewManager(doer, server.URL), nil - }). - Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - time.Sleep(250 * time.Millisecond) - - rw.Header().Set("Replay-Nonce", "12345") - rw.Header().Set("Retry-After", "0") - - servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) - })). - BuildHTTPS(t) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + time.Sleep(250 * time.Millisecond) + w.Header().Set("Replay-Nonce", "12345") + w.Header().Set("Retry-After", "0") + err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + })) + t.Cleanup(server.Close) + doer := sender.NewDoer(http.DefaultClient, "lego-test") + j := nonces.NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) - go func() { - _, errN := manager.Nonce() + _, errN := j.Nonce() if errN != nil { t.Log(errN) } - ch <- true }() go func() { - _, errN := manager.Nonce() + _, errN := j.Nonce() if errN != nil { t.Log(errN) } - ch <- true }() go func() { <-ch <-ch - resultCh <- true }() - select { case <-resultCh: case <-time.After(500 * time.Millisecond): diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index d8859edf4..2e1bbec8d 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -27,8 +27,6 @@ type Doer struct { // NewDoer Creates a new Doer. func NewDoer(client *http.Client, userAgent string) *Doer { - client.Transport = newHTTPSOnly(client) - return &Doer{ httpClient: client, userAgent: userAgent, @@ -120,69 +118,35 @@ 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 } -} - -type httpsOnly struct { - rt http.RoundTripper -} - -func newHTTPSOnly(client *http.Client) *httpsOnly { - if client.Transport == nil { - return &httpsOnly{rt: http.DefaultTransport} - } - - return &httpsOnly{rt: client.Transport} -} - -// RoundTrip ensure HTTPS is used. -// Each ACME function is accomplished by the client sending a sequence of HTTPS requests to the server [RFC2818], -// carrying JSON messages [RFC8259]. -// Use of HTTPS is REQUIRED. -// https://datatracker.ietf.org/doc/html/rfc8555#section-6.1 -func (r *httpsOnly) RoundTrip(req *http.Request) (*http.Response, error) { - if req.URL.Scheme != "https" { - return nil, fmt.Errorf("HTTPS is required: %s", req.URL) - } - - return r.rt.RoundTrip(req) + return nil } diff --git a/acme/api/internal/sender/sender_test.go b/acme/api/internal/sender/sender_test.go index 73701ab11..2fd43c878 100644 --- a/acme/api/internal/sender/sender_test.go +++ b/acme/api/internal/sender/sender_test.go @@ -1,28 +1,24 @@ 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" ) func TestDo_UserAgentOnAllHTTPMethod(t *testing.T) { var ua, method string - - server := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ua = r.Header.Get("User-Agent") method = r.Method })) t.Cleanup(server.Close) - doer := NewDoer(server.Client(), "") + doer := NewDoer(http.DefaultClient, "") testCases := []struct { method string @@ -64,87 +60,8 @@ func TestDo_CustomUserAgent(t *testing.T) { ua := doer.formatUserAgent() assert.Contains(t, ua, ourUserAgent) assert.Contains(t, ua, customUA) - if strings.HasSuffix(ua, " ") { t.Errorf("UA should not have trailing spaces; got '%s'", ua) } - assert.Len(t, strings.Split(ua, " "), 5) } - -func TestDo_failWithHTTP(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) - t.Cleanup(server.Close) - - sender := NewDoer(server.Client(), "test") - - _, err := sender.Post(server.URL, strings.NewReader("data"), "text/plain", nil) - require.ErrorContains(t, err, "HTTPS is required: http://") -} - -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..a4c287793 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.32.0" + ourUserAgent = "xenolf-acme/4.25.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/acme/api/order.go b/acme/api/order.go index fad6be2b8..96cd4d287 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "net" "slices" "time" @@ -17,7 +18,7 @@ type OrderOptions struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 Profile string // A string uniquely identifying a previously-issued certificate which this @@ -35,7 +36,18 @@ func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) { // NewWithOptions Creates a new order. func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acme.ExtendedOrder, error) { - orderReq := acme.Order{Identifiers: createIdentifiers(domains)} + var identifiers []acme.Identifier + for _, domain := range domains { + ident := acme.Identifier{Value: domain, Type: "dns"} + + if net.ParseIP(domain) != nil { + ident.Type = "ip" + } + + identifiers = append(identifiers, ident) + } + + orderReq := acme.Order{Identifiers: identifiers} if opts != nil { if !opts.NotAfter.IsZero() { @@ -56,7 +68,6 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm } var order acme.Order - resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order) if err != nil { are := &acme.AlreadyReplacedError{} @@ -75,23 +86,18 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm } } - // The server MUST return an error if it cannot fulfill the request as specified, - // and it MUST NOT issue a certificate with contents other than those requested. - // If the server requires the request to be modified in a certain way, - // it should indicate the required changes using an appropriate error type and description. - // https://www.rfc-editor.org/rfc/rfc8555#section-7.4 - // - // Some ACME servers don't return an error, - // and/or change the order identifiers in the response, - // so we need to ensure that the identifiers are the same as requested. - // Deduplication by the server is allowed. + // The elements of the "authorizations" and "identifiers" arrays are immutable once set. + // The server MUST NOT change the contents of either array after they are created. + // If a client observes a change in the contents of either array, + // then it SHOULD consider the order invalid. + // https://www.rfc-editor.org/rfc/rfc8555#section-7.1.3 if compareIdentifiers(orderReq.Identifiers, order.Identifiers) != 0 { // Sorts identifiers to avoid error message ambiguities about the order of the identifiers. slices.SortStableFunc(orderReq.Identifiers, compareIdentifier) slices.SortStableFunc(order.Identifiers, compareIdentifier) return acme.ExtendedOrder{}, - fmt.Errorf("order identifiers have been modified by the ACME server (RFC8555 §7.4): %+v != %+v", + fmt.Errorf("order identifiers have been by the ACME server (RFC8555 §7.1.3): %+v != %+v", orderReq.Identifiers, order.Identifiers) } @@ -108,7 +114,6 @@ func (o *OrderService) Get(orderURL string) (acme.ExtendedOrder, error) { } var order acme.Order - _, err := o.core.postAsGet(orderURL, &order) if err != nil { return acme.ExtendedOrder{}, err @@ -124,7 +129,6 @@ func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.ExtendedO } var order acme.Order - _, err := o.core.post(orderURL, csrMsg, &order) if err != nil { return acme.ExtendedOrder{}, err diff --git a/acme/api/order_test.go b/acme/api/order_test.go index f74f473d2..cf28e517b 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -11,51 +11,57 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOrderService_NewWithOptions(t *testing.T) { + mux, apiURL := tester.SetupFakeAPI(t) + // small value keeps test fast privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - server := tester.MockACMEServer(). - Route("POST /newOrder", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - body, err := readSignedBody(req, privateKey) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } + mux.HandleFunc("/newOrder", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } - order := acme.Order{} + body, err := readSignedBody(r, privateKey) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } - err = json.Unmarshal(body, &order) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } + order := acme.Order{} + err = json.Unmarshal(body, &order) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } - servermock.JSONEncode(acme.Order{ - Status: acme.StatusValid, - Expires: order.Expires, - Identifiers: order.Identifiers, - Profile: order.Profile, - NotBefore: order.NotBefore, - NotAfter: order.NotAfter, - Error: order.Error, - Authorizations: order.Authorizations, - Finalize: order.Finalize, - Certificate: order.Certificate, - Replaces: order.Replaces, - }).ServeHTTP(rw, req) - })). - BuildHTTPS(t) + err = tester.WriteJSONResponse(w, acme.Order{ + Status: acme.StatusValid, + Expires: order.Expires, + Identifiers: order.Identifiers, + Profile: order.Profile, + NotBefore: order.NotBefore, + NotAfter: order.NotAfter, + Error: order.Error, + Authorizations: order.Authorizations, + Finalize: order.Finalize, + Certificate: order.Certificate, + Replaces: order.Replaces, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) - core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -108,7 +114,6 @@ func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) } sigAlgs := []jose.SignatureAlgorithm{jose.RS256} - jws, err := jose.ParseSigned(string(reqBody), sigAlgs) if err != nil { return nil, err diff --git a/acme/api/service.go b/acme/api/service.go index 22ce05124..6f812ee03 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 { @@ -26,13 +23,11 @@ func getLinks(header http.Header, rel string) []string { linkExpr := regexp.MustCompile(`<(.+?)>(?:;[^;]+)*?;\s*rel="(.+?)"`) var links []string - for _, link := range header["Link"] { for _, m := range linkExpr.FindAllStringSubmatch(link, -1) { if len(m) != 3 { continue } - if m[2] == rel { links = append(links, m[1]) } @@ -59,29 +54,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/commons.go b/acme/commons.go index 0af623e4e..489c74938 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -77,14 +77,13 @@ type Meta struct { // profiles (optional, object): // A map of profile names to human-readable descriptions of those profiles. - // https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-3 + // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-3 Profiles map[string]string `json:"profiles"` } // ExtendedAccount an extended Account. type ExtendedAccount struct { Account - // Contains the value of the response header `Location` Location string `json:"-"` } @@ -157,7 +156,7 @@ type Order struct { // profile (string, optional): // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 + // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 Profile string `json:"profile,omitempty"` // notBefore (optional, string): @@ -221,11 +220,11 @@ type Authorization struct { // The timestamp after which the server will consider this authorization invalid, // encoded in the format specified in RFC 3339 [RFC3339]. // This field is REQUIRED for objects with "valid" in the "status" field. - Expires time.Time `json:"expires,omitzero"` + Expires time.Time `json:"expires,omitempty"` // identifier (required, object): // The identifier that the account is authorized to represent - Identifier Identifier `json:"identifier"` + Identifier Identifier `json:"identifier,omitempty"` // challenges (required, array of objects): // For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier. @@ -245,7 +244,6 @@ type Authorization struct { // ExtendedChallenge a extended Challenge. type ExtendedChallenge struct { Challenge - // Contains the value of the response header `Retry-After` RetryAfter string `json:"-"` // Contains the value of the response header `Link` rel="up" @@ -272,7 +270,7 @@ type Challenge struct { // The time at which the server validated this challenge, // encoded in the format specified in RFC 3339 [RFC3339]. // This field is REQUIRED if the "status" field is "valid". - Validated time.Time `json:"validated,omitzero"` + Validated time.Time `json:"validated,omitempty"` // error (optional, object): // Error that occurred while the server was validating the challenge, if any, diff --git a/acme/errors.go b/acme/errors.go index cd447d7b4..9a255468d 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -2,7 +2,6 @@ package acme import ( "fmt" - "strings" ) // Errors types. @@ -10,7 +9,6 @@ const ( errNS = "urn:ietf:params:acme:error:" BadNonceErr = errNS + "badNonce" AlreadyReplacedErr = errNS + "alreadyReplaced" - RateLimitedErr = errNS + "rateLimited" ) // ProblemDetails the problem details object. @@ -29,25 +27,21 @@ type ProblemDetails struct { } func (p *ProblemDetails) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "acme: error: %d", p.HTTPStatus) - + msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus) if p.Method != "" || p.URL != "" { - _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Method, p.URL) + msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL) } - - _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Type, p.Detail) + msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail) for _, sub := range p.SubProblems { - _, _ = fmt.Fprintf(msg, ", problem: %q :: %s", sub.Type, sub.Detail) + msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail) } if p.Instance != "" { - msg.WriteString(", url: " + p.Instance) + msg += ", url: " + p.Instance } - return msg.String() + return msg } // SubProblem a "subproblems". @@ -55,7 +49,7 @@ func (p *ProblemDetails) Error() string { type SubProblem struct { Type string `json:"type,omitempty"` Detail string `json:"detail,omitempty"` - Identifier Identifier `json:"identifier"` + Identifier Identifier `json:"identifier,omitempty"` } // NonceError represents the error which is returned @@ -64,30 +58,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/buildx.Dockerfile b/buildx.Dockerfile index 37f1dde94..92a86dd3d 100644 --- a/buildx.Dockerfile +++ b/buildx.Dockerfile @@ -1,12 +1,10 @@ # syntax=docker/dockerfile:1.4 FROM alpine:3 -ARG TARGETPLATFORM - RUN apk --no-cache --no-progress add git ca-certificates tzdata \ && rm -rf /var/cache/apk/* -COPY $TARGETPLATFORM/lego / +COPY lego / ENTRYPOINT ["/lego"] EXPOSE 80 diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index 800bb3f5b..d6f53c3a1 100644 --- a/certcrypto/crypto.go +++ b/certcrypto/crypto.go @@ -57,10 +57,8 @@ type DERCertificateBytes []byte // ParsePEMBundle parses a certificate bundle from top to bottom and returns // a slice of x509 certificates. This function will error if no certificates are found. func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) { - var ( - certificates []*x509.Certificate - certDERBlock *pem.Block - ) + var certificates []*x509.Certificate + var certDERBlock *pem.Block for { certDERBlock, bundle = pem.Decode(bundle) @@ -73,7 +71,6 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) { if err != nil { return nil, err } - certificates = append(certificates, cert) } } @@ -155,11 +152,8 @@ type CSROptions struct { } func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) { - var ( - dnsNames []string - ipAddresses []net.IP - ) - + var dnsNames []string + var ipAddresses []net.IP for _, altname := range opts.SAN { if ip := net.ParseIP(altname); ip != nil { ipAddresses = append(ipAddresses, ip) @@ -191,7 +185,6 @@ func PEMEncode(data any) []byte { func PEMBlock(data any) *pem.Block { var pemBlock *pem.Block - switch key := data.(type) { case *ecdsa.PrivateKey: keyBytes, _ := x509.MarshalECPrivateKey(key) @@ -242,15 +235,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 +251,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 { @@ -276,7 +265,6 @@ func ExtractDomains(cert *x509.Certificate) []string { if sanDomain == cert.Subject.CommonName { continue } - domains = append(domains, sanDomain) } @@ -328,7 +316,6 @@ func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pki func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return nil, err diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index f5609fdf4..0b0ce8f6f 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -13,13 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - testDomain1 = "lego.example" - testDomain2 = "a.lego.example" - testDomain3 = "b.lego.example" - testDomain4 = "c.lego.example" -) - func TestGeneratePrivateKey(t *testing.T) { key, err := GeneratePrivateKey(RSA2048) require.NoError(t, err, "Error generating private key") @@ -46,30 +39,30 @@ func TestGenerateCSR(t *testing.T) { desc: "without SAN (nil)", privateKey: privateKey, opts: CSROptions{ - Domain: testDomain1, + Domain: "lego.acme", MustStaple: true, }, - expected: expected{len: 382}, + expected: expected{len: 379}, }, { desc: "without SAN (empty)", privateKey: privateKey, opts: CSROptions{ - Domain: testDomain1, + Domain: "lego.acme", SAN: []string{}, MustStaple: true, }, - expected: expected{len: 382}, + expected: expected{len: 379}, }, { desc: "with SAN", privateKey: privateKey, opts: CSROptions{ - Domain: testDomain1, - SAN: []string{testDomain2, testDomain3, testDomain4}, + Domain: "lego.acme", + SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, MustStaple: true, }, - expected: expected{len: 442}, + expected: expected{len: 430}, }, { desc: "no domain", @@ -85,16 +78,16 @@ func TestGenerateCSR(t *testing.T) { privateKey: privateKey, opts: CSROptions{ Domain: "", - SAN: []string{testDomain2, testDomain3, testDomain4}, + SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, MustStaple: true, }, - expected: expected{len: 419}, + expected: expected{len: 409}, }, { desc: "private key nil", privateKey: nil, opts: CSROptions{ - Domain: testDomain1, + Domain: "fizz.buzz", MustStaple: true, }, expected: expected{error: true}, @@ -179,7 +172,6 @@ func TestParsePEMPrivateKey(t *testing.T) { // ignoring precomputed values. decoded, err := ParsePEMPrivateKey(pemPrivateKey) require.NoError(t, err) - decodedRsaPrivateKey := decoded.(*rsa.PrivateKey) require.True(t, decodedRsaPrivateKey.Equal(privateKey)) diff --git a/certificate/authorization.go b/certificate/authorization.go index 49f958776..c77bcbd5f 100644 --- a/certificate/authorization.go +++ b/certificate/authorization.go @@ -29,7 +29,6 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz var responses []acme.Authorization failures := newObtainError() - for range len(order.Authorizations) { select { case res := <-resc: @@ -63,7 +62,6 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force boo } log.Infof("Deactivating auth: %s", authzURL) - if c.core.Authorizations.Deactivate(authzURL) != nil { log.Infof("Unable to deactivate the authorization: %s", authzURL) } diff --git a/certificate/certificates.go b/certificate/certificates.go index 04904e794..e5830722d 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -77,7 +77,7 @@ type ObtainRequest struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 Profile string AlwaysDeactivateAuthorizations bool @@ -106,7 +106,7 @@ type ObtainForCSRRequest struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 Profile string AlwaysDeactivateAuthorizations bool @@ -198,7 +198,6 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := newObtainError() - cert, err := c.getForOrder(domains, order, request) if err != nil { for _, auth := range authz { @@ -296,7 +295,6 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, requ if privateKey == nil { var err error - privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType) if err != nil { return nil, err @@ -492,7 +490,6 @@ type RenewOptions struct { // If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle. // // For private key reuse the PrivateKey property of the passed in Resource should be non-nil. -// // Deprecated: use RenewWithOptions instead. func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) { return c.RenewWithOptions(certRes, &RenewOptions{ @@ -725,7 +722,6 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) { // https://www.rfc-editor.org/rfc/rfc5280.html#section-7 func sanitizeDomain(domains []string) []string { var sanitizedDomains []string - for _, domain := range domains { sanitizedDomain, err := idna.ToASCII(domain) if err != nil { @@ -734,6 +730,5 @@ func sanitizeDomain(domains []string) []string { sanitizedDomains = append(sanitizedDomains, sanitizedDomain) } } - return sanitizedDomains } diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index c0e35e795..3be2d606a 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -3,6 +3,7 @@ package certificate import ( "crypto/rand" "crypto/rsa" + "encoding/pem" "fmt" "net/http" "testing" @@ -11,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -175,14 +175,20 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -190,7 +196,7 @@ func Test_checkResponse(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: server.URL + "/certificate", + Certificate: apiURL + "/certificate", }, } certRes := &Resource{} @@ -209,14 +215,30 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { + p, _ := pem.Decode([]byte(issuerMock)) + _, err := w.Write(p.Bytes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -224,7 +246,7 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: server.URL + "/certificate", + Certificate: apiURL + "/certificate", }, } certRes := &Resource{} @@ -243,14 +265,20 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -258,7 +286,7 @@ func Test_checkResponse_no_bundle(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: server.URL + "/certificate", + Certificate: apiURL + "/certificate", }, } certRes := &Resource{} @@ -277,21 +305,30 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /certificate", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Add("Link", - fmt.Sprintf(`;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) + mux, apiURL := tester.SetupFakeAPI(t) - servermock.RawStringResponse(certResponseMock).ServeHTTP(rw, req) - })). - Route("/certificate/1", servermock.RawStringResponse(certResponseMock2)). - BuildHTTPS(t) + mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Link", fmt.Sprintf(`<%s/certificate/1>;title="foo";rel="alternate"`, apiURL)) + + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + mux.HandleFunc("/certificate/1", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(certResponseMock2)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -299,7 +336,7 @@ func Test_checkResponse_alternate(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: server.URL + "/certificate", + Certificate: apiURL + "/certificate", }, } certRes := &Resource{ @@ -321,25 +358,31 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - server := tester.MockACMEServer(). - Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + + mux.HandleFunc("/acme/cert/test-cert", func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(certResponseMock)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) - certRes, err := certifier.Get(server.URL+"/acme/cert/test-cert", true) + certRes, err := certifier.Get(apiURL+"/acme/cert/test-cert", true) require.NoError(t, err) assert.NotNil(t, certRes) assert.Equal(t, "acme.wtf", certRes.Domain) - assert.Equal(t, server.URL+"/acme/cert/test-cert", certRes.CertStableURL) - assert.Equal(t, server.URL+"/acme/cert/test-cert", certRes.CertURL) + assert.Equal(t, apiURL+"/acme/cert/test-cert", certRes.CertStableURL) + assert.Equal(t, apiURL+"/acme/cert/test-cert", certRes.CertURL) assert.Nil(t, certRes.CSR) assert.Nil(t, certRes.PrivateKey) assert.Equal(t, certResponseMock, string(certRes.Certificate), "Certificate") diff --git a/certificate/renewal.go b/certificate/renewal.go index 59d31cfb5..0a9059501 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. @@ -86,16 +85,15 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse defer resp.Body.Close() var info RenewalInfoResponse - err = json.NewDecoder(resp.Body).Decode(&info) if err != nil { return nil, err } 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..9f20e374e 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -43,24 +42,31 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - server := tester.MockACMEServer(). - Route("GET /renewalInfo/"+ariLeafCertID, - servermock.RawStringResponse(`{ + mux, apiURL := tester.SetupFakeAPI(t) + mux.HandleFunc("/renewalInfo/"+ariLeafCertID, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Retry-After", "21600") + w.WriteHeader(http.StatusOK) + _, wErr := w.Write([]byte(`{ "suggestedWindow": { "start": "2020-03-17T17:51:09Z", "end": "2020-03-17T18:21:09Z" }, - "explanationUrl": "https://aricapable.ca.example/docs/renewal-advice/" + "explanationUrl": "https://aricapable.ca/docs/renewal-advice/" } - }`). - WithHeader("Content-Type", "application/json"). - WithHeader("Retry-After", "21600")). - BuildHTTPS(t) + }`)) + require.NoError(t, wErr) + }) 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) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -70,46 +76,10 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NotNil(t, ri) assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339)) assert.Equal(t, "2020-03-17T18:21:09Z", ri.SuggestedWindow.End.Format(time.RFC3339)) - assert.Equal(t, "https://aricapable.ca.example/docs/renewal-advice/", ri.ExplanationURL) + assert.Equal(t, "https://aricapable.ca/docs/renewal-advice/", ri.ExplanationURL) 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) @@ -118,23 +88,24 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { require.NoError(t, err, "Could not generate test key") testCases := []struct { - desc string - timeout time.Duration - request RenewalInfoRequest - handler http.HandlerFunc + desc string + httpClient *http.Client + request RenewalInfoRequest + handler http.HandlerFunc }{ { - desc: "API timeout", - timeout: 500 * time.Millisecond, // HTTP client that times out after 500ms. - request: RenewalInfoRequest{leaf}, + desc: "API timeout", + httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms. + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that takes 2ms to respond. time.Sleep(2 * time.Millisecond) }, }, { - desc: "API error", - request: RenewalInfoRequest{leaf}, + desc: "API error", + httpClient: http.DefaultClient, + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that responds with error instead of renewal info. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) @@ -146,17 +117,10 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - server := tester.MockACMEServer(). - Route("GET /renewalInfo/"+ariLeafCertID, test.handler). - BuildHTTPS(t) + mux, apiURL := tester.SetupFakeAPI(t) + mux.HandleFunc("/renewalInfo/"+ariLeafCertID, test.handler) - client := server.Client() - - if test.timeout != 0 { - client.Timeout = test.timeout - } - - core, err := api.New(client, "lego-test", server.URL+"/dir", "", key) + core, err := api.New(test.httpClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) diff --git a/challenge/challenges.go b/challenge/challenges.go index f6d5cdb28..39bf3bee2 100644 --- a/challenge/challenges.go +++ b/challenge/challenges.go @@ -40,6 +40,5 @@ func GetTargetedDomain(authz acme.Authorization) string { if authz.Wildcard { return "*." + authz.Identifier.Value } - return authz.Identifier.Value } diff --git a/challenge/dns01/dns_challenge.go b/challenge/dns01/dns_challenge.go index 1d106d7b7..8594d2799 100644 --- a/challenge/dns01/dns_challenge.go +++ b/challenge/dns01/dns_challenge.go @@ -40,7 +40,6 @@ func CondOption(condition bool, opt ChallengeOption) ChallengeOption { return nil } } - return opt } @@ -119,7 +118,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { info := GetChallengeInfo(authz.Identifier.Value, keyAuth) var timeout, interval time.Duration - switch provider := c.provider.(type) { case challenge.ProviderTimeout: timeout, interval = provider.Timeout() @@ -136,7 +134,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if !stop || errP != nil { log.Infof("[%s] acme: Waiting for DNS record propagation.", domain) } - return stop, errP }) if err != nil { @@ -144,7 +141,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth - return c.validate(c.core, domain, chlng) } @@ -169,7 +165,6 @@ func (c *Challenge) Sequential() (bool, time.Duration) { if p, ok := c.provider.(sequential); ok { return ok, p.Sequential() } - return false, 0 } @@ -178,7 +173,6 @@ type sequential interface { } // GetRecord returns a DNS record which will fulfill the `dns-01` challenge. -// // Deprecated: use GetChallengeInfo instead. func GetRecord(domain, keyAuth string) (fqdn, value string) { info := GetChallengeInfo(domain, keyAuth) diff --git a/challenge/dns01/dns_challenge_manual.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 94% rename from providers/dns/manual/manual_test.go rename to challenge/dns01/dns_challenge_manual_test.go index 7badd4b8b..26a508d1c 100644 --- a/providers/dns/manual/manual_test.go +++ b/challenge/dns01/dns_challenge_manual_test.go @@ -1,4 +1,4 @@ -package manual +package dns01 import ( "io" @@ -10,7 +10,6 @@ import ( func TestDNSProviderManual(t *testing.T) { backupStdin := os.Stdin - defer func() { os.Stdin = backupStdin }() testCases := []struct { @@ -44,7 +43,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/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index 325f1656c..c09273c2a 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "crypto/rsa" "errors" + "net/http" "testing" "time" @@ -11,8 +12,6 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/dnsmock" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,12 +32,12 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -115,16 +114,12 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - useAsNameserver(t, dnsmock.NewServer(). - Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). - Build(t)) - - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -184,7 +179,6 @@ func TestChallenge_Solve(t *testing.T) { if test.preCheck != nil { options = append(options, WrapPreCheck(test.preCheck)) } - chlg := NewChallenge(core, test.validate, test.provider, options...) authz := acme.Authorization{ @@ -207,12 +201,12 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -287,55 +281,3 @@ func TestChallenge_CleanUp(t *testing.T) { }) } } - -func TestGetChallengeInfo(t *testing.T) { - useAsNameserver(t, dnsmock.NewServer(). - Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). - Build(t)) - - info := GetChallengeInfo("example.com", "123") - - expected := ChallengeInfo{ - FQDN: "_acme-challenge.example.com.", - EffectiveFQDN: "_acme-challenge.example.com.", - Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", - } - - assert.Equal(t, expected, info) -} - -func TestGetChallengeInfo_CNAME(t *testing.T) { - useAsNameserver(t, dnsmock.NewServer(). - Query("_acme-challenge.example.com. CNAME", dnsmock.CNAME("example.org.")). - Query("example.org. CNAME", dnsmock.Noop). - Build(t)) - - info := GetChallengeInfo("example.com", "123") - - expected := ChallengeInfo{ - FQDN: "_acme-challenge.example.com.", - EffectiveFQDN: "example.org.", - Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", - } - - assert.Equal(t, expected, info) -} - -func TestGetChallengeInfo_CNAME_disabled(t *testing.T) { - useAsNameserver(t, dnsmock.NewServer(). - // Never called when the env var works. - Query("_acme-challenge.example.com. CNAME", dnsmock.CNAME("example.org.")). - Build(t)) - - t.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true") - - info := GetChallengeInfo("example.com", "123") - - expected := ChallengeInfo{ - FQDN: "_acme-challenge.example.com.", - EffectiveFQDN: "_acme-challenge.example.com.", - Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", - } - - assert.Equal(t, expected, info) -} diff --git a/challenge/dns01/fixtures/resolv.conf.1 b/challenge/dns01/fixtures/resolv.conf.1 index bc2a3c1ac..3098f99b5 100644 --- a/challenge/dns01/fixtures/resolv.conf.1 +++ b/challenge/dns01/fixtures/resolv.conf.1 @@ -1,4 +1,4 @@ -domain example.com +domain company.com nameserver 10.200.3.249 nameserver 10.200.3.250:5353 nameserver 2001:4860:4860::8844 diff --git a/challenge/dns01/fqdn.go b/challenge/dns01/fqdn.go index 11ac3d0c2..3b94a491a 100644 --- a/challenge/dns01/fqdn.go +++ b/challenge/dns01/fqdn.go @@ -7,10 +7,12 @@ import ( ) // ToFqdn converts the name into a fqdn appending a trailing dot. -// -// Deprecated: Use [github.com/miekg/dns.Fqdn] directly. func ToFqdn(name string) string { - return dns.Fqdn(name) + n := len(name) + if n == 0 || name[n-1] == '.' { + return name + } + return name + "." } // UnFqdn converts the fqdn into a name removing the trailing dot. @@ -19,7 +21,6 @@ func UnFqdn(name string) string { if n != 0 && name[n-1] == '.' { return name[:n-1] } - return name } diff --git a/challenge/dns01/fqdn_test.go b/challenge/dns01/fqdn_test.go index 641e39081..7a6506d4e 100644 --- a/challenge/dns01/fqdn_test.go +++ b/challenge/dns01/fqdn_test.go @@ -7,6 +7,34 @@ import ( "github.com/stretchr/testify/assert" ) +func TestToFqdn(t *testing.T) { + testCases := []struct { + desc string + domain string + expected string + }{ + { + desc: "simple", + domain: "foo.example.com", + expected: "foo.example.com.", + }, + { + desc: "already FQDN", + domain: "foo.example.com.", + expected: "foo.example.com.", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + fqdn := ToFqdn(test.domain) + assert.Equal(t, test.expected, fqdn) + }) + } +} + func TestUnFqdn(t *testing.T) { testCases := []struct { desc string diff --git a/challenge/dns01/mock_test.go b/challenge/dns01/mock_test.go deleted file mode 100644 index 5dcad3013..000000000 --- a/challenge/dns01/mock_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package dns01 - -import ( - "context" - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/require" -) - -func fakeNS(name, ns string) *dns.NS { - return &dns.NS{ - Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 172800}, - Ns: ns, - } -} - -func fakeA(name, ip string) *dns.A { - return &dns.A{ - Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 10}, - A: net.ParseIP(ip), - } -} - -func fakeTXT(name, value string) *dns.TXT { - return &dns.TXT{ - Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 10}, - Txt: []string{value}, - } -} - -// mockResolver modifies the default DNS resolver to use a custom network address during the test execution. -// IMPORTANT: it modifying global variables. -func mockResolver(t *testing.T, addr net.Addr) { - t.Helper() - - _, port, err := net.SplitHostPort(addr.String()) - require.NoError(t, err) - - originalDefaultNameserverPort := defaultNameserverPort - - t.Cleanup(func() { - defaultNameserverPort = originalDefaultNameserverPort - }) - - defaultNameserverPort = port - - originalResolver := net.DefaultResolver - - t.Cleanup(func() { - net.DefaultResolver = originalResolver - }) - - net.DefaultResolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{Timeout: 1 * time.Second} - - return d.DialContext(ctx, network, addr.String()) - }, - } -} - -func useAsNameserver(t *testing.T, addr net.Addr) { - t.Helper() - - ClearFqdnCache() - t.Cleanup(func() { - ClearFqdnCache() - }) - - originalRecursiveNameservers := recursiveNameservers - - t.Cleanup(func() { - recursiveNameservers = originalRecursiveNameservers - }) - - recursiveNameservers = ParseNameservers([]string{addr.String()}) -} diff --git a/challenge/dns01/nameserver.go b/challenge/dns01/nameserver.go index 554eb7cc2..bb6dc0841 100644 --- a/challenge/dns01/nameserver.go +++ b/challenge/dns01/nameserver.go @@ -81,7 +81,6 @@ func getNameservers(path string, defaults []string) []string { func ParseNameservers(servers []string) []string { var resolvers []string - for _, resolver := range servers { // ensure all servers have a port number if _, _, err := net.SplitHostPort(resolver); err != nil { @@ -90,7 +89,6 @@ func ParseNameservers(servers []string) []string { resolvers = append(resolvers, resolver) } } - return resolvers } @@ -134,7 +132,6 @@ func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error if err != nil { return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err) } - return soa.primaryNs, nil } @@ -151,7 +148,6 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) { if err != nil { return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err) } - return soa.zone, nil } @@ -176,10 +172,8 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) } func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) { - var ( - err error - r *dns.Msg - ) + var err error + var r *dns.Msg for domain := range DomainsSeq(fqdn) { r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true) @@ -235,11 +229,9 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) ( return nil, &DNSError{Message: "empty list of nameservers"} } - var ( - r *dns.Msg - err error - errAll error - ) + var r *dns.Msg + var err error + var errAll error for _, ns := range nameservers { r, err = sendDNSQuery(m, ns) @@ -272,7 +264,6 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg { func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) { if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok { tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout} - r, _, err := tcp.Exchange(m, ns) if err != nil { return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err} diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index dd4d66dcb..d5bb0c0a1 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -5,237 +5,138 @@ import ( "sort" "testing" - "github.com/go-acme/lego/v4/platform/tester/dnsmock" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func Test_lookupNameserversOK(t *testing.T) { +func TestLookupNameserversOK(t *testing.T) { testCases := []struct { - desc string - fakeDNSServer *dnsmock.Builder - fqdn string - expected []string + fqdn string + nss []string }{ { - fqdn: "en.wikipedia.org.localhost.", - fakeDNSServer: dnsmock.NewServer(). - Query("en.wikipedia.org.localhost SOA", dnsmock.CNAME("dyna.wikimedia.org.localhost")). - Query("wikipedia.org.localhost SOA", dnsmock.SOA("")). - Query("wikipedia.org.localhost NS", - dnsmock.Answer( - fakeNS("wikipedia.org.localhost.", "ns0.wikimedia.org.localhost."), - fakeNS("wikipedia.org.localhost.", "ns1.wikimedia.org.localhost."), - fakeNS("wikipedia.org.localhost.", "ns2.wikimedia.org.localhost."), - ), - ), - expected: []string{"ns0.wikimedia.org.localhost.", "ns1.wikimedia.org.localhost.", "ns2.wikimedia.org.localhost."}, + fqdn: "en.wikipedia.org.", + nss: []string{"ns0.wikimedia.org.", "ns1.wikimedia.org.", "ns2.wikimedia.org."}, }, { - fqdn: "www.google.com.localhost.", - fakeDNSServer: dnsmock.NewServer(). - Query("www.google.com.localhost. SOA", dnsmock.Noop). - Query("google.com.localhost. SOA", dnsmock.SOA("")). - Query("google.com.localhost. NS", - dnsmock.Answer( - fakeNS("google.com.localhost.", "ns1.google.com.localhost."), - fakeNS("google.com.localhost.", "ns2.google.com.localhost."), - fakeNS("google.com.localhost.", "ns3.google.com.localhost."), - fakeNS("google.com.localhost.", "ns4.google.com.localhost."), - ), - ), - expected: []string{"ns1.google.com.localhost.", "ns2.google.com.localhost.", "ns3.google.com.localhost.", "ns4.google.com.localhost."}, + fqdn: "www.google.com.", + nss: []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, }, { - fqdn: "mail.proton.me.localhost.", - fakeDNSServer: dnsmock.NewServer(). - Query("mail.proton.me.localhost. SOA", dnsmock.Noop). - Query("proton.me.localhost. SOA", dnsmock.SOA("")). - Query("proton.me.localhost. NS", - dnsmock.Answer( - fakeNS("proton.me.localhost.", "ns1.proton.me.localhost."), - fakeNS("proton.me.localhost.", "ns2.proton.me.localhost."), - fakeNS("proton.me.localhost.", "ns3.proton.me.localhost."), - ), - ), - expected: []string{"ns1.proton.me.localhost.", "ns2.proton.me.localhost.", "ns3.proton.me.localhost."}, + fqdn: "mail.proton.me.", + nss: []string{"ns1.proton.me.", "ns2.proton.me.", "ns3.proton.me."}, }, } for _, test := range testCases { t.Run(test.fqdn, func(t *testing.T) { - useAsNameserver(t, test.fakeDNSServer.Build(t)) + t.Parallel() nss, err := lookupNameservers(test.fqdn) require.NoError(t, err) sort.Strings(nss) - sort.Strings(test.expected) + sort.Strings(test.nss) - assert.Equal(t, test.expected, nss) + assert.Equal(t, test.nss, nss) }) } } -func Test_lookupNameserversErr(t *testing.T) { +func TestLookupNameserversErr(t *testing.T) { testCases := []struct { - desc string - fqdn string - fakeDNSServer *dnsmock.Builder - error string + desc string + fqdn string + error string }{ { - desc: "NXDOMAIN", - fqdn: "example.invalid.", - fakeDNSServer: dnsmock.NewServer(). - Query(". SOA", dnsmock.Error(dns.RcodeNameError)), - error: "could not find zone: [fqdn=example.invalid.] could not find the start of authority for 'example.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]", - }, - { - desc: "NS error", - fqdn: "example.com.", - fakeDNSServer: dnsmock.NewServer(). - Query("example.com. SOA", dnsmock.SOA("")). - Query("example.com. NS", dnsmock.Error(dns.RcodeServerFailure)), - error: "[zone=example.com.] could not determine authoritative nameservers", - }, - { - desc: "empty NS", - fqdn: "example.com.", - fakeDNSServer: dnsmock.NewServer(). - Query("example.com. SOA", dnsmock.SOA("")). - Query("example.me NS", dnsmock.Noop), - error: "[zone=example.com.] could not determine authoritative nameservers", + desc: "invalid tld", + fqdn: "_null.n0n0.", + error: "could not find zone", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - useAsNameserver(t, test.fakeDNSServer.Build(t)) + t.Parallel() _, err := lookupNameservers(test.fqdn) require.Error(t, err) - assert.EqualError(t, err, test.error) + assert.Contains(t, err.Error(), test.error) }) } } -type lookupSoaByFqdnTestCase struct { +var findXByFqdnTestCases = []struct { desc string fqdn string zone string primaryNs string nameservers []string expectedError string -} - -func lookupSoaByFqdnTestCases(t *testing.T) []lookupSoaByFqdnTestCase { - t.Helper() - - return []lookupSoaByFqdnTestCase{ - { - desc: "domain is a CNAME", - fqdn: "mail.example.com.", - zone: "example.com.", - primaryNs: "ns1.example.com.", - nameservers: []string{ - dnsmock.NewServer(). - Query("mail.example.com. SOA", dnsmock.CNAME("example.com.")). - Query("example.com. SOA", dnsmock.SOA("")). - Build(t). - String(), - }, - }, - { - desc: "domain is a non-existent subdomain", - fqdn: "foo.example.com.", - zone: "example.com.", - primaryNs: "ns1.example.com.", - nameservers: []string{ - dnsmock.NewServer(). - Query("foo.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("example.com. SOA", dnsmock.SOA("")). - Build(t). - String(), - }, - }, - { - desc: "domain is a eTLD", - fqdn: "example.com.ac.", - zone: "ac.", - primaryNs: "ns1.nic.ac.", - nameservers: []string{ - dnsmock.NewServer(). - Query("example.com.ac. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("com.ac. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("ac. SOA", dnsmock.SOA("")). - Build(t). - String(), - }, - }, - { - desc: "domain is a cross-zone CNAME", - fqdn: "cross-zone-example.example.com.", - zone: "example.com.", - primaryNs: "ns1.example.com.", - nameservers: []string{ - dnsmock.NewServer(). - Query("cross-zone-example.example.com. SOA", dnsmock.CNAME("example.org.")). - Query("example.com. SOA", dnsmock.SOA("")). - Build(t). - String(), - }, - }, - { - desc: "NXDOMAIN", - fqdn: "test.lego.invalid.", - zone: "lego.invalid.", - nameservers: []string{ - dnsmock.NewServer(). - Query("test.lego.invalid. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("lego.invalid. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("invalid. SOA", dnsmock.Error(dns.RcodeNameError)). - Build(t). - String(), - }, - expectedError: `[fqdn=test.lego.invalid.] could not find the start of authority for 'test.lego.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]`, - }, - { - desc: "several non existent nameservers", - fqdn: "mail.example.com.", - zone: "example.com.", - primaryNs: "ns1.example.com.", - nameservers: []string{ - ":7053", - ":8053", - dnsmock.NewServer(). - Query("mail.example.com. SOA", dnsmock.CNAME("example.com.")). - Query("example.com. SOA", dnsmock.SOA("")). - Build(t). - String(), - }, - }, - { - desc: "only non-existent nameservers", - fqdn: "mail.example.com.", - zone: "example.com.", - nameservers: []string{":7053", ":8053", ":9053"}, - // use only the start of the message because the port changes with each call: 127.0.0.1:XXXXX->127.0.0.1:7053. - expectedError: "[fqdn=mail.example.com.] could not find the start of authority for 'mail.example.com.': DNS call error: read udp ", - }, - { - desc: "no nameservers", - fqdn: "test.example.com.", - zone: "example.com.", - nameservers: []string{}, - expectedError: "[fqdn=test.example.com.] could not find the start of authority for 'test.example.com.': empty list of nameservers", - }, - } +}{ + { + desc: "domain is a CNAME", + fqdn: "mail.google.com.", + zone: "google.com.", + primaryNs: "ns1.google.com.", + nameservers: recursiveNameservers, + }, + { + desc: "domain is a non-existent subdomain", + fqdn: "foo.google.com.", + zone: "google.com.", + primaryNs: "ns1.google.com.", + nameservers: recursiveNameservers, + }, + { + desc: "domain is a eTLD", + fqdn: "example.com.ac.", + zone: "ac.", + primaryNs: "a0.nic.ac.", + nameservers: recursiveNameservers, + }, + { + desc: "domain is a cross-zone CNAME", + fqdn: "cross-zone-example.assets.sh.", + zone: "assets.sh.", + primaryNs: "gina.ns.cloudflare.com.", + nameservers: recursiveNameservers, + }, + { + desc: "NXDOMAIN", + fqdn: "test.lego.zz.", + zone: "lego.zz.", + nameservers: []string{"8.8.8.8:53"}, + expectedError: "[fqdn=test.lego.zz.] could not find the start of authority for 'test.lego.zz.' [question='zz. IN SOA', code=NXDOMAIN]", + }, + { + desc: "several non existent nameservers", + fqdn: "mail.google.com.", + zone: "google.com.", + primaryNs: "ns1.google.com.", + nameservers: []string{":7053", ":8053", "8.8.8.8:53"}, + }, + { + desc: "only non-existent nameservers", + fqdn: "mail.google.com.", + zone: "google.com.", + nameservers: []string{":7053", ":8053", ":9053"}, + // use only the start of the message because the port changes with each call: 127.0.0.1:XXXXX->127.0.0.1:7053. + expectedError: "[fqdn=mail.google.com.] could not find the start of authority for 'mail.google.com.': DNS call error: read udp ", + }, + { + desc: "no nameservers", + fqdn: "test.ldez.com.", + zone: "ldez.com.", + nameservers: []string{}, + expectedError: "[fqdn=test.ldez.com.] could not find the start of authority for 'test.ldez.com.': empty list of nameservers", + }, } func TestFindZoneByFqdnCustom(t *testing.T) { - for _, test := range lookupSoaByFqdnTestCases(t) { + for _, test := range findXByFqdnTestCases { t.Run(test.desc, func(t *testing.T) { ClearFqdnCache() @@ -252,7 +153,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) { } func TestFindPrimaryNsByFqdnCustom(t *testing.T) { - for _, test := range lookupSoaByFqdnTestCases(t) { + for _, test := range findXByFqdnTestCases { t.Run(test.desc, func(t *testing.T) { ClearFqdnCache() @@ -268,7 +169,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) { } } -func Test_getNameservers_ResolveConfServers(t *testing.T) { +func TestResolveConfServers(t *testing.T) { testCases := []struct { fixture string expected []string diff --git a/challenge/dns01/precheck.go b/challenge/dns01/precheck.go index 45e17e3ac..706e8dbec 100644 --- a/challenge/dns01/precheck.go +++ b/challenge/dns01/precheck.go @@ -9,10 +9,6 @@ import ( "github.com/miekg/dns" ) -// defaultNameserverPort used by authoritative NS. -// This is for tests only. -var defaultNameserverPort = "53" - // PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready. type PreCheckFunc func(fqdn, value string) (bool, error) @@ -29,7 +25,6 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption { } // DisableCompletePropagationRequirement obsolete. -// // Deprecated: use DisableAuthoritativeNssPropagationRequirement instead. func DisableCompletePropagationRequirement() ChallengeOption { return DisableAuthoritativeNssPropagationRequirement() @@ -126,7 +121,7 @@ func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) { func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) { for _, ns := range nameservers { if addPort { - ns = net.JoinHostPort(ns, defaultNameserverPort) + ns = net.JoinHostPort(ns, "53") } r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false) @@ -141,11 +136,9 @@ func checkNameserversPropagation(fqdn, value string, nameservers []string, addPo var records []string var found bool - for _, rr := range r.Answer { if txt, ok := rr.(*dns.TXT); ok { record := strings.Join(txt.Txt, "") - records = append(records, record) if record == value { found = true diff --git a/challenge/dns01/precheck_test.go b/challenge/dns01/precheck_test.go index bda8c781e..1f3ecbf7e 100644 --- a/challenge/dns01/precheck_test.go +++ b/challenge/dns01/precheck_test.go @@ -3,73 +3,40 @@ package dns01 import ( "testing" - "github.com/go-acme/lego/v4/platform/tester/dnsmock" - "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func Test_preCheck_checkDNSPropagation(t *testing.T) { - mockResolver(t, - dnsmock.NewServer(). - Query("ns0.lego.localhost. A", - dnsmock.Answer(fakeA("ns0.lego.localhost.", "127.0.0.1"))). - Query("ns1.lego.localhost. A", - dnsmock.Answer(fakeA("ns1.lego.localhost.", "127.0.0.1"))). - Query("example.com. TXT", - dnsmock.Answer( - fakeTXT("example.com.", "one"), - fakeTXT("example.com.", "two"), - fakeTXT("example.com.", "three"), - fakeTXT("example.com.", "four"), - fakeTXT("example.com.", "five"), - ), - ). - Build(t), - ) - - useAsNameserver(t, - dnsmock.NewServer(). - Query("acme-staging.api.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("api.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("example.com. SOA", dnsmock.SOA("")). - Query("example.com. NS", - dnsmock.Answer( - fakeNS("example.com.", "ns0.lego.localhost."), - fakeNS("example.com.", "ns1.lego.localhost."), - ), - ). - Build(t), - ) - +func TestCheckDNSPropagation(t *testing.T) { testCases := []struct { - desc string - fqdn string - value string - expectedError string + desc string + fqdn string + value string + expectError bool }{ { desc: "success", - fqdn: "example.com.", - value: "four", + fqdn: "postman-echo.com.", + value: "postman-domain-verification=c85de626cb79d941310696e06558e2e790223802f3697dfbdcaf65510152d52c", }, { - desc: "no matching TXT record", - fqdn: "acme-staging.api.example.com.", - value: "fe01=", - expectedError: "did not return the expected TXT record [fqdn: acme-staging.api.example.com., value: fe01=]: one ,two ,three ,four ,five", + desc: "no TXT record", + fqdn: "acme-staging.api.letsencrypt.org.", + value: "fe01=", + expectError: true, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + t.Parallel() ClearFqdnCache() check := newPreCheck() ok, err := check.checkDNSPropagation(test.fqdn, test.value) - if test.expectedError != "" { - assert.ErrorContainsf(t, err, test.expectedError, "PreCheckDNS must fail for %s", test.fqdn) + if test.expectError { + assert.Errorf(t, err, "PreCheckDNS must fail for %s", test.fqdn) assert.False(t, ok, "PreCheckDNS must fail for %s", test.fqdn) } else { assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn) @@ -79,67 +46,69 @@ func Test_preCheck_checkDNSPropagation(t *testing.T) { } } -func Test_checkNameserversPropagation_authoritativeNss(t *testing.T) { +func TestCheckAuthoritativeNss(t *testing.T) { testCases := []struct { - desc string - fqdn, value string - fakeDNSServer *dnsmock.Builder - expectedError string + desc string + fqdn, value string + ns []string + expected bool }{ { - desc: "TXT RR w/ expected value", - // NS: asnums.routeviews.org. - fqdn: "8.8.8.8.asn.routeviews.org.", - value: "151698.8.8.024", - fakeDNSServer: dnsmock.NewServer(). - Query("8.8.8.8.asn.routeviews.org. TXT", - dnsmock.Answer( - fakeTXT("8.8.8.8.asn.routeviews.org.", "151698.8.8.024"), - ), - ), - }, - { - desc: "TXT RR w/ unexpected value", - // NS: asnums.routeviews.org. - fqdn: "8.8.8.8.asn.routeviews.org.", - value: "fe01=", - fakeDNSServer: dnsmock.NewServer(). - Query("8.8.8.8.asn.routeviews.org. TXT", - dnsmock.Answer( - fakeTXT("8.8.8.8.asn.routeviews.org.", "15169"), - fakeTXT("8.8.8.8.asn.routeviews.org.", "8.8.8.0"), - fakeTXT("8.8.8.8.asn.routeviews.org.", "24"), - ), - ), - expectedError: "did not return the expected TXT record [fqdn: 8.8.8.8.asn.routeviews.org., value: fe01=]: 15169 ,8.8.8.0 ,24", + desc: "TXT RR w/ expected value", + fqdn: "8.8.8.8.asn.routeviews.org.", + value: "151698.8.8.024", + ns: []string{"asnums.routeviews.org."}, + expected: true, }, { desc: "No TXT RR", - // NS: ns2.google.com. - fqdn: "ns1.google.com.", - value: "fe01=", - fakeDNSServer: dnsmock.NewServer(). - Query("ns1.google.com.", dnsmock.Noop), - expectedError: "did not return the expected TXT record [fqdn: ns1.google.com., value: fe01=]: ", + fqdn: "ns1.google.com.", + ns: []string{"ns2.google.com."}, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + t.Parallel() ClearFqdnCache() - addr := test.fakeDNSServer.Build(t) - - ok, err := checkNameserversPropagation(test.fqdn, test.value, []string{addr.String()}, false) - - if test.expectedError == "" { - require.NoError(t, err) - assert.True(t, ok) - } else { - require.Error(t, err) - require.ErrorContains(t, err, test.expectedError) - assert.False(t, ok) - } + ok, _ := checkNameserversPropagation(test.fqdn, test.value, test.ns, true) + assert.Equal(t, test.expected, ok, test.fqdn) + }) + } +} + +func TestCheckAuthoritativeNssErr(t *testing.T) { + testCases := []struct { + desc string + fqdn, value string + ns []string + error string + }{ + { + desc: "TXT RR /w unexpected value", + fqdn: "8.8.8.8.asn.routeviews.org.", + value: "fe01=", + ns: []string{"asnums.routeviews.org."}, + error: "did not return the expected TXT record", + }, + { + desc: "No TXT RR", + fqdn: "ns1.google.com.", + value: "fe01=", + ns: []string{"ns2.google.com."}, + error: "did not return the expected TXT record", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + ClearFqdnCache() + + _, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true) + require.Error(t, err) + assert.Contains(t, err.Error(), test.error) }) } } diff --git a/challenge/http01/domain_matcher.go b/challenge/http01/domain_matcher.go index 058d1a314..c31aeed6a 100644 --- a/challenge/http01/domain_matcher.go +++ b/challenge/http01/domain_matcher.go @@ -88,7 +88,6 @@ func (m *forwardedMatcher) matches(r *http.Request, domain string) bool { } host := fwds[0]["host"] - return matchDomain(host, domain) } @@ -100,7 +99,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { inquote := false pos := 0 - l := len(s) for i := 0; i < l; i++ { r := rune(s[i]) @@ -112,7 +110,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { pos = i inquote = false } - continue } @@ -121,7 +118,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { if key == "" { return nil, fmt.Errorf("unexpected quoted string as pos %d", i) } - inquote = true pos = i + 1 @@ -141,7 +137,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { val = s[pos:i] cur[key] = val } - elements = append(elements, cur) cur = make(map[string]string) key = "" @@ -164,14 +159,11 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { if pos < len(s) { val = s[pos:] } - cur[key] = val } - if len(cur) > 0 { elements = append(elements, cur) } - return elements, nil } @@ -186,7 +178,6 @@ func skipWS(s string, i int) int { for isWS(rune(s[i+1])) { i++ } - return i } diff --git a/challenge/http01/http_challenge.go b/challenge/http01/http_challenge.go index a042979c2..79dbfb4d0 100644 --- a/challenge/http01/http_challenge.go +++ b/challenge/http01/http_challenge.go @@ -74,7 +74,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if err != nil { return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err) } - defer func() { err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth) if err != nil { @@ -87,6 +86,5 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth - return c.validate(c.core, domain, chlng) } diff --git a/challenge/http01/http_challenge_server.go b/challenge/http01/http_challenge_server.go index ab962917e..009271cec 100644 --- a/challenge/http01/http_challenge_server.go +++ b/challenge/http01/http_challenge_server.go @@ -44,7 +44,6 @@ func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer // Present starts a web server and makes the token available at `ChallengePath(token)` for web requests. func (s *ProviderServer) Present(domain, token, keyAuth string) error { var err error - s.listener, err = net.Listen(s.network, s.GetAddress()) if err != nil { return fmt.Errorf("could not start HTTP server for challenge: %w", err) @@ -121,7 +120,6 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) { } log.Infof("[%s] Served key authentication", domain) - return } diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index 06c555e42..c29c49068 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) providerServer := NewProviderServer("", "23457") @@ -88,7 +88,6 @@ func TestChallenge(t *testing.T) { if err != nil { return err } - bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { @@ -101,7 +100,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -124,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -158,7 +157,6 @@ func TestChallengeUnix(t *testing.T) { if err != nil { return err } - bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { @@ -171,7 +169,7 @@ func TestChallengeUnix(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -190,12 +188,12 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) validate := func(_ *api.Core, _ string, _ acme.Challenge) error { return nil } @@ -226,7 +224,6 @@ func (h *testProxyHeader) update(r *http.Request) { if h == nil || len(h.values) == 0 { return } - if h.name == "Host" { r.Host = h.values[0] } else if h.name != "" { @@ -374,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { @@ -388,7 +385,6 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro if err != nil { return err } - header.update(req) extra.update(req) @@ -406,7 +402,6 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro if err != nil { return err } - bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { @@ -419,7 +414,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) diff --git a/challenge/resolver/errors.go b/challenge/resolver/errors.go index 65a6ccdb7..94ccbd76a 100644 --- a/challenge/resolver/errors.go +++ b/challenge/resolver/errors.go @@ -3,8 +3,6 @@ package resolver import ( "bytes" "fmt" - "maps" - "slices" "sort" ) @@ -18,16 +16,10 @@ func (e obtainError) Error() string { for domain := range e { domains = append(domains, domain) } - sort.Strings(domains) for _, domain := range domains { _, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain]) } - return buffer.String() } - -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..021facbb5 100644 --- a/challenge/resolver/prober.go +++ b/challenge/resolver/prober.go @@ -50,14 +50,11 @@ func NewProber(solverManager *SolverManager) *Prober { func (p *Prober) Solve(authorizations []acme.Authorization) error { failures := make(obtainError) - var ( - authSolvers []*selectedAuthSolver - authSolversSequential []*selectedAuthSolver - ) + var authSolvers []*selectedAuthSolver + var authSolversSequential []*selectedAuthSolver // Loop through the resources, basically through the domains. // First pass just selects a solver for each authz. - for _, authz := range authorizations { domain := challenge.GetTargetedDomain(authz) if authz.Status == acme.StatusValid { @@ -93,88 +90,47 @@ func (p *Prober) Solve(authorizations []acme.Authorization) error { if len(failures) > 0 { return failures } - return nil } 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 - cleanUp(authSolver.solver, authSolver.authz) - continue } - - uniq[authSolver.authz.Identifier.Value+chlg.Token] = struct{}{} } // Solve challenge err := authSolver.solver.Solve(authSolver.authz) if err != nil { failures[domain] = err - cleanUp(authSolver.solver, authSolver.authz) - 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 +142,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) } }() @@ -203,7 +149,6 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { // Finally solve all challenges for real for _, authSolver := range authSolvers { authz := authSolver.authz - domain := challenge.GetTargetedDomain(authz) if failures[domain] != nil { // already failed in previous loop @@ -220,7 +165,6 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { func cleanUp(solvr solver, authz acme.Authorization) { if solvr, ok := solvr.(cleanup); ok { domain := challenge.GetTargetedDomain(authz) - err := solvr.CleanUp(authz) if err != nil { log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err) diff --git a/challenge/resolver/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..4ee9b1b46 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", @@ -29,33 +26,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), - 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", + createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), }, }, { @@ -68,12 +41,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("example.com", acme.StatusValid), - createStubAuthorizationHTTP01("example.org", acme.StatusValid), - createStubAuthorizationHTTP01("example.net", acme.StatusValid), - }, - expectedCounters: map[challenge.Type]string{ - challenge.HTTP01: "PreSolve: 0, Solve: 0, CleanUp: 0", + createStubAuthorizationHTTP01("acme.wtf", acme.StatusValid), + createStubAuthorizationHTTP01("lego.wtf", acme.StatusValid), + createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusValid), }, }, { @@ -81,56 +51,50 @@ func TestProber_Solve(t *testing.T) { solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "example.com": errors.New("preSolve error example.com"), + "acme.wtf": errors.New("preSolve error acme.wtf"), }, solve: map[string]error{ - "example.com": errors.New("solve error example.com"), + "acme.wtf": errors.New("solve error acme.wtf"), }, cleanUp: map[string]error{ - "example.com": errors.New("clean error example.com"), + "acme.wtf": errors.New("clean error acme.wtf"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), - createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), - createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), + createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[example.com] preSolve error example.com +[acme.wtf] preSolve error acme.wtf `, - expectedCounters: map[challenge.Type]string{ - challenge.HTTP01: "PreSolve: 3, Solve: 2, CleanUp: 3", - }, }, { desc: "errors at different stages", solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "example.com": errors.New("preSolve error example.com"), + "acme.wtf": errors.New("preSolve error acme.wtf"), }, solve: map[string]error{ - "example.com": errors.New("solve error example.com"), - "example.org": errors.New("solve error example.org"), + "acme.wtf": errors.New("solve error acme.wtf"), + "lego.wtf": errors.New("solve error lego.wtf"), }, cleanUp: map[string]error{ - "example.net": errors.New("clean error example.net"), + "mydomain.wtf": errors.New("clean error mydomain.wtf"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), - createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), - createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), + createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[example.com] preSolve error example.com -[example.org] solve error example.org +[acme.wtf] preSolve error acme.wtf +[lego.wtf] solve error lego.wtf `, - 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..dcde3a3ed 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -1,13 +1,13 @@ package resolver import ( - "context" "errors" "fmt" "sort" + "strconv" "time" - "github.com/cenkalti/backoff/v5" + "github.com/cenkalti/backoff/v4" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/challenge" @@ -15,7 +15,6 @@ import ( "github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/log" - "github.com/go-acme/lego/v4/platform/wait" ) type byType []acme.Challenge @@ -70,7 +69,6 @@ func (c *SolverManager) chooseSolver(authz acme.Authorization) solver { log.Infof("[%s] acme: use %s solver", domain, chlg.Type) return solvr } - log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type) } @@ -93,20 +91,20 @@ 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 } - - ctx := context.Background() + initialInterval := time.Duration(ra) * time.Second bo := backoff.NewExponentialBackOff() - bo.InitialInterval = retryAfter - bo.MaxInterval = 10 * retryAfter + bo.InitialInterval = initialInterval + bo.MaxInterval = 10 * initialInterval + bo.MaxElapsedTime = 100 * initialInterval // After the path is sent, the ACME server will access our server. // Repeatedly check the server for an updated status on our request. @@ -129,9 +127,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return fmt.Errorf("the server didn't respond to our request (status=%s)", authz.Status) } - return wait.Retry(ctx, operation, - backoff.WithBackOff(bo), - backoff.WithMaxElapsedTime(100*retryAfter)) + return backoff.Retry(operation, bo) } func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) { @@ -161,7 +157,6 @@ func checkAuthorizationStatus(authz acme.Authorization) (bool, error) { return false, fmt.Errorf("invalid authorization: %w", chlg.Err()) } } - return false, errors.New("invalid authorization") default: return false, fmt.Errorf("the server returned an unexpected authorization status: %s", authz.Status) diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index 77149c73a..b1e198d3c 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,50 +32,66 @@ func TestByType(t *testing.T) { } func TestValidate(t *testing.T) { + mux, apiURL := tester.SetupFakeAPI(t) + var statuses []string privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - server := tester.MockACMEServer(). - Route("POST /chlg", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if err := validateNoBody(privateKey, req); err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } + mux.HandleFunc("/chlg", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } - rw.Header().Set("Link", - fmt.Sprintf(`; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) + if err := validateNoBody(privateKey, r); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } - st := statuses[0] - statuses = statuses[1:] + w.Header().Set("Link", "<"+apiURL+`/my-authz>; rel="up"`) - chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} + st := statuses[0] + statuses = statuses[1:] - servermock.JSONEncode(chlg).ServeHTTP(rw, req) - })). - Route("POST /my-authz", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - st := statuses[0] - statuses = statuses[1:] + chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} - authorization := acme.Authorization{ - Status: st, - Challenges: []acme.Challenge{}, - } + err := tester.WriteJSONResponse(w, chlg) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) - if st == acme.StatusInvalid { - chlg := acme.Challenge{ - Status: acme.StatusInvalid, - } - authorization.Challenges = append(authorization.Challenges, chlg) - } + mux.HandleFunc("/my-authz", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } - servermock.JSONEncode(authorization).ServeHTTP(rw, req) - })). - BuildHTTPS(t) + st := statuses[0] + statuses = statuses[1:] - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + authorization := acme.Authorization{ + Status: st, + Challenges: []acme.Challenge{}, + } + + if st == acme.StatusInvalid { + chlg := acme.Challenge{ + Status: acme.StatusInvalid, + } + authorization.Challenges = append(authorization.Challenges, chlg) + } + + err := tester.WriteJSONResponse(w, authorization) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -118,7 +133,7 @@ func TestValidate(t *testing.T) { t.Run(test.name, func(t *testing.T) { statuses = test.statuses - err := validate(core, "example.com", acme.Challenge{Type: "http-01", Token: "token", URL: server.URL + "/chlg"}) + err := validate(core, "example.com", acme.Challenge{Type: "http-01", Token: "token", URL: apiURL + "/chlg"}) if test.want == "" { require.NoError(t, err) } else { @@ -260,7 +275,6 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error { } sigAlgs := []jose.SignatureAlgorithm{jose.RS256} - jws, err := jose.ParseSigned(string(reqBody), sigAlgs) if err != nil { return err @@ -277,6 +291,5 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error { if bodyStr := string(body); bodyStr != "{}" && bodyStr != "" { return fmt.Errorf(`expected JWS POST body "{}" or "", got %q`, bodyStr) } - return nil } diff --git a/challenge/tlsalpn01/tls_alpn_challenge.go b/challenge/tlsalpn01/tls_alpn_challenge.go index d8e939106..559e1f905 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge.go +++ b/challenge/tlsalpn01/tls_alpn_challenge.go @@ -80,7 +80,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if err != nil { return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err) } - defer func() { err := c.provider.CleanUp(domain, chlng.Token, keyAuth) if err != nil { @@ -93,7 +92,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth - return c.validate(c.core, domain, chlng) } diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 59c2d61bc..9f65742f3 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "encoding/asn1" "net" + "net/http" "testing" "github.com/go-acme/lego/v4/acme" @@ -20,7 +21,7 @@ import ( ) func TestChallenge(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) domain := "localhost" port := "24457" @@ -42,7 +43,6 @@ func TestChallenge(t *testing.T) { assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions") idx := -1 - for i, ext := range remoteCert.Extensions { if idPeAcmeIdentifierV1.Equal(ext.Id) { idx = i @@ -69,7 +69,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -93,12 +93,12 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -123,7 +123,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) domain := "127.0.0.1" port := "24457" @@ -146,24 +146,18 @@ func TestChallengeIPaddress(t *testing.T) { assert.True(t, net.ParseIP("127.0.0.1").Equal(remoteCert.IPAddresses[0]), "challenge certificate IPAddress ") assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions") - var ( - foundAcmeIdentifier bool - extValue []byte - ) - + var foundAcmeIdentifier bool + var extValue []byte for _, ext := range remoteCert.Extensions { if idPeAcmeIdentifierV1.Equal(ext.Id) { assert.True(t, ext.Critical, "Expected the challenge certificate id-pe-acmeIdentifier extension to be marked as critical") - foundAcmeIdentifier = true extValue = ext.Value - break } } require.True(t, foundAcmeIdentifier, "Expected the challenge certificate to contain an extension with the id-pe-acmeIdentifier id,") - zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization)) value, err := asn1.Marshal(zBytes[:sha256.Size]) require.NoError(t, err, "Expected marshaling of the keyAuth to return no error") @@ -176,7 +170,7 @@ func TestChallengeIPaddress(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( diff --git a/cmd/accounts_storage.go b/cmd/accounts_storage.go index 01db2faf8..b3e4986dd 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -2,8 +2,10 @@ package cmd import ( "crypto" + "crypto/x509" "encoding/json" "encoding/pem" + "errors" "net/url" "os" "path/filepath" @@ -16,8 +18,6 @@ import ( "github.com/urfave/cli/v2" ) -const userIDPlaceholder = "noemail@example.com" - const ( baseAccountsRootFolderName = "accounts" baseKeysFolderName = "keys" @@ -34,7 +34,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 +42,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 +51,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 +59,6 @@ const ( // └── "path" option type AccountsStorage struct { userID string - email string rootPath string rootUserPath string keysPath string @@ -69,13 +68,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 +79,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), @@ -105,7 +98,6 @@ func (s *AccountsStorage) ExistsAccountFilePath() bool { } else if err != nil { log.Fatal(err) } - return true } @@ -121,10 +113,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 +125,13 @@ 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,13 @@ 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,19 +153,18 @@ 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) - return privateKey } @@ -193,7 +178,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) } } @@ -210,7 +195,6 @@ func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.Private defer certOut.Close() pemKey := certcrypto.PEMBlock(privateKey) - err = pem.Encode(certOut, pemKey) if err != nil { return nil, err @@ -225,12 +209,16 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) { return nil, err } - privateKey, err := certcrypto.ParsePEMPrivateKey(keyBytes) - if err != nil { - return nil, err + keyBlock, _ := pem.Decode(keyBytes) + + switch keyBlock.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(keyBlock.Bytes) } - return privateKey, nil + return nil, errors.New("unknown private key type") } func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) { @@ -248,6 +236,5 @@ func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*re if err != nil { return nil, err } - return reg, nil } diff --git a/cmd/certs_storage.go b/cmd/certs_storage.go index 25ef58075..f9bcdade8 100644 --- a/cmd/certs_storage.go +++ b/cmd/certs_storage.go @@ -2,6 +2,7 @@ package cmd import ( "bytes" + "crypto" "crypto/x509" "encoding/json" "encoding/pem" @@ -158,7 +159,6 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool { } else if err != nil { log.Fatal(err) } - return true } @@ -233,9 +233,27 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err) } - privateKey, err := certcrypto.ParsePEMPrivateKey(certRes.PrivateKey) - if err != nil { - return fmt.Errorf("unable to parse PrivateKey for domain %s: %w", domain, err) + keyPemBlock, _ := pem.Decode(certRes.PrivateKey) + if keyPemBlock == nil { + return fmt.Errorf("unable to parse PrivateKey for domain %s", domain) + } + + var privateKey crypto.Signer + var keyErr error + + switch keyPemBlock.Type { + case "RSA PRIVATE KEY": + privateKey, keyErr = x509.ParsePKCS1PrivateKey(keyPemBlock.Bytes) + if keyErr != nil { + return fmt.Errorf("unable to load RSA PrivateKey for domain %s: %w", domain, keyErr) + } + case "EC PRIVATE KEY": + privateKey, keyErr = x509.ParseECPrivateKey(keyPemBlock.Bytes) + if keyErr != nil { + return fmt.Errorf("unable to load EC PrivateKey for domain %s: %w", domain, keyErr) + } + default: + return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain) } encoder, err := getPFXEncoder(s.pfxFormat) @@ -284,7 +302,6 @@ func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, er } var certChain []*x509.Certificate - for chainCertPemBlock != nil { chainCert, err := x509.ParseCertificate(chainCertPemBlock.Bytes) if err != nil { @@ -300,7 +317,6 @@ func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, er func getPFXEncoder(pfxFormat string) (*pkcs12.Encoder, error) { var encoder *pkcs12.Encoder - switch pfxFormat { case "SHA256": encoder = pkcs12.Modern2023 @@ -321,6 +337,5 @@ func sanitizedDomain(domain string) string { if err != nil { log.Fatal(err) } - return safe } diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 53cd12c3c..bf7b232da 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, }, }, @@ -68,7 +67,6 @@ func listCertificates(ctx *cli.Context) error { if !names { fmt.Println("No certificates found.") } - return nil } @@ -101,11 +99,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() @@ -129,7 +122,6 @@ func listAccount(ctx *cli.Context) error { } fmt.Println("Found the following accounts:") - for _, filename := range matches { data, err := os.ReadFile(filename) if err != nil { @@ -137,7 +129,6 @@ func listAccount(ctx *cli.Context) error { } var account Account - err = json.Unmarshal(data, &account) if err != nil { return err @@ -156,12 +147,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..00bdf1b62 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -39,20 +39,16 @@ func createRenew() *cli.Command { Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both hasDomains := len(ctx.StringSlice(flgDomains)) > 0 - hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { log.Fatalf("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR) } - if !hasDomains && !hasCsr { log.Fatalf("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) } - if ctx.Bool(flgForceCertDomains) && hasCsr { log.Fatalf("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR) } - return nil }, Flags: []cli.Flag{ @@ -105,7 +101,7 @@ func createRenew() *cli.Command { }, &cli.StringFlag{ Name: flgProfile, - Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.", + Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, @@ -144,9 +140,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) { @@ -171,10 +165,8 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT cert := certificates[0] - var ( - ariRenewalTime *time.Time - replacesCertID string - ) + var ariRenewalTime *time.Time + var replacesCertID string var client *lego.Client @@ -216,7 +208,6 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) var privateKey crypto.PrivateKey - if ctx.Bool(flgReuseKey) { keyBytes, errR := certsStorage.ReadFile(domain, keyExt) if errR != nil { @@ -234,7 +225,6 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool(flgNoRandomSleep) { // https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472 const jitter = 8 * time.Minute - rnd := rand.New(rand.NewSource(time.Now().UnixNano())) sleepTime := time.Duration(rnd.Int63n(int64(jitter))) @@ -298,10 +288,8 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, cert := certificates[0] - var ( - ariRenewalTime *time.Time - replacesCertID string - ) + var ariRenewalTime *time.Time + var replacesCertID string var client *lego.Client @@ -369,7 +357,7 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bo } if dynamic { - return needRenewalDynamic(x509Cert, domain, time.Now()) + return needRenewalDynamic(x509Cert, time.Now()) } if days < 0 { @@ -387,7 +375,7 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bo return false } -func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time) bool { +func needRenewalDynamic(x509Cert *x509.Certificate, now time.Time) bool { lifetime := x509Cert.NotAfter.Sub(x509Cert.NotBefore) var divisor int64 = 3 @@ -397,14 +385,7 @@ func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time dueDate := x509Cert.NotAfter.Add(-1 * time.Duration(lifetime.Nanoseconds()/divisor)) - if dueDate.Before(now) { - return true - } - - log.Infof("[%s] The certificate expires at %s, the renewal can be performed in %s: no renewal.", - domain, x509Cert.NotAfter.Format(time.RFC3339), dueDate.Sub(now)) - - return false + return dueDate.Before(now) } // getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint. @@ -420,20 +401,16 @@ func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, log.Warnf("[%s] acme: %v", domain, err) return nil } - log.Warnf("[%s] acme: calling renewal info endpoint: %v", domain, err) - return nil } now := time.Now().UTC() - renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration(flgARIWaitToRenewDuration)) if renewalTime == nil { log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain) return nil } - log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is needed", domain) if renewalInfo.ExplanationURL != "" { diff --git a/cmd/cmd_renew_test.go b/cmd/cmd_renew_test.go index 2485c5240..405dda5fa 100644 --- a/cmd/cmd_renew_test.go +++ b/cmd/cmd_renew_test.go @@ -161,7 +161,7 @@ func Test_needRenewalDynamic(t *testing.T) { NotAfter: test.notAfter, } - ok := needRenewalDynamic(x509Cert, "example.com", test.now) + ok := needRenewalDynamic(x509Cert, test.now) test.expected(t, ok) }) diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 5924c4b66..8135e3546 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -35,16 +35,13 @@ func createRun() *cli.Command { Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both hasDomains := len(ctx.StringSlice(flgDomains)) > 0 - hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { log.Fatal("Please specify either --domains/-d or --csr/-c, but not both") } - if !hasDomains && !hasCsr { log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)") } - return nil }, Action: run, @@ -79,7 +76,7 @@ func createRun() *cli.Command { }, &cli.StringFlag{ Name: flgProfile, - Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.", + Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, @@ -104,9 +101,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 { @@ -158,12 +155,10 @@ func handleTOS(ctx *cli.Context, client *lego.Client) bool { } reader := bufio.NewReader(os.Stdin) - log.Printf("Please review the TOS at %s", client.GetToSURL()) for { fmt.Println("Do you accept the TOS? Y/n") - text, err := reader.ReadString('\n') if err != nil { log.Fatalf("Could not read from console: %v", err) @@ -224,7 +219,6 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso if ctx.IsSet(flgPrivateKey) { var err error - request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) if err != nil { return nil, fmt.Errorf("load private key: %w", err) @@ -253,7 +247,6 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso if ctx.IsSet(flgPrivateKey) { var err error - request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) if err != nil { return nil, fmt.Errorf("load private key: %w", err) diff --git a/cmd/flags.go b/cmd/flags.go index c7e8371b6..4ceccbdd9 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -90,8 +90,9 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "Email used for registration and recovery contact.", }, &cli.BoolFlag{ - Name: flgDisableCommonName, - Usage: "Disable the use of the common name in the CSR.", + Name: flgDisableCommonName, + EnvVars: []string{flgDisableCommonName}, + Usage: "Disable the use of the common name in the CSR.", }, &cli.StringFlag{ Name: flgCSR, @@ -258,6 +259,5 @@ func getTime(ctx *cli.Context, name string) time.Time { if value == nil { return time.Time{} } - return *value } diff --git a/cmd/hook.go b/cmd/hook.go index 7883108b6..c1de29c58 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -34,7 +34,6 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro parts := strings.Fields(hook) cmd := exec.CommandContext(ctxCmd, parts[0], parts[1:]...) - cmd.Env = append(os.Environ(), metaToEnv(meta)...) stdout, err := cmd.StdoutPipe() @@ -51,7 +50,6 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro go func() { <-ctxCmd.Done() - if ctxCmd.Err() != nil { _ = cmd.Process.Kill() _ = stdout.Close() diff --git a/cmd/lego/main.go b/cmd/lego/main.go index c301a51f1..61a3d532a 100644 --- a/cmd/lego/main.go +++ b/cmd/lego/main.go @@ -26,7 +26,6 @@ func main() { } var defaultPath string - cwd, err := os.Getwd() if err == nil { defaultPath = filepath.Join(cwd, ".lego") diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index cf9ad00ef..6b3086baa 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.32.0+dev-detach" +const defaultVersion = "v4.25.1+dev-release" var version = "" diff --git a/cmd/setup.go b/cmd/setup.go index 6d15adad3..fd8038464 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -1,18 +1,14 @@ package cmd import ( - "context" "crypto/x509" - "encoding/json" "encoding/pem" "fmt" - "io" "net/http" "os" "strings" "time" - "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/log" @@ -40,7 +36,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 @@ -74,7 +70,6 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy retryClient := retryablehttp.NewClient() retryClient.RetryMax = 5 retryClient.HTTPClient = config.HTTPClient - retryClient.CheckRetry = checkRetry retryClient.Logger = nil if _, v := os.LookupEnv("LEGO_DEBUG_ACME_HTTP_CLIENT"); v { @@ -114,10 +109,17 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType { } log.Fatalf("Unsupported KeyType: %s", 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)) } @@ -128,7 +130,6 @@ func createNonExistingFolder(path string) error { } else if err != nil { return err } - return nil } @@ -137,12 +138,10 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) { if err != nil { return nil, err } - raw := bytes // see if we can find a PEM-encoded CSR var p *pem.Block - rest := bytes for { // decode a PEM block @@ -164,49 +163,3 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) { // (if this assumption is wrong, parsing these bytes will fail) return x509.ParseCertificateRequest(raw) } - -func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { - rt, err := retryablehttp.ErrorPropagatedRetryPolicy(ctx, resp, err) - if err != nil { - return rt, err - } - - if resp == nil { - return rt, nil - } - - if resp.StatusCode/100 == 2 { - return rt, nil - } - - all, err := io.ReadAll(resp.Body) - if err == nil { - var errorDetails *acme.ProblemDetails - - err = json.Unmarshal(all, &errorDetails) - if err != nil { - return rt, fmt.Errorf("%s %s: %s", resp.Request.Method, resp.Request.URL.Redacted(), string(all)) - } - - switch errorDetails.Type { - case acme.BadNonceErr: - return false, &acme.NonceError{ - ProblemDetails: errorDetails, - } - - case acme.AlreadyReplacedErr: - if errorDetails.HTTPStatus == http.StatusConflict { - return false, &acme.AlreadyReplacedError{ - ProblemDetails: errorDetails, - } - } - - default: - log.Warnf("retry: %v", errorDetails) - - return rt, errorDetails - } - } - - return rt, nil -} diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 6968c7ba3..c923fa004 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -54,21 +54,18 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { if err != nil { log.Fatal(err) } - return ps case ctx.IsSet(flgHTTPMemcachedHost): ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost)) if err != nil { log.Fatal(err) } - return ps case ctx.IsSet(flgHTTPS3Bucket): ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket)) if err != nil { log.Fatal(err) } - return ps case ctx.IsSet(flgHTTPPort): iface := ctx.String(flgHTTPPort) @@ -85,14 +82,12 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } - return srv case ctx.Bool(flgHTTP): srv := http01.NewProviderServer("", "") if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } - return srv default: log.Fatal("Invalid HTTP challenge options.") diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index f73f3920b..b1772e863 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -12,14 +12,11 @@ import ( func allDNSCodes() string { providers := []string{ + "manual", "acme-dns", "active24", "alidns", - "aliesa", "allinkl", - "alwaysdata", - "anexia", - "artfiles", "arvancloud", "auroradns", "autodns", @@ -28,11 +25,8 @@ func allDNSCodes() string { "azure", "azuredns", "baiducloud", - "beget", - "binarylane", "bindman", "bluecat", - "bluecatv2", "bookmyname", "brandit", "bunny", @@ -43,20 +37,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 +59,9 @@ func allDNSCodes() string { "dyndnsfree", "dynu", "easydns", - "edgecenter", "edgedns", - "edgeone", "efficientip", "epik", - "eurodns", - "excedo", "exec", "exoscale", "f5xc", @@ -84,15 +70,11 @@ func allDNSCodes() string { "gandiv5", "gcloud", "gcore", - "gigahostno", "glesys", "godaddy", "googledomains", - "gravity", "hetzner", "hostingde", - "hostinger", - "hostingnl", "hosttech", "httpnet", "httpreq", @@ -107,15 +89,9 @@ func allDNSCodes() string { "internetbs", "inwx", "ionos", - "ionoscloud", "ipv64", - "ispconfig", - "ispconfigddns", "iwantmyname", - "jdcloud", "joker", - "keyhelp", - "leaseweb", "liara", "lightsail", "limacity", @@ -125,7 +101,6 @@ func allDNSCodes() string { "luadns", "mailinabox", "manageengine", - "manual", "metaname", "metaregistrar", "mijnhost", @@ -136,9 +111,7 @@ func allDNSCodes() string { "namecheap", "namedotcom", "namesilo", - "namesurfer", "nearlyfreespeech", - "neodigit", "netcup", "netlify", "nicmanager", @@ -147,7 +120,6 @@ func allDNSCodes() string { "njalla", "nodion", "ns1", - "octenium", "oraclecloud", "otc", "ovh", @@ -174,26 +146,21 @@ func allDNSCodes() string { "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", @@ -269,38 +236,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,69 +263,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.`) - ew.writeln(`Code: 'anexia'`) - ew.writeln(`Since: 'v4.28.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "ANEXIA_TOKEN": API token for Anexia Engine`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ANEXIA_API_URL": API endpoint URL (default: https://engine.anexia-it.com)`) - ew.writeln(` - "ANEXIA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "ANEXIA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "ANEXIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) - ew.writeln(` - "ANEXIA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/anexia`) - - case "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,52 +442,11 @@ 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`) - case "beget": - // generated from: providers/dns/beget/beget.toml - ew.writeln(`Configuration for Beget.com.`) - ew.writeln(`Code: 'beget'`) - ew.writeln(`Since: 'v4.27.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "BEGET_PASSWORD": API password`) - ew.writeln(` - "BEGET_USERNAME": API username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BEGET_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "BEGET_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) - ew.writeln(` - "BEGET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) - ew.writeln(` - "BEGET_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/beget`) - - case "binarylane": - // generated from: providers/dns/binarylane/binarylane.toml - ew.writeln(`Configuration for Binary Lane.`) - ew.writeln(`Code: 'binarylane'`) - ew.writeln(`Since: 'v4.26.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "BINARYLANE_API_TOKEN": API token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BINARYLANE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "BINARYLANE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "BINARYLANE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "BINARYLANE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/binarylane`) - case "bindman": // generated from: providers/dns/bindman/bindman.toml ew.writeln(`Configuration for Bindman.`) @@ -653,31 +491,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.`) @@ -732,7 +545,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BUNNY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "BUNNY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "BUNNY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "BUNNY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) @@ -896,27 +708,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 +820,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 +935,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 +1181,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.`) @@ -1496,30 +1206,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgedns`) - case "edgeone": - // generated from: providers/dns/edgeone/edgeone.toml - ew.writeln(`Configuration for Tencent EdgeOne.`) - ew.writeln(`Code: 'edgeone'`) - ew.writeln(`Since: 'v4.26.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "EDGEONE_SECRET_ID": Access key ID`) - ew.writeln(` - "EDGEONE_SECRET_KEY": Access Key secret`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "EDGEONE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "EDGEONE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) - ew.writeln(` - "EDGEONE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) - ew.writeln(` - "EDGEONE_REGION": Region`) - ew.writeln(` - "EDGEONE_SESSION_TOKEN": Access Key token`) - ew.writeln(` - "EDGEONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) - ew.writeln(` - "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`) - case "efficientip": // generated from: providers/dns/efficientip/efficientip.toml ew.writeln(`Configuration for Efficient IP.`) @@ -1564,48 +1250,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 +1299,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 +1411,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 +1472,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.`) @@ -1881,14 +1480,14 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Credentials:`) - ew.writeln(` - "HETZNER_API_TOKEN": API token`) + ew.writeln(` - "HETZNER_API_KEY": API key`) ew.writeln() ew.writeln(`Additional Configuration:`) ew.writeln(` - "HETZNER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "HETZNER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hetzner`) @@ -1914,46 +1513,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostingde`) - case "hostinger": - // generated from: providers/dns/hostinger/hostinger.toml - ew.writeln(`Configuration for Hostinger.`) - ew.writeln(`Code: 'hostinger'`) - ew.writeln(`Since: 'v4.27.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "HOSTINGER_API_TOKEN": API Token`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HOSTINGER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "HOSTINGER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "HOSTINGER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "HOSTINGER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostinger`) - - case "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.`) @@ -2088,7 +1647,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "SOFTLAYER_API_KEY": Classic Infrastructure API key`) - ew.writeln(` - "SOFTLAYER_USERNAME": Username (IBM Cloud is {accountID}_{emailAddress})`) + ew.writeln(` - "SOFTLAYER_USERNAME": Username (IBM Cloud is _)`) ew.writeln() ew.writeln(`Additional Configuration:`) @@ -2253,26 +1812,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,53 +1831,9 @@ 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).`) + ew.writeln(`Configuration for iwantmyname.`) ew.writeln(`Code: 'iwantmyname'`) ew.writeln(`Since: 'v4.7.0'`) ew.writeln() @@ -2357,28 +1852,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.`) @@ -2403,47 +1876,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/joker`) - case "keyhelp": - // generated from: providers/dns/keyhelp/keyhelp.toml - ew.writeln(`Configuration for KeyHelp.`) - ew.writeln(`Code: 'keyhelp'`) - ew.writeln(`Since: 'v4.26.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "KEYHELP_API_KEY": API key`) - ew.writeln(` - "KEYHELP_BASE_URL": Server URL`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "KEYHELP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "KEYHELP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "KEYHELP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "KEYHELP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/keyhelp`) - - case "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 +1891,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() @@ -2607,7 +2038,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MAILINABOX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) @@ -2634,16 +2064,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 +2272,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 +2294,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.`) @@ -3088,26 +2464,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ns1`) - case "octenium": - // generated from: providers/dns/octenium/octenium.toml - ew.writeln(`Configuration for Octenium.`) - ew.writeln(`Code: 'octenium'`) - ew.writeln(`Since: 'v4.27.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "OCTENIUM_API_KEY": API key`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "OCTENIUM_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "OCTENIUM_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "OCTENIUM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "OCTENIUM_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/octenium`) - case "oraclecloud": // generated from: providers/dns/oraclecloud/oraclecloud.toml ew.writeln(`Configuration for Oracle Cloud.`) @@ -3117,25 +2473,19 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "OCI_COMPARTMENT_OCID": Compartment OCID`) - ew.writeln(` - "OCI_FINGERPRINT": Public key fingerprint (ignored if 'OCI_AUTH_TYPE=instance_principal')`) - ew.writeln(` - "OCI_PRIVATE_KEY_PASSWORD": Private key password (ignored if 'OCI_AUTH_TYPE=instance_principal')`) - ew.writeln(` - "OCI_PRIVATE_KEY_PATH": Private key file (ignored if 'OCI_AUTH_TYPE=instance_principal')`) - ew.writeln(` - "OCI_REGION": Region (it can be empty if 'OCI_AUTH_TYPE=instance_principal').`) - ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID (ignored if 'OCI_AUTH_TYPE=instance_principal')`) - ew.writeln(` - "OCI_USER_OCID": User OCID (ignored if 'OCI_AUTH_TYPE=instance_principal')`) + ew.writeln(` - "OCI_PRIVKEY_FILE": Private key file`) + ew.writeln(` - "OCI_PRIVKEY_PASS": Private key password`) + ew.writeln(` - "OCI_PUBKEY_FINGERPRINT": Public key fingerprint`) + ew.writeln(` - "OCI_REGION": Region`) + ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID`) + ew.writeln(` - "OCI_USER_OCID": User OCID`) ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "OCI_AUTH_TYPE": Authorization type. Possible values: 'instance_principal', '' (Default: '')`) ew.writeln(` - "OCI_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) ew.writeln(` - "OCI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "OCI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "OCI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - ew.writeln(` - "TF_VAR_fingerprint": Alias on 'OCI_FINGERPRINT'`) - ew.writeln(` - "TF_VAR_private_key_path": Alias on 'OCI_PRIVATE_KEY_PATH'`) - ew.writeln(` - "TF_VAR_region": Alias on 'OCI_REGION'`) - ew.writeln(` - "TF_VAR_tenancy_ocid": Alias on 'OCI_TENANCY_OCID'`) - ew.writeln(` - "TF_VAR_user_ocid": Alias on 'OCI_USER_OCID'`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/oraclecloud`) @@ -3149,6 +2499,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "OTC_DOMAIN_NAME": Domain name`) + ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`) ew.writeln(` - "OTC_PASSWORD": Password`) ew.writeln(` - "OTC_PROJECT_NAME": Project name`) ew.writeln(` - "OTC_USER_NAME": User name`) @@ -3156,9 +2507,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) - ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens)`) ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "OTC_PRIVATE_ZONE": Set to true to use private zones only (default: use public zones only)`) ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) @@ -3439,7 +2788,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() @@ -3492,7 +2841,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "SCW_ACCESS_KEY": Access key`) - ew.writeln(` - "SCW_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) @@ -3536,14 +2884,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SELECTELV2_AUTH_REGION": Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1')`) - ew.writeln(` - "SELECTELV2_AUTH_URL": Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/')`) ew.writeln(` - "SELECTELV2_BASE_URL": API endpoint URL`) ew.writeln(` - "SELECTELV2_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "SELECTELV2_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) ew.writeln(` - "SELECTELV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "SELECTELV2_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) - ew.writeln(` - "SELECTELV2_USER_DOMAIN_NAME": To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectelv2`) @@ -3697,26 +3042,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 +3105,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.`) @@ -3814,7 +3118,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "TRANSIP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "TRANSIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) ew.writeln(` - "TRANSIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) ew.writeln(` - "TRANSIP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) @@ -3843,26 +3146,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.`) @@ -3963,7 +3246,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VINYLDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "VINYLDNS_QUOTE_VALUE": Adds quotes around the TXT record value (Default: false)`) @@ -3972,26 +3254,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.`) @@ -4083,44 +3345,23 @@ func displayDNSHelp(w io.Writer, name string) error { case "webnames": // generated from: providers/dns/webnames/webnames.toml - ew.writeln(`Configuration for webnames.ru.`) + ew.writeln(`Configuration for Webnames.`) ew.writeln(`Code: 'webnames'`) ew.writeln(`Since: 'v4.15.0'`) ew.writeln() ew.writeln(`Credentials:`) - ew.writeln(` - "WEBNAMESRU_API_KEY": Domain API key`) + ew.writeln(` - "WEBNAMES_API_KEY": Domain API key`) ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEBNAMESRU_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "WEBNAMESRU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "WEBNAMESRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`) - case "webnamesca": - // generated from: providers/dns/webnamesca/webnamesca.toml - ew.writeln(`Configuration for webnames.ca.`) - ew.writeln(`Code: 'webnamesca'`) - ew.writeln(`Since: 'v4.28.0'`) - ew.writeln() - - ew.writeln(`Credentials:`) - ew.writeln(` - "WEBNAMESCA_API_KEY": API key`) - ew.writeln(` - "WEBNAMESCA_API_USER": API username`) - ew.writeln() - - ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEBNAMESCA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "WEBNAMESCA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "WEBNAMESCA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "WEBNAMESCA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) - - ew.writeln() - ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnamesca`) - case "websupport": // generated from: providers/dns/websupport/websupport.toml ew.writeln(`Configuration for Websupport.`) @@ -4307,6 +3548,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/Makefile b/docs/Makefile index 6c84c7d1d..8e32681d1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,14 +1,14 @@ -.PHONY: default clean serve build +.PHONY: default clean hugo hugo-build -default: clean serve +default: clean hugo clean: rm -rf public/ -build: clean +hugo-build: clean hugo --enableGitInfo --source . -serve: +hugo: hugo server --disableFastRender --enableGitInfo --watch --source . # hugo server -D diff --git a/docs/content/_index.md b/docs/content/_index.md index 95e411afc..a211c5d62 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -7,24 +7,14 @@ 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) - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [180 DNS providers]({{% ref "dns" %}}) + - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension +- Comes with about [150 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates 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 deleted file mode 100644 index e12ec7cfd..000000000 --- a/docs/content/dns/zz_gen_anexia.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: "Anexia CloudDNS" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: anexia -dnsprovider: - since: "v4.28.0" - code: "anexia" - url: "https://www.anexia-it.com/" ---- - - - - - - -Configuration for [Anexia CloudDNS](https://www.anexia-it.com/). - - - - -- Code: `anexia` -- Since: v4.28.0 - - -Here is an example bash command using the Anexia CloudDNS provider: - -```bash -ANEXIA_TOKEN=xxx \ -lego --dns anexia -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `ANEXIA_TOKEN` | API token for Anexia Engine | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `ANEXIA_API_URL` | API endpoint URL (default: https://engine.anexia-it.com) | -| `ANEXIA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `ANEXIA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `ANEXIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | -| `ANEXIA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - -## Description - -You need to create an API token in the [Anexia Engine](https://engine.anexia-it.com/). - -The token must have permissions to manage DNS zones and records. - - - -## More information - -- [API documentation](https://engine.anexia-it.com/docs/en/module/clouddns/api) - - - - diff --git a/docs/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..f9e7d3844 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 ``` @@ -229,10 +229,6 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider. It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`. -### Azure DevOps Pipelines - -It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`. - diff --git a/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 deleted file mode 100644 index 3f03a2ac5..000000000 --- a/docs/content/dns/zz_gen_beget.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "Beget.com" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: beget -dnsprovider: - since: "v4.27.0" - code: "beget" - url: "https://beget.com/" ---- - - - - - - -Configuration for [Beget.com](https://beget.com/). - - - - -- Code: `beget` -- Since: v4.27.0 - - -Here is an example bash command using the Beget.com provider: - -```bash -BEGET_USERNAME=xxxxxx \ -BEGET_PASSWORD=yyyyyy \ -lego --dns beget -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `BEGET_PASSWORD` | API password | -| `BEGET_USERNAME` | API username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `BEGET_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `BEGET_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | -| `BEGET_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | -| `BEGET_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://beget.com/ru/kb/api/funkczii-upravleniya-dns) - - - - diff --git a/docs/content/dns/zz_gen_binarylane.md b/docs/content/dns/zz_gen_binarylane.md deleted file mode 100644 index eebf3c54e..000000000 --- a/docs/content/dns/zz_gen_binarylane.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Binary Lane" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: binarylane -dnsprovider: - since: "v4.26.0" - code: "binarylane" - url: "https://www.binarylane.com.au/" ---- - - - - - - -Configuration for [Binary Lane](https://www.binarylane.com.au/). - - - - -- Code: `binarylane` -- Since: v4.26.0 - - -Here is an example bash command using the Binary Lane provider: - -```bash -BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns binarylane -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `BINARYLANE_API_TOKEN` | API token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `BINARYLANE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `BINARYLANE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `BINARYLANE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `BINARYLANE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://api.binarylane.com.au/reference/#tag/Domains) - - - - diff --git a/docs/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..7b4db2020 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 ``` @@ -47,7 +47,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `BUNNY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `BUNNY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `BUNNY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `BUNNY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | diff --git a/docs/content/dns/zz_gen_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 deleted file mode 100644 index ba5de5ba2..000000000 --- a/docs/content/dns/zz_gen_edgeone.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: "Tencent EdgeOne" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: edgeone -dnsprovider: - since: "v4.26.0" - code: "edgeone" - url: "https://edgeone.ai" ---- - - - - - - -Configuration for [Tencent EdgeOne](https://edgeone.ai). - - - - -- Code: `edgeone` -- Since: v4.26.0 - - -Here is an example bash command using the Tencent EdgeOne provider: - -```bash -EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ -EDGEONE_SECRET_KEY=your-secret-key \ -lego --dns edgeone -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `EDGEONE_SECRET_ID` | Access key ID | -| `EDGEONE_SECRET_KEY` | Access Key secret | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `EDGEONE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `EDGEONE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | -| `EDGEONE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | -| `EDGEONE_REGION` | Region | -| `EDGEONE_SESSION_TOKEN` | Access Key token | -| `EDGEONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | -| `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" %}}). - - - - -## More information - -- [API documentation](https://edgeone.ai/document/50454#dns-record-apis) -- [Go client](https://github.com/tencentcloud/tencentcloud-sdk-go) - - - - diff --git a/docs/content/dns/zz_gen_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..a42175e8d 100644 --- a/docs/content/dns/zz_gen_hetzner.md +++ b/docs/content/dns/zz_gen_hetzner.md @@ -26,8 +26,8 @@ Configuration for [Hetzner](https://hetzner.com). 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 +HETZNER_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ +lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ``` @@ -37,7 +37,7 @@ lego --dns hetzner -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| -| `HETZNER_API_TOKEN` | API token | +| `HETZNER_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" %}}). @@ -49,8 +49,8 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `HETZNER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | +| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). @@ -60,7 +60,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://docs.hetzner.cloud/reference/cloud#dns) +- [API documentation](https://dns.hetzner.com/api-docs) 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 deleted file mode 100644 index c05b3f003..000000000 --- a/docs/content/dns/zz_gen_hostinger.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Hostinger" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: hostinger -dnsprovider: - since: "v4.27.0" - code: "hostinger" - url: "https://www.hostinger.com/" ---- - - - - - - -Configuration for [Hostinger](https://www.hostinger.com/). - - - - -- Code: `hostinger` -- Since: v4.27.0 - - -Here is an example bash command using the Hostinger provider: - -```bash -HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostinger -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `HOSTINGER_API_TOKEN` | API Token | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `HOSTINGER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `HOSTINGER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `HOSTINGER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `HOSTINGER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://developers.hostinger.com/#tag/dns-zone) - - - - diff --git a/docs/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..c2f9f4fe1 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 ``` @@ -39,7 +39,7 @@ lego --dns ibmcloud -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| | `SOFTLAYER_API_KEY` | Classic Infrastructure API key | -| `SOFTLAYER_USERNAME` | Username (IBM Cloud is {accountID}_{emailAddress}) | +| `SOFTLAYER_USERNAME` | Username (IBM Cloud is _) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). 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..251bf2096 100644 --- a/docs/content/dns/zz_gen_iwantmyname.md +++ b/docs/content/dns/zz_gen_iwantmyname.md @@ -1,5 +1,5 @@ --- -title: "iwantmyname (Deprecated)" +title: "iwantmyname" date: 2019-03-03T16:39:46+01:00 draft: false slug: iwantmyname @@ -13,10 +13,8 @@ dnsprovider: -The iwantmyname API has shut down. - -https://github.com/go-acme/lego/issues/2563 +Configuration for [iwantmyname](https://iwantmyname.com). @@ -25,12 +23,12 @@ https://github.com/go-acme/lego/issues/2563 - Since: v4.7.0 -Here is an example bash command using the iwantmyname (Deprecated) provider: +Here is an example bash command using the iwantmyname 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 deleted file mode 100644 index e39d3ce82..000000000 --- a/docs/content/dns/zz_gen_keyhelp.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "KeyHelp" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: keyhelp -dnsprovider: - since: "v4.26.0" - code: "keyhelp" - url: "https://www.keyweb.de/en/keyhelp/keyhelp/" ---- - - - - - - -Configuration for [KeyHelp](https://www.keyweb.de/en/keyhelp/keyhelp/). - - - - -- Code: `keyhelp` -- Since: v4.26.0 - - -Here is an example bash command using the KeyHelp provider: - -```bash -KEYHELP_BASE_URL="https://keyhelp.example.com" \ -KEYHELP_API_KEY="xxx" \ -lego --dns keyhelp -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `KEYHELP_API_KEY` | API key | -| `KEYHELP_BASE_URL` | Server URL | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `KEYHELP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `KEYHELP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `KEYHELP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `KEYHELP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://app.swaggerhub.com/apis-docs/keyhelp/api/) - - - - diff --git a/docs/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..8b5048c60 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 ``` @@ -51,7 +51,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MAILINABOX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `MAILINABOX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | | `MAILINABOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | diff --git a/docs/content/dns/zz_gen_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 deleted file mode 100644 index f25da4f44..000000000 --- a/docs/content/dns/zz_gen_octenium.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Octenium" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: octenium -dnsprovider: - since: "v4.27.0" - code: "octenium" - url: "https://octenium.com/" ---- - - - - - - -Configuration for [Octenium](https://octenium.com/). - - - - -- Code: `octenium` -- Since: v4.27.0 - - -Here is an example bash command using the Octenium provider: - -```bash -OCTENIUM_API_KEY="xxx" \ -lego --dns octenium -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `OCTENIUM_API_KEY` | API key | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `OCTENIUM_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `OCTENIUM_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `OCTENIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `OCTENIUM_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://octenium.com/api#tag/Domains-DNS) - - - - diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index b7192f380..1adf58088 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -26,21 +26,14 @@ Configuration for [Oracle Cloud](https://cloud.oracle.com/home). Here is an example bash command using the Oracle Cloud provider: ```bash -# Using API Key authentication: -OCI_PRIVATE_KEY_PATH="~/.oci/oci_api_key.pem" \ -OCI_PRIVATE_KEY_PASSWORD="secret" \ +OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ +OCI_PRIVKEY_PASS="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ 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_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --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 ``` @@ -51,12 +44,12 @@ lego --dns oraclecloud -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| | `OCI_COMPARTMENT_OCID` | Compartment OCID | -| `OCI_FINGERPRINT` | Public key fingerprint (ignored if `OCI_AUTH_TYPE=instance_principal`) | -| `OCI_PRIVATE_KEY_PASSWORD` | Private key password (ignored if `OCI_AUTH_TYPE=instance_principal`) | -| `OCI_PRIVATE_KEY_PATH` | Private key file (ignored if `OCI_AUTH_TYPE=instance_principal`) | -| `OCI_REGION` | Region (it can be empty if `OCI_AUTH_TYPE=instance_principal`). | -| `OCI_TENANCY_OCID` | Tenancy OCID (ignored if `OCI_AUTH_TYPE=instance_principal`) | -| `OCI_USER_OCID` | User OCID (ignored if `OCI_AUTH_TYPE=instance_principal`) | +| `OCI_PRIVKEY_FILE` | Private key file | +| `OCI_PRIVKEY_PASS` | Private key password | +| `OCI_PUBKEY_FINGERPRINT` | Public key fingerprint | +| `OCI_REGION` | Region | +| `OCI_TENANCY_OCID` | Tenancy OCID | +| `OCI_USER_OCID` | User OCID | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). @@ -66,16 +59,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `OCI_AUTH_TYPE` | Authorization type. Possible values: 'instance_principal', '' (Default: '') | | `OCI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | | `OCI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `OCI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `OCI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | -| `TF_VAR_fingerprint` | Alias on `OCI_FINGERPRINT` | -| `TF_VAR_private_key_path` | Alias on `OCI_PRIVATE_KEY_PATH` | -| `TF_VAR_region` | Alias on `OCI_REGION` | -| `TF_VAR_tenancy_ocid` | Alias on `OCI_TENANCY_OCID` | -| `TF_VAR_user_ocid` | Alias on `OCI_USER_OCID` | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index 9da69c694..fe92f3001 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -23,15 +23,9 @@ Configuration for [Open Telekom Cloud](https://cloud.telekom.de/en). - Since: v0.4.1 -Here is an example bash command using the Open Telekom Cloud provider: - -```bash -OTC_DOMAIN_NAME=domain_name \ -OTC_USER_NAME=user_name \ -OTC_PASSWORD=password \ -OTC_PROJECT_NAME=project_name \ -lego --dns otc -d '*.example.com' -d example.com run -``` +{{% notice note %}} +_Please contribute by adding a CLI example._ +{{% /notice %}} @@ -41,6 +35,7 @@ lego --dns otc -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| | `OTC_DOMAIN_NAME` | Domain name | +| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL | | `OTC_PASSWORD` | Password | | `OTC_PROJECT_NAME` | Project name | | `OTC_USER_NAME` | User name | @@ -54,9 +49,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `OTC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | -| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens) | | `OTC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `OTC_PRIVATE_ZONE` | Set to true to use private zones only (default: use public zones only) | | `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `OTC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | | `OTC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | diff --git a/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..7f9d6b7c7 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 ``` @@ -49,7 +49,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `SCW_ACCESS_KEY` | Access key | -| `SCW_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `SCW_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | | `SCW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `SCW_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | diff --git a/docs/content/dns/zz_gen_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..24dd67d1e 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 ``` @@ -53,14 +53,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SELECTELV2_AUTH_REGION` | Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1') | -| `SELECTELV2_AUTH_URL` | Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/') | | `SELECTELV2_BASE_URL` | API endpoint URL | | `SELECTELV2_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `SELECTELV2_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | | `SELECTELV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `SELECTELV2_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | -| `SELECTELV2_USER_DOMAIN_NAME` | To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/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..3214b0ca5 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 ``` @@ -62,7 +62,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://wiki.servercow.de/en/domains/dns_api/api-syntax/) +- [API documentation](https://cp.servercow.de/client/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/) 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..bc08c43ce 100644 --- a/docs/content/dns/zz_gen_tencentcloud.md +++ b/docs/content/dns/zz_gen_tencentcloud.md @@ -6,7 +6,7 @@ slug: tencentcloud dnsprovider: since: "v4.6.0" code: "tencentcloud" - url: "https://cloud.tencent.com/product/dns" + url: "https://cloud.tencent.com/product/cns" --- @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [Tencent Cloud DNS](https://cloud.tencent.com/product/dns). +Configuration for [Tencent Cloud DNS](https://cloud.tencent.com/product/cns). @@ -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..68b0f7acf 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 ``` @@ -49,7 +49,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `TRANSIP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `TRANSIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | | `TRANSIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | | `TRANSIP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | diff --git a/docs/content/dns/zz_gen_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..9a9c4bef0 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 ``` @@ -51,7 +51,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VINYLDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | | `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `VINYLDNS_QUOTE_VALUE` | Adds quotes around the TXT record value (Default: false) | diff --git a/docs/content/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..d721466b3 100644 --- a/docs/content/dns/zz_gen_webnames.md +++ b/docs/content/dns/zz_gen_webnames.md @@ -1,5 +1,5 @@ --- -title: "webnames.ru" +title: "Webnames" date: 2019-03-03T16:39:46+01:00 draft: false slug: webnames @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [webnames.ru](https://www.webnames.ru/). +Configuration for [Webnames](https://www.webnames.ru/). @@ -23,11 +23,11 @@ Configuration for [webnames.ru](https://www.webnames.ru/). - Since: v4.15.0 -Here is an example bash command using the webnames.ru provider: +Here is an example bash command using the Webnames provider: ```bash -WEBNAMESRU_API_KEY=xxxxxx \ -lego --dns webnamesru -d '*.example.com' -d example.com run +WEBNAMES_API_KEY=xxxxxx \ +lego --email you@example.com --dns webnames -d '*.example.com' -d example.com run ``` @@ -37,7 +37,7 @@ lego --dns webnamesru -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| -| `WEBNAMESRU_API_KEY` | Domain API key | +| `WEBNAMES_API_KEY` | Domain API key | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WEBNAMESRU_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `WEBNAMESRU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `WEBNAMESRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `WEBNAMES_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. More information [here]({{% ref "dns#configuration-and-credentials" %}}). diff --git a/docs/content/dns/zz_gen_webnamesca.md b/docs/content/dns/zz_gen_webnamesca.md deleted file mode 100644 index 4a7d3794f..000000000 --- a/docs/content/dns/zz_gen_webnamesca.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "webnames.ca" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: webnamesca -dnsprovider: - since: "v4.28.0" - code: "webnamesca" - url: "https://www.webnames.ca/" ---- - - - - - - -Configuration for [webnames.ca](https://www.webnames.ca/). - - - - -- Code: `webnamesca` -- Since: v4.28.0 - - -Here is an example bash command using the webnames.ca provider: - -```bash -WEBNAMESCA_API_USER="xxx" \ -WEBNAMESCA_API_KEY="yyy" \ -lego --dns webnamesca -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `WEBNAMESCA_API_KEY` | API key | -| `WEBNAMESCA_API_USER` | API username | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - -## Additional Configuration - -| Environment Variable Name | Description | -|--------------------------------|-------------| -| `WEBNAMESCA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `WEBNAMESCA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `WEBNAMESCA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `WEBNAMESCA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. -More information [here]({{% ref "dns#configuration-and-credentials" %}}). - - - - -## More information - -- [API documentation](https://www.webnames.ca/_/swagger/index.html) - - - - diff --git a/docs/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/content/usage/cli/Options.md b/docs/content/usage/cli/Options.md index 7b5df027a..25ba7e593 100644 --- a/docs/content/usage/cli/Options.md +++ b/docs/content/usage/cli/Options.md @@ -143,25 +143,6 @@ Example: LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true ``` -### LEGO_DEBUG_DNS_API_HTTP_CLIENT - -> **⚠️ WARNING: This will expose credentials in the log output! ⚠️** -> -> Do not run this in production environments, or if you can't be sure that logs aren't accessed by third parties or tools (like log collectors). -> -> You have been warned. Here be dragons. - -The environment variable `LEGO_DEBUG_DNS_API_HTTP_CLIENT` allows debugging the DNS API interaction. -It will dump the full request and response to the log output. - -Some DNS providers don't support this option. - -Example: - -```bash -LEGO_DEBUG_DNS_API_HTTP_CLIENT=true -``` - ### LEGO_DEBUG_ACME_HTTP_CLIENT The environment variable `LEGO_DEBUG_ACME_HTTP_CLIENT` allows debug the calls to the ACME server. diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 139143b17..95ff0770a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -23,7 +23,7 @@ GLOBAL OPTIONS: --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER] --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) --email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL] - --disable-cn Disable the use of the common name in the CSR. (default: false) + --disable-cn Disable the use of the common name in the CSR. (default: false) [$disable-cn] --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB] --kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID] @@ -76,7 +76,7 @@ OPTIONS: --not-after value Set the notAfter field in the certificate (RFC3339 format) --private-key value Path to private key (in PEM encoding) for the certificate. By default, the private key is generated. --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one. + --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --run-hook value Define a hook. The hook is executed when the certificates are effectively created. --run-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) @@ -103,7 +103,7 @@ OPTIONS: --not-before value Set the notBefore field in the certificate (RFC3339 format) --not-after value Set the notAfter field in the certificate (RFC3339 format) --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one. + --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. --renew-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) @@ -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, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/docs/go.mod b/docs/go.mod index 2240eb1e6..5cb2add45 100644 --- a/docs/go.mod +++ b/docs/go.mod @@ -2,4 +2,4 @@ module github.com/go-acme/lego/docs go 1.20 -require github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb +require github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851 diff --git a/docs/go.sum b/docs/go.sum index b62d5c809..1ed963e87 100644 --- a/docs/go.sum +++ b/docs/go.sum @@ -1,2 +1,2 @@ -github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb h1:iTGWOs8uKUaYmd7+wHRyPGXxt+SS5Bhvx2RRboYRXlI= -github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= +github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851 h1:JpmKIb1bRzuAcgnphwSb35Xz9rk/Alq19uRWVGSwScA= +github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= diff --git a/docs/hugo.toml b/docs/hugo.toml index fe076a306..a974cea73 100644 --- a/docs/hugo.toml +++ b/docs/hugo.toml @@ -2,20 +2,47 @@ baseURL = "https://go-acme.github.io/lego/" languageCode = "en-us" title = "Lego" +# Code highlighting settings +pygmentsCodefences = true +pygmentsCodeFencesGuesSsyntax = false +pygmentsOptions = "" +pygmentsStyle = "monokai" +# The monokai stylesheet is included in the base template. +pygmentsUseClasses = true + [permalinks] dns = "/dns/:slug/" [params] + # Prefix URL to edit current page. Will display an "Edit this page" button on top right hand corner of every page. + # Useful to give opportunity to people to create merge request for your doc. + # See the config.toml file from this documentation site to have an example. +# editURL = "" # Description of the site, will be used in meta information # description = "" # Shows a checkmark for visited pages on the menu showVisitedLinks = true + # Disable search function. It will hide search bar +# disableSearch = false + # Javascript and CSS cache are automatically busted when new version of site is generated. + # Set this to true to disable this behavior (some proxies don't handle well this optimization) +# disableAssetsBusting = false + # Set this to true to disable copy-to-clipboard button for inline code. +# disableInlineCopyToClipBoard = true + # A title for shortcuts in menu is set by default. Set this to true to disable it. +# disableShortcutsTitle = false + # When using mulitlingual website, disable the switch language button. +# disableLanguageSwitchingButton = false + # Hide breadcrumbs in the header and only show the current page title +# disableBreadcrumb = true + # Hide Next and Previous page buttons normally displayed full height beside content +# disableNextPrev = true + # Order sections in menu by "weight" or "title". Default to "weight" +# ordersectionsby = "weight" # Change default color scheme with a variant one. Can be "red", "blue", "green". themeVariant = "blue" custom_css = ["css/theme-custom.css"] disableLandingPageButton = true - hideAuthorEmail = true - hideAuthorName = true # Author of the site, will be used in meta information [params.author] @@ -44,7 +71,7 @@ title = "Lego" weight = 12 [outputs] - home = ['html', 'rss', 'print'] + home = [ "html", "rss", "search", "searchpage"] [module] [[module.imports]] diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index be1d23131..59930923b 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -5,10 +5,8 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "encoding/pem" "fmt" "os" - "path/filepath" "testing" "time" @@ -23,18 +21,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - testDomain1 = "acme.localhost" - testDomain2 = "lego.localhost" - testDomain3 = "acme.lego.localhost" - testDomain4 = "légô.localhost" -) - -const ( - testEmail1 = "lego@example.com" - testEmail2 = "acme@example.com" -) - var load = loader.EnvLoader{ PebbleOptions: &loader.CmdOption{ HealthCheckURL: "https://localhost:14000/dir", @@ -65,10 +51,10 @@ func TestChallengeHTTP_Run(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain1, + "-d", "acme.wtf", "--http", "--http.port", ":5002", "run") @@ -81,10 +67,10 @@ func TestChallengeTLS_Run_Domains(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain1, + "-d", "acme.wtf", "--tls", "--tls.port", ":5001", "run") @@ -97,7 +83,7 @@ func TestChallengeTLS_Run_IP(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", "-d", "127.0.0.1", @@ -112,13 +98,11 @@ func TestChallengeTLS_Run_IP(t *testing.T) { func TestChallengeTLS_Run_CSR(t *testing.T) { loader.CleanLegoFiles() - csrPath := createTestCSRFile(t, true) - err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-csr", csrPath, + "-csr", "./fixtures/csr.raw", "--tls", "--tls.port", ":5001", "run") @@ -130,13 +114,11 @@ func TestChallengeTLS_Run_CSR(t *testing.T) { func TestChallengeTLS_Run_CSR_PEM(t *testing.T) { loader.CleanLegoFiles() - csrPath := createTestCSRFile(t, false) - err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-csr", csrPath, + "-csr", "./fixtures/csr.cert", "--tls", "--tls.port", ":5001", "run") @@ -149,11 +131,11 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain2, - "-d", testDomain3, + "-d", "lego.wtf", + "-d", "acme.lego.wtf", "--tls", "--tls.port", ":5001", "run") @@ -162,10 +144,10 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { } err = load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain2, + "-d", "lego.wtf", "--tls", "--tls.port", ":5001", "revoke") @@ -178,10 +160,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain4, + "-d", "légô.wtf", "--tls", "--tls.port", ":5001", "run") @@ -190,10 +172,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { } err = load.RunLego( - "-m", testEmail1, + "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", testDomain4, + "-d", "légô.wtf", "--tls", "--tls.port", ":5001", "revoke") @@ -205,7 +187,6 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { func TestChallengeHTTP_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -223,18 +204,17 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{testDomain1}, + Domains: []string{"acme.wtf"}, Bundle: true, } resource, err := client.Certificate.Obtain(request) require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -245,7 +225,6 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -263,11 +242,10 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{testDomain1}, + Domains: []string{"acme.wtf"}, Bundle: true, Profile: "shortlived", } @@ -275,7 +253,7 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -286,7 +264,6 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -304,19 +281,18 @@ func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{testDomain1}, + Domains: []string{"acme.wtf"}, Bundle: true, - EmailAddresses: []string{testEmail1}, + EmailAddresses: []string{"foo@example.com"}, } resource, err := client.Certificate.Obtain(request) require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -327,7 +303,6 @@ func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -345,13 +320,12 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg now := time.Now().UTC() request := certificate.ObtainRequest{ - Domains: []string{testDomain1}, + Domains: []string{"acme.wtf"}, NotBefore: now.Add(1 * time.Hour), NotAfter: now.Add(2 * time.Hour), Bundle: true, @@ -360,7 +334,7 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -376,7 +350,6 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -394,7 +367,6 @@ func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg resource, err := client.Registration.QueryRegistration() @@ -410,7 +382,6 @@ func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { func TestChallengeTLS_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -428,7 +399,6 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg // https://github.com/letsencrypt/pebble/issues/285 @@ -436,7 +406,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { require.NoError(t, err, "Could not generate test key") request := certificate.ObtainRequest{ - Domains: []string{testDomain1}, + Domains: []string{"acme.wtf"}, Bundle: true, PrivateKey: privateKeyCSR, } @@ -444,7 +414,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -455,7 +425,6 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -473,10 +442,12 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg - csr, err := x509.ParseCertificateRequest(createTestCSR(t)) + csrRaw, err := os.ReadFile("./fixtures/csr.raw") + require.NoError(t, err) + + csr, err := x509.ParseCertificateRequest(csrRaw) require.NoError(t, err) resource, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ @@ -486,7 +457,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -497,7 +468,6 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -515,10 +485,12 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg - csr, err := x509.ParseCertificateRequest(createTestCSR(t)) + csrRaw, err := os.ReadFile("./fixtures/csr.raw") + require.NoError(t, err) + + csr, err := x509.ParseCertificateRequest(csrRaw) require.NoError(t, err) resource, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ @@ -529,7 +501,7 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, testDomain1, resource.Domain) + assert.Equal(t, "acme.wtf", resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -540,7 +512,6 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { func TestRegistrar_UpdateAccount(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -548,7 +519,7 @@ func TestRegistrar_UpdateAccount(t *testing.T) { user := &fakeUser{ privateKey: privateKey, - email: testEmail1, + email: "foo@example.com", } config := lego.NewConfig(user) config.CADirURL = load.PebbleOptions.HealthCheckURL @@ -559,13 +530,13 @@ func TestRegistrar_UpdateAccount(t *testing.T) { regOptions := registration.RegisterOptions{TermsOfServiceAgreed: true} reg, err := client.Registration.Register(regOptions) require.NoError(t, err) - require.Equal(t, []string{"mailto:" + testEmail1}, reg.Body.Contact) + require.Equal(t, []string{"mailto:foo@example.com"}, reg.Body.Contact) user.registration = reg - user.email = testEmail2 + user.email = "bar@example.com" resource, err := client.Registration.UpdateRegistration(regOptions) require.NoError(t, err) - require.Equal(t, []string{"mailto:" + testEmail2}, resource.Body.Contact) + require.Equal(t, []string{"mailto:bar@example.com"}, resource.Body.Contact) require.Equal(t, reg.URI, resource.URI) } @@ -578,53 +549,3 @@ type fakeUser struct { func (f *fakeUser) GetEmail() string { return f.email } func (f *fakeUser) GetRegistration() *registration.Resource { return f.registration } func (f *fakeUser) GetPrivateKey() crypto.PrivateKey { return f.privateKey } - -func createTestCSRFile(t *testing.T, raw bool) string { - t.Helper() - - csr := createTestCSR(t) - - if raw { - filename := filepath.Join(t.TempDir(), "csr.raw") - - fileRaw, err := os.Create(filename) - require.NoError(t, err) - - defer fileRaw.Close() - - _, err = fileRaw.Write(csr) - require.NoError(t, err) - - return filename - } - - filename := filepath.Join(t.TempDir(), "csr.cert") - - file, err := os.Create(filename) - require.NoError(t, err) - - defer file.Close() - - _, err = file.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr})) - require.NoError(t, err) - - return filename -} - -func createTestCSR(t *testing.T) []byte { - t.Helper() - - privateKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(t, err) - - csr, err := certcrypto.CreateCSR(privateKey, certcrypto.CSROptions{ - Domain: testDomain1, - SAN: []string{ - testDomain1, - testDomain2, - }, - }) - require.NoError(t, err) - - return csr -} diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 9dd9ab0d6..9ae07d46a 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -18,11 +18,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - testDomain1 = "légo.localhost" - testDomain2 = "*.légo.localhost" -) - var load = loader.EnvLoader{ PebbleOptions: &loader.CmdOption{ HealthCheckURL: "https://localhost:15000/dir", @@ -58,13 +53,14 @@ func TestChallengeDNS_Run(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( + "-m", "hubert@hubert.com", "--accept-tos", "--dns", "exec", "--dns.resolvers", ":8053", "--dns.disable-cp", "-s", "https://localhost:15000/dir", - "-d", testDomain2, - "-d", testDomain1, + "-d", "*.légo.acme", + "-d", "légo.acme", "run") if err != nil { t.Fatal(err) @@ -74,12 +70,10 @@ func TestChallengeDNS_Run(t *testing.T) { func TestChallengeDNS_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() err = os.Setenv("EXEC_PATH", "../fixtures/update-dns.sh") require.NoError(t, err) - defer func() { _ = os.Unsetenv("EXEC_PATH") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -102,10 +96,9 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg - domains := []string{testDomain2, testDomain1} + domains := []string{"*.légo.acme", "légo.acme"} // https://github.com/letsencrypt/pebble/issues/285 privateKeyCSR, err := rsa.GenerateKey(rand.Reader, 2048) @@ -120,7 +113,7 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "*.xn--lgo-bma.localhost", resource.Domain) + assert.Equal(t, "*.xn--lgo-bma.acme", resource.Domain) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -131,12 +124,10 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem") require.NoError(t, err) - defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() err = os.Setenv("EXEC_PATH", "../fixtures/update-dns.sh") require.NoError(t, err) - defer func() { _ = os.Unsetenv("EXEC_PATH") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -159,10 +150,9 @@ func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) - user.registration = reg - domains := []string{testDomain2, testDomain1} + domains := []string{"*.légo.acme", "légo.acme"} // https://github.com/letsencrypt/pebble/issues/285 privateKeyCSR, err := rsa.GenerateKey(rand.Reader, 2048) @@ -178,7 +168,7 @@ func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "*.xn--lgo-bma.localhost", resource.Domain) + assert.Equal(t, "*.xn--lgo-bma.acme", resource.Domain) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) 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/fixtures/csr.cert b/e2e/fixtures/csr.cert new file mode 100644 index 000000000..cece7ddec --- /dev/null +++ b/e2e/fixtures/csr.cert @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICfjCCAWYCAQAwEzERMA8GA1UEAxMIYWNtZS53dGYwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDAhXnho1w9OPHWs4YSMahYbG4Ui1K6hsHytBZfhsz0 +09igSWzHMEFZYHZJVuSr60enuJSZRhgwDjfhQWSUgHgKItLPnlNVYM6RhVaW0WfT +w6CpmE2AuH3WuQbrR2he1Nt0xfUJla+VWOFZuW7GhgBiV5iWBvdLv6Ztgh8eATjo +2vG2R+KuSUzrm6h+sb3nUR28OYunZ3vESjNwnL3/D/1th2rFpe3EA3em1HArJdXN +F4eclciun5Js17AS9tdoHEEZMMBWyViiuz3CQlh+YD2qAvqaubanWNa+r+iijMvd +4HlDHC99LTk6TJoSKoL+E/OGKmntLqmBJ1UrCFgvnw3DAgMBAAGgJjAkBgkqhkiG +9w0BCQ4xFzAVMBMGA1UdEQQMMAqCCGFjbWUud3RmMA0GCSqGSIb3DQEBCwUAA4IB +AQAfBLR8njftxf15V49szNsgNaG7Y5UQFwgl8pyiIaanGvX1DE0BtU1RB/w7itzX +wW5W/wjielEbs1XkI2uz3hkebvHVA1QpA7bbrX01WonS18xCkiRDj8ZqFEG4vEGa +HswzGUfq2v0gCOIPpVGE+8Q2Y7In5zwEfev+5DkHox4/vgwMhyPMI+y7jKtdG/dV +U58SFnt/F1raoSmR6vfDcAFXm/L8LXEkxqqefFbhiRHRqQar1Wr15BH//swmNzEW +5SVCCHcyIqreSua8uPjBcJ8aYVLniX6DMRyYv4ij/PSvSQy9xJDewLqR235WfTd/ +tk4hhJaqizKDpsvB+UFod5o5 +-----END CERTIFICATE REQUEST----- diff --git a/e2e/fixtures/csr.raw b/e2e/fixtures/csr.raw new file mode 100644 index 000000000..f4bb701cd Binary files /dev/null and b/e2e/fixtures/csr.raw differ diff --git a/e2e/loader/loader.go b/e2e/loader/loader.go index 3e63302a3..b5ac9cef8 100644 --- a/e2e/loader/loader.go +++ b/e2e/loader/loader.go @@ -43,14 +43,12 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, e2e := os.LookupEnv("LEGO_E2E_TESTS"); !e2e { fmt.Fprintln(os.Stderr, "skipping test: e2e tests are disabled. (no 'LEGO_E2E_TESTS' env var)") fmt.Println("PASS") - return 0 } if _, err := exec.LookPath("git"); err != nil { fmt.Fprintln(os.Stderr, "skipping because git command not found") fmt.Println("PASS") - return 0 } @@ -58,7 +56,6 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, err := exec.LookPath(cmdNamePebble); err != nil { fmt.Fprintln(os.Stderr, "skipping because pebble binary not found") fmt.Println("PASS") - return 0 } } @@ -67,7 +64,6 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, err := exec.LookPath(cmdNameChallSrv); err != nil { fmt.Fprintln(os.Stderr, "skipping because challtestsrv binary not found") fmt.Println("PASS") - return 0 } } @@ -80,7 +76,6 @@ func (l *EnvLoader) MainTest(m *testing.M) int { legoBinary, tearDown, err := buildLego() defer tearDown() - if err != nil { fmt.Fprintln(os.Stderr, err) return 1 @@ -141,7 +136,6 @@ func (l *EnvLoader) launchPebble() func() { } pebble, outPebble := l.cmdPebble() - go func() { err := pebble.Run() if err != nil { @@ -154,7 +148,6 @@ func (l *EnvLoader) launchPebble() func() { if err != nil { fmt.Println(err) } - fmt.Println(outPebble.String()) } } @@ -167,13 +160,11 @@ func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) { if err != nil { panic(err) } - cmd.Dir = dir fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) var b bytes.Buffer - cmd.Stdout = &b cmd.Stderr = &b @@ -182,7 +173,6 @@ func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) { func pebbleHealthCheck(options *CmdOption) { client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} - err := wait.For("pebble", 10*time.Second, 500*time.Millisecond, func() (bool, error) { resp, err := client.Get(options.HealthCheckURL) if err != nil { @@ -206,7 +196,6 @@ func (l *EnvLoader) launchChallSrv() func() { } challtestsrv, outChalSrv := l.cmdChallSrv() - go func() { err := challtestsrv.Run() if err != nil { @@ -219,7 +208,6 @@ func (l *EnvLoader) launchChallSrv() func() { if err != nil { fmt.Println(err) } - fmt.Println(outChalSrv.String()) } } @@ -230,7 +218,6 @@ func (l *EnvLoader) cmdChallSrv() (*exec.Cmd, *bytes.Buffer) { fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) var b bytes.Buffer - cmd.Stdout = &b cmd.Stderr = &b @@ -242,7 +229,6 @@ func buildLego() (string, func(), error) { if err != nil { return "", func() {}, err } - defer func() { _ = os.Chdir(here) }() buildPath, err := os.MkdirTemp("", "lego_test") @@ -276,7 +262,6 @@ func buildLego() (string, func(), error) { return binary, func() { _ = os.RemoveAll(buildPath) - CleanLegoFiles() }, nil } @@ -298,7 +283,6 @@ func build(binary string) error { if err != nil { return err } - cmd := exec.Command(toolPath, "build", "-o", binary) output, err := cmd.CombinedOutput() @@ -350,7 +334,6 @@ func goTool() (string, error) { func CleanLegoFiles() { cmd := exec.Command("rm", "-rf", ".lego") fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) - output, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(output)) diff --git a/e2e/readme.md b/e2e/readme.md index 171170507..746b9d726 100644 --- a/e2e/readme.md +++ b/e2e/readme.md @@ -1,9 +1,20 @@ # E2E tests +How to run: + +- Add the following entries to your `/etc/hosts`: +``` +127.0.0.1 acme.wtf +127.0.0.1 lego.wtf +127.0.0.1 acme.lego.wtf +127.0.0.1 légô.wtf +127.0.0.1 xn--lg-bja9b.wtf +``` + - Install [Pebble](https://github.com/letsencrypt/pebble): ```bash -go install github.com/letsencrypt/pebble/v2/cmd/pebble@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..f2c6a71d0 100644 --- a/go.mod +++ b/go.mod @@ -3,170 +3,161 @@ module github.com/go-acme/lego/v4 go 1.24.0 require ( - cloud.google.com/go/compute/metadata v0.9.0 + cloud.google.com/go/compute/metadata v0.7.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 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/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/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/cenkalti/backoff/v5 v5.0.3 + github.com/BurntSushi/toml v1.5.0 + github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 + github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 + github.com/aliyun/credentials-go v1.4.6 + github.com/aws/aws-sdk-go-v2 v1.36.6 + github.com/aws/aws-sdk-go-v2/config v1.29.18 + github.com/aws/aws-sdk-go-v2/credentials v1.17.71 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 + github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 + github.com/aziontech/azionapi-go-sdk v0.142.0 + github.com/baidubce/bce-sdk-go v0.9.235 + github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.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/go-jose/go-jose/v4 v4.1.3 - github.com/go-viper/mapstructure/v2 v2.5.0 + github.com/exoscale/egoscale/v3 v3.1.24 + github.com/go-acme/alidns-20150109/v4 v4.5.10 + github.com/go-acme/tencentclouddnspod v1.0.1208 + github.com/go-jose/go-jose/v4 v4.1.1 + github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-querystring v1.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.159 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/ldez/grignotin v0.9.0 + github.com/linode/linodego v1.53.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.72 + github.com/miekg/dns v1.1.67 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 - github.com/nrdcg/auroradns v1.2.0 - github.com/nrdcg/bunny-go v0.1.0 - github.com/nrdcg/desec v0.11.1 + github.com/nrdcg/auroradns v1.1.0 + github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea + github.com/nrdcg/desec v0.11.0 github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 - github.com/nrdcg/goinwx v0.12.0 - github.com/nrdcg/mailinabox v0.3.0 - github.com/nrdcg/namesilo v0.5.0 + github.com/nrdcg/goinwx v0.11.0 + github.com/nrdcg/mailinabox v0.2.0 + github.com/nrdcg/namesilo v0.2.1 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 github.com/nrdcg/porkbun v0.4.0 - github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 github.com/ovh/go-ovh v1.9.0 github.com/pquerna/otp v1.5.0 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 github.com/regfish/regfish-dnsapi-go v0.1.1 - github.com/sacloud/api-client-go v0.3.3 - github.com/sacloud/iaas-api-go v1.23.1 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 + github.com/sacloud/api-client-go v0.3.2 + github.com/sacloud/iaas-api-go v1.16.1 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 - github.com/softlayer/softlayer-go v1.2.1 - github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48 - github.com/transip/gotransip/v6 v6.26.1 - github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 + github.com/softlayer/softlayer-go v1.1.7 + github.com/stretchr/testify v1.10.0 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 + github.com/transip/gotransip/v6 v6.26.0 + github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.7 - 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 - golang.org/x/time v0.14.0 - google.golang.org/api v0.267.0 - gopkg.in/ns1/ns1-go.v2 v2.17.2 + github.com/vinyldns/go-vinyldns v0.9.16 + github.com/volcengine/volc-sdk-golang v1.0.216 + github.com/vultr/govultr/v3 v3.21.1 + github.com/yandex-cloud/go-genproto v0.14.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.3 + github.com/yandex-cloud/go-sdk/v2 v2.0.8 + golang.org/x/crypto v0.40.0 + golang.org/x/net v0.42.0 + golang.org/x/oauth2 v0.30.0 + golang.org/x/text v0.27.0 + golang.org/x/time v0.12.0 + google.golang.org/api v0.242.0 + gopkg.in/ns1/ns1-go.v2 v2.14.4 gopkg.in/yaml.v2 v2.4.0 - software.sslmate.com/src/go-pkcs12 v0.7.0 + software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( - cloud.google.com/go/auth v0.18.1 // indirect + cloud.google.com/go/auth v0.16.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect + github.com/alibabacloud-go/tea v1.3.9 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.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/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/benbjohnson/clock v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect + github.com/aws/smithy-go v1.22.4 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect - github.com/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 github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/fatih/color v1.16.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect - github.com/go-resty/resty/v2 v2.17.1 // indirect - github.com/goccy/go-yaml v1.9.8 // indirect - github.com/gofrs/flock v0.13.0 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/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.14.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -174,11 +165,11 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -186,17 +177,18 @@ 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 github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -204,26 +196,23 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/auto/sdk v1.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.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.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/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 + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -retract v4.30.0 // Problem related to misuse of sycalls by aliyun/credentials-go diff --git a/go.sum b/go.sum index f5b87c9fe..6ed16ca42 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.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= +cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -42,14 +42,14 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.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.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= @@ -85,17 +85,19 @@ 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.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.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= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= +github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -103,8 +105,8 @@ github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -121,10 +123,9 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.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-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 h1:AL+nH363NJFS1NXIjCdmj5MOElgKEqgFeoq7vjje350= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8/go.mod h1:d+z3ScRqc7PFzg4h9oqE3h8yunRZvAvU7u+iuPYEhpU= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -145,83 +146,78 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.13/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 v1.3.9 h1:bjgt1bvdY780vz/17iWNNtbXl4A77HWntWMeaUF3So0= +github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= -github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= -github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8Y0ClOk= +github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -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/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 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= +github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I= +github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA= +github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 h1:XTZZ0I3SZUHAtBLBU6395ad+VOblE0DwQP6MuaNeics= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37/go.mod h1:Pi6ksbniAWVwu2S8pEzcYPyhUkAcLaufxN7PfAUQjBk= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.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.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 h1:M5/B8JUaCI8+9QD+u3S/f4YHpvqE9RpSkV3rf0Iks2w= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5/go.mod h1:Bktzci1bwdbpuLiu3AOksiNPMl/LLKmX1TWmqp2xbvs= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 h1:OS2e0SKqsU2LiJPqL8u9x41tKc6MMEHrWjLVLn3oysg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18/go.mod h1:+Yrk+MDGzlNGxCXieljNeWpoZTCQUQVL+Jk9hGGJ8qM= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 h1:DYQbfSAWcMwRM0LbCDyQkPB1AcaZcLzLoaFrYcpyMag= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5/go.mod h1:Lav4KLgncVjjrwLWutOccjEgJ4T/RAdY+Ic0hmNIgI0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 h1:R3nSX1hguRy6MnknHiepSvqnnL8ansFwK2hidPesAYU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1/go.mod h1:fmSiB4OAghn85lQgk7XN9l9bpFg5Bm1v3HuaXKytPEw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 h1:RkHXU9jP0DptGy7qKI8CBGsUJruWz0v5IgwBa2DwWcU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1/go.mod h1:3xAOf7tdKF+qbb+XpU+EPhNXAdun3Lu1RcDrj8KC24I= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.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.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aziontech/azionapi-go-sdk v0.142.0 h1:1NOHXlC0/7VgbfbTIGVpsYn1THCugM4/SCOXVdUHQ+A= +github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= +github.com/baidubce/bce-sdk-go v0.9.235 h1:iAi+seH9w1Go2szFNzyGumahLGDsuYZ3i8hduX3qiM8= +github.com/baidubce/bce-sdk-go v0.9.235/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -235,9 +231,8 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -249,6 +244,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -271,6 +267,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= @@ -289,11 +287,10 @@ 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.24 h1:EUWmjw/JgMj1faX5ojosjrJE5eY0QEWP0KBmLyFU6aE= +github.com/exoscale/egoscale/v3 v3.1.24/go.mod h1:A53enXfm8nhVMpIYw0QxiwQ2P6AdCF4F/nVYChNEzdE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -317,24 +314,18 @@ 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.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zpuqDz2aO1/R/k= +github.com/go-acme/alidns-20150109/v4 v4.5.10/go.mod h1:qGRq8kD0xVgn82qRSQmhHwh/oWxKRjF4Db5OI4ScV5g= +github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= +github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -345,43 +336,33 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-resty/resty/v2 v2.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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.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= -github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ= -github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.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/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -390,8 +371,8 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -448,9 +429,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= @@ -462,21 +442,22 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -495,6 +476,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -525,8 +508,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 +524,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.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.159 h1:6LZysc4iyO4cHB1aJsRklWfSEJr8CEhW7BmcM0SkYcU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -553,8 +536,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 h1:AKsihjFT/t6Y0keEv3p59DACcOuh0inWXdUB0ZOzYH0= github.com/infobloxopen/infoblox-go-client/v2 v2.10.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= -github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= +github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= +github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -572,9 +555,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= -github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -611,13 +593,12 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPC github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= -github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= -github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= github.com/leodido/go-urn v1.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.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI= +github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -640,7 +621,6 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -653,8 +633,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= -github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -694,35 +674,34 @@ github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nrdcg/auroradns v1.2.0 h1:Jg407vTdXZvZKsART9CNWMp8rQOyhBk04q0MsOf0YR4= -github.com/nrdcg/auroradns v1.2.0/go.mod h1:hnByA4Z7MOmV4EPRw5eOmEaNRFavcCIz6kONpNxp9LI= -github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA= -github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A= -github.com/nrdcg/desec v0.11.1 h1:ilpKmCr4gGsLcyq3RHfHNmlRzm9fzT2XbWxoVaUCS0s= -github.com/nrdcg/desec v0.11.1/go.mod h1:2LuxHlOcwML/7cntu0eimONmA1U+ZxFDAonoSXr4igQ= +github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= +github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuFOObP2gwrenL4/qiKXQbQugr/Two= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA= +github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= +github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= -github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4= -github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0= -github.com/nrdcg/mailinabox v0.3.0 h1:PHkC1elKXKAjEvdx2HHFMgcEGZFqudAl7aU3L2JDhM4= -github.com/nrdcg/mailinabox v0.3.0/go.mod h1:1eFIGcM4lI+AfFOUpbs548SFGz1ZWoMOGbECBmkghw4= -github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= -github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= +github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= +github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= +github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= +github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= +github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= +github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.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.95.2 h1:a7QUZD5c+NkrFrdkdyJUO9cOUo8VQJyRkcIzk9Wh+DI= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2/go.mod h1:O6osg9dPzXq7H2ib/1qzimzG5oXSJFgccR7iawg7SwA= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 h1:yflYnbQu4ciWH/GEztqlAccLPw4k5mp11uhW++al5ow= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2/go.mod h1:atPDu37gu8HT7TtPpovrkgNmDAgOGM6TVEJ7ANTblMs= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= -github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= -github.com/nrdcg/vegadns v0.3.0/go.mod h1:NqSyRKZuJlAsv8VI/7rSubfPXN68NwaJ0aG9KxQVFVo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -737,16 +716,16 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= @@ -754,6 +733,8 @@ github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -811,31 +792,33 @@ github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 h1:dq90+d51/hQR github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE= github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sqBnCKDs= -github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= +github.com/sacloud/api-client-go v0.3.2 h1:INbdSpQbyGN9Ai4hQ+Gbv3UQcgtRPG2tJrOmqT7HGl0= +github.com/sacloud/api-client-go v0.3.2/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.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.16.1 h1:B5Lec9WyZkrOCjtGkVuPn5RxDm/zCzazVsHh7BQIjYQ= +github.com/sacloud/iaas-api-go v1.16.1/go.mod h1:QVPHLwYzpECMsuml55I3FWAggsb4XSuzYGE9re/SkrQ= +github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= +github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.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.34 h1:48+VFHsyVcAHIN2v1Ao9v1/RkjJS5AwctFucBrfYNIA= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -851,10 +834,15 @@ 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/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/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= +github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= +github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE= +github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -870,15 +858,14 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= @@ -900,50 +887,52 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.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.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 h1:waSk2KyI2VvXtR+XQJm0v1lWfgbJg51iSWJh4hWnyeo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/transip/gotransip/v6 v6.26.1 h1:MeqIjkTBBsZwWAK6giZyMkqLmKMclVHEuTNmoBdx4MA= -github.com/transip/gotransip/v6 v6.26.1/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= +github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= +github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b68e3iMvkr27fU7JqPKU4j7tIITZnjQX1k= -github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c= +github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= +github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.27.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.216 h1:+wAq8RvxpGECveRJaAXZFpzrZoQ33WjMuRyd9iY2Oc0= +github.com/volcengine/volc-sdk-golang v1.0.216/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM= +github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -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.14.0 h1:yDqD260mICkjodXyAaDhESfrLr6gIGwwRc9MYE0jvW0= +github.com/yandex-cloud/go-genproto v0.14.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.3 h1:erphTBXKSpm/tETa6FXrw4niSHjhySzAKHUc2/BZKx0= +github.com/yandex-cloud/go-sdk/services/dns v0.0.3/go.mod h1:lbBaFJVouETfVnd3YzNF5vW6vgYR2FVfGLUzLexyGlI= +github.com/yandex-cloud/go-sdk/v2 v2.0.8 h1:wQNIzEZYnClSQyo2fjEgnGEErWjJNBpSAinaKcP+VSg= +github.com/yandex-cloud/go-sdk/v2 v2.0.8/go.mod h1:9Gqpq7d0EUAS+H2OunILtMi3hmMPav+fYoy9rmydM4s= github.com/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 +956,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.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1035,8 +1024,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1080,8 +1069,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1139,16 +1128,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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1165,8 +1155,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1233,11 +1223,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1252,8 +1240,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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1268,8 +1256,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.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1288,16 +1276,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1355,18 +1343,14 @@ 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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1385,8 +1369,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= -google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= +google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1425,12 +1409,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-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/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 +1432,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.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1464,8 +1448,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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1476,15 +1460,17 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= +gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 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.14.4 h1:77eP71rZ24I+9k1gITgjJXRyJzzmflA9oPUkYPB/wyc= +gopkg.in/ns1/ns1-go.v2 v2.14.4/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1515,5 +1501,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.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= +software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/internal/clihelp/generator.go b/internal/clihelp/generator.go index fcabde015..2d256b4d7 100644 --- a/internal/clihelp/generator.go +++ b/internal/clihelp/generator.go @@ -50,7 +50,6 @@ func generate() error { // collect output of various help pages var help []commandHelp - for _, args := range [][]string{ {"lego", "help"}, {"lego", "help", "run"}, @@ -73,9 +72,7 @@ func generate() error { } err = outputTpl.Execute(f, help) - defer func() { _ = f.Close() }() - if err != nil { return fmt.Errorf("failed to write cli_help.toml: %w", err) } @@ -101,11 +98,9 @@ func createStubApp() *cli.App { func run(app *cli.App, args []string) (h commandHelp, err error) { w := app.Writer - defer func() { app.Writer = w }() var buf bytes.Buffer - app.Writer = &buf if err := app.Run(args); err != nil { diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index 9355d0d1b..d618ce568 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -48,11 +48,6 @@ func main() { log.Fatal(err) } - err = cleanDocumentation() - if err != nil { - log.Fatal(err) - } - for _, m := range models.Providers { // generate documentation err = generateDocumentation(m) @@ -76,22 +71,6 @@ func main() { fmt.Printf("Documentation for %d DNS providers has been generated.\n", len(models.Providers)+1) } -func cleanDocumentation() error { - paths, err := filepath.Glob(filepath.Join(docOutput, "zz_gen_*.md")) - if err != nil { - return err - } - - for _, p := range paths { - err = os.RemoveAll(p) - if err != nil { - return err - } - } - - return nil -} - func generateDocumentation(m descriptors.Provider) error { filename := filepath.Join(docOutput, "zz_gen_"+m.Code+".md") @@ -116,7 +95,6 @@ func generateCLIHelp(models *descriptors.Providers) error { defer func() { _ = file.Close() }() b := &bytes.Buffer{} - err = template.Must( template.New(filepath.Base(cliTemplate)).Funcs(map[string]any{ "safe": func(src string) string { @@ -135,7 +113,6 @@ func generateCLIHelp(models *descriptors.Providers) error { } _, err = file.Write(source) - return err } @@ -163,7 +140,6 @@ func generateReadMe(models *descriptors.Providers) error { if err = tpl.Execute(buffer, providers); err != nil { return err } - skip = true } @@ -190,29 +166,31 @@ 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)) }) - var ( - matrix [][]descriptors.Provider - row []descriptors.Provider - ) + var matrix [][]descriptors.Provider + var 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{}) } - matrix = append(matrix, row) default: @@ -224,7 +202,6 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { for j := len(row); j < nbCol; j++ { row = append(row, descriptors.Provider{}) } - matrix = append(matrix, row) } diff --git a/internal/dns/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/internal/dns/providers/generator.go b/internal/dns/providers/generator.go index df3f8a2e6..8f133a765 100644 --- a/internal/dns/providers/generator.go +++ b/internal/dns/providers/generator.go @@ -46,7 +46,6 @@ func generate() error { defer func() { _ = file.Close() }() b := &bytes.Buffer{} - err = template.Must( template.New("").Funcs(map[string]any{ "cleanName": func(src string) string { diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go index 57b463933..6047c427c 100644 --- a/internal/releaser/releaser.go +++ b/internal/releaser/releaser.go @@ -108,7 +108,6 @@ func detach(_ *cli.Context) error { func readCurrentVersion(filename string) (*hcversion.Version, error) { fset := token.NewFileSet() - file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) if err != nil { return nil, err @@ -142,7 +141,6 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { if !ok { continue } - if len(valueSpec.Names) != 1 || len(valueSpec.Values) != 1 { continue } @@ -151,7 +149,6 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { if !ok { continue } - if va.Kind != token.STRING { continue } @@ -167,7 +164,6 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { default: // noop } - return v } diff --git a/lego/client_test.go b/lego/client_test.go index 63d3b0ad1..4f07eb1ea 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - server := tester.MockACMEServer().BuildHTTPS(t) + _, apiURL := tester.SetupFakeAPI(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -25,8 +25,7 @@ func TestNewClient(t *testing.T) { } config := NewConfig(user) - config.CADirURL = server.URL + "/dir" - config.HTTPClient = server.Client() + config.CADirURL = apiURL + "/dir" client, err := NewClient(config) require.NoError(t, err, "Could not create client") diff --git a/platform/config/env/env.go b/platform/config/env/env.go index 33a0d6caa..b74a65cd9 100644 --- a/platform/config/env/env.go +++ b/platform/config/env/env.go @@ -16,13 +16,11 @@ func Get(names ...string) (map[string]string, error) { values := map[string]string{} var missingEnvVars []string - for _, envVar := range names { value := GetOrFile(envVar) if value == "" { missingEnvVars = append(missingEnvVars, envVar) } - values[envVar] = value } @@ -60,7 +58,6 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) { values := map[string]string{} var missingEnvVars []string - for _, names := range groups { if len(names) == 0 { return nil, errors.New("undefined environment variable names") @@ -71,7 +68,6 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) { missingEnvVars = append(missingEnvVars, envVar) continue } - values[envVar] = value } @@ -152,7 +148,6 @@ func GetOrFile(envVar string) string { } fileVar := envVar + "_FILE" - fileVarValue := os.Getenv(fileVar) if fileVarValue == "" { return envVarValue diff --git a/platform/tester/api.go b/platform/tester/api.go index 8343b487f..2084cf1bb 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -2,36 +2,53 @@ package tester import ( "encoding/json" - "fmt" "net/http" "net/http/httptest" + "testing" "github.com/go-acme/lego/v4/acme" - "github.com/go-acme/lego/v4/platform/tester/servermock" ) -// MockACMEServer Minimal stub ACME server for validation. -func MockACMEServer() *servermock.Builder[*httptest.Server] { - return servermock.NewBuilder( - func(server *httptest.Server) (*httptest.Server, error) { - return server, nil - }). - Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - serverURL := fmt.Sprintf("https://%s", req.Context().Value(http.LocalAddrContextKey)) +// SetupFakeAPI Minimal stub ACME server for validation. +func SetupFakeAPI(t *testing.T) (*http.ServeMux, string) { + t.Helper() - servermock.JSONEncode(acme.Directory{ - NewNonceURL: serverURL + "/nonce", - NewAccountURL: serverURL + "/account", - NewOrderURL: serverURL + "/newOrder", - RevokeCertURL: serverURL + "/revokeCert", - KeyChangeURL: serverURL + "/keyChange", - RenewalInfo: serverURL + "/renewalInfo", - }).ServeHTTP(rw, req) - })). - Route("HEAD /nonce", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Replay-Nonce", "12345") - rw.Header().Set("Retry-After", "0") - })) + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/dir", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + err := WriteJSONResponse(w, acme.Directory{ + NewNonceURL: server.URL + "/nonce", + NewAccountURL: server.URL + "/account", + NewOrderURL: server.URL + "/newOrder", + RevokeCertURL: server.URL + "/revokeCert", + KeyChangeURL: server.URL + "/keyChange", + RenewalInfo: server.URL + "/renewalInfo", + }) + + mux.HandleFunc("/nonce", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodHead { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Replay-Nonce", "12345") + w.Header().Set("Retry-After", "0") + }) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + return mux, server.URL } // WriteJSONResponse marshals the body as JSON and writes it to the response. @@ -42,7 +59,6 @@ func WriteJSONResponse(w http.ResponseWriter, body any) error { } w.Header().Set("Content-Type", "application/json") - if _, err := w.Write(bs); err != nil { return err } diff --git a/platform/tester/dnsmock/dnsmock.go b/platform/tester/dnsmock/dnsmock.go deleted file mode 100644 index 6cb4f45b8..000000000 --- a/platform/tester/dnsmock/dnsmock.go +++ /dev/null @@ -1,191 +0,0 @@ -package dnsmock - -import ( - "fmt" - "math" - "net" - "strings" - "sync" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/require" -) - -const noType uint16 = math.MaxUint16 - -type Option func(*dns.Server) error - -type Builder struct { - // domain -> op -> type - routes map[string]map[int]map[uint16]dns.Handler - - stringToType map[string]uint16 -} - -func NewServer() *Builder { - stringToType := make(map[string]uint16) - for typ, str := range dns.TypeToString { - stringToType[str] = typ - } - - return &Builder{ - routes: make(map[string]map[int]map[uint16]dns.Handler), - stringToType: stringToType, - } -} - -func (b *Builder) Query(pattern string, handler dns.HandlerFunc) *Builder { - route, err := b.route(pattern, dns.OpcodeQuery, handler) - if err != nil { - panic(err.Error()) - } - - return route -} - -func (b *Builder) Update(pattern string, handler dns.HandlerFunc) *Builder { - route, err := b.route(pattern, dns.OpcodeUpdate, handler) - if err != nil { - panic(err.Error()) - } - - return route -} - -func (b *Builder) route(pattern string, op int, handler dns.HandlerFunc) (*Builder, error) { - parts := strings.Fields(pattern) - - domain := parts[0] - - _, ok := dns.IsDomainName(domain) - if !ok { - return nil, fmt.Errorf("%s: invalid domain: %s", dns.OpcodeToString[op], domain) - } - - if _, ok := b.routes[domain]; !ok { - b.routes[domain] = make(map[int]map[uint16]dns.Handler) - } - - if _, ok := b.routes[domain][op]; !ok { - b.routes[domain][op] = make(map[uint16]dns.Handler) - } - - if _, ok := b.routes[domain][op][noType]; ok { - return nil, fmt.Errorf("%s: a global route already exists for the domain: %s", dns.OpcodeToString[op], domain) - } - - switch len(parts) { - case 1: - if len(b.routes[domain][op]) > 0 { - return nil, fmt.Errorf("%s: global route and specific routes cannot be mixed for the same domain: %s", dns.OpcodeToString[op], domain) - } - - b.routes[domain][op][noType] = handler - - return b, nil - - case 2: - raw := parts[1] - - qType, ok := b.stringToType[raw] - if !ok { - return nil, fmt.Errorf("%s: unknown type: %s", dns.OpcodeToString[op], raw) - } - - if _, ok := b.routes[domain][op][qType]; ok { - return nil, fmt.Errorf("%s: duplicate route: %s", dns.OpcodeToString[op], pattern) - } - - b.routes[domain][op][qType] = handler - - return b, nil - - default: - return nil, fmt.Errorf("%s: invalid pattern: %s", dns.OpcodeToString[op], pattern) - } -} - -func (b *Builder) Build(t *testing.T, options ...Option) net.Addr { - t.Helper() - - mux := dns.NewServeMux() - - server := &dns.Server{ - Addr: "127.0.0.1:0", - Net: "udp", - ReadTimeout: time.Hour, - WriteTimeout: time.Hour, - Handler: mux, - MsgAcceptFunc: func(dh dns.Header) dns.MsgAcceptAction { - // bypass defaultMsgAcceptFunc to allow dynamic update (https://github.com/miekg/dns/pull/830) - return dns.MsgAccept - }, - } - - for _, option := range options { - require.NoError(t, option(server)) - } - - for pattern, ops := range b.routes { - mux.HandleFunc(pattern, func(w dns.ResponseWriter, req *dns.Msg) { - mTypes, ok := ops[req.Opcode] - if !ok { - _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeNotImplemented)) - - return - } - - if h, found := mTypes[noType]; found { - h.ServeDNS(w, req) - - return - } - - // For safety but it doesn't happen. - if len(req.Question) == 0 { - _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeRefused)) - - return - } - - // For safety but it doesn't happen. - if req.Question[0].Qclass != dns.ClassINET { - _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeRefused)) - - return - } - - // Works only for [Query]. - h, ok := mTypes[req.Question[0].Qtype] - if !ok { - _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeNotImplemented)) - - return - } - - h.ServeDNS(w, req) - }) - } - - t.Cleanup(func() { - _ = server.Shutdown() - }) - - waitLock := sync.Mutex{} - waitLock.Lock() - - server.NotifyStartedFunc = waitLock.Unlock - - go func() { - err := server.ListenAndServe() - if err != nil { - t.Log(err) - } - }() - - waitLock.Lock() - - return server.PacketConn.LocalAddr() -} diff --git a/platform/tester/dnsmock/dnsmock_test.go b/platform/tester/dnsmock/dnsmock_test.go deleted file mode 100644 index 77a67a402..000000000 --- a/platform/tester/dnsmock/dnsmock_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package dnsmock - -import ( - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestServer_Query_matchType(t *testing.T) { - addr := NewServer(). - Query("example.com. SOA", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Query_noType(t *testing.T) { - addr := NewServer(). - Query("example.com.", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Query_noMatch_domain(t *testing.T) { - addr := NewServer(). - Query("example.com. SOA", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.org.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeRefused, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeRefused], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Query_noMatch_type(t *testing.T) { - addr := NewServer(). - Query("example.com. SOA", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeTXT) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Query_noMatch_opType(t *testing.T) { - addr := NewServer(). - Query("example.com.", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetUpdate("example.com.") - m.Insert([]dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, - Txt: []string{"foo"}, - }, - }) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Query_unknownType(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: unknown type: ABC", func() { - NewServer(). - Query("example.com. ABC", Noop). - Build(t) - }) -} - -func TestServer_Query_duplicate(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: duplicate route: example.com. SOA", func() { - NewServer(). - Query("example.com. SOA", Noop). - Query("example.com. SOA", Noop). - Build(t) - }) -} - -func TestServer_Query_duplicateGlobal(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: a global route already exists for the domain: example.com.", func() { - NewServer(). - Query("example.com.", Noop). - Query("example.com.", Noop). - Build(t) - }) -} - -func TestServer_Query_mixed(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: global route and specific routes cannot be mixed for the same domain: example.com.", func() { - NewServer(). - Query("example.com. SOA", Noop). - Query("example.com.", Noop). - Build(t) - }) -} - -func TestServer_Query_invalidDomain(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: invalid domain: .example.com.", func() { - NewServer(). - Query(".example.com. SOA", Noop). - Build(t) - }) -} - -func TestServer_Query_invalidPattern(t *testing.T) { - assert.PanicsWithValue(t, "QUERY: invalid pattern: example.com. SOA 13", func() { - NewServer(). - Query("example.com. SOA 13", Noop). - Build(t) - }) -} - -func TestServer_Update(t *testing.T) { - addr := NewServer(). - Update("example.com.", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetUpdate("example.com.") - m.Insert([]dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, - Txt: []string{"foo"}, - }, - }) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Update_noMatch_domain(t *testing.T) { - addr := NewServer(). - Update("example.com.", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetUpdate("example.org.") - m.Insert([]dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{Name: "example.org.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, - Txt: []string{"foo"}, - }, - }) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeRefused, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeRefused], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Update_noMatch_opType(t *testing.T) { - addr := NewServer(). - Update("example.com.", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeTXT) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestServer_Update_duplicate(t *testing.T) { - assert.PanicsWithValue(t, "UPDATE: a global route already exists for the domain: example.com.", func() { - NewServer(). - Update("example.com.", Noop). - Update("example.com.", Noop). - Build(t) - }) -} - -func TestServer_Update_invalidDomain(t *testing.T) { - assert.PanicsWithValue(t, "UPDATE: invalid domain: .example.com.", func() { - NewServer(). - Update(".example.com.", Noop). - Build(t) - }) -} - -func TestServer_Update_invalidPattern(t *testing.T) { - assert.PanicsWithValue(t, "UPDATE: invalid pattern: example.com. SOA 13", func() { - NewServer(). - Update("example.com. SOA 13", Noop). - Build(t) - }) -} diff --git a/platform/tester/dnsmock/handlers.go b/platform/tester/dnsmock/handlers.go deleted file mode 100644 index e1b047318..000000000 --- a/platform/tester/dnsmock/handlers.go +++ /dev/null @@ -1,76 +0,0 @@ -package dnsmock - -import ( - "fmt" - - "github.com/miekg/dns" -) - -func DumpRequest() dns.HandlerFunc { - return func(w dns.ResponseWriter, req *dns.Msg) { - fmt.Println(req) - - Noop(w, req) - } -} - -func SOA(name string) dns.HandlerFunc { - return func(w dns.ResponseWriter, req *dns.Msg) { - if name == "" { - name = req.Question[0].Name - } - - // Handle TLD - base := name - if dns.CountLabel(req.Question[0].Name) == 1 { - base = "nic." + req.Question[0].Name - } - - answer := &dns.SOA{ - Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120}, - Ns: "ns1." + base, - Mbox: "admin." + base, - Serial: 2016022801, - Refresh: 28800, - Retry: 7200, - Expire: 2419200, - Minttl: 1200, - } - - Answer(answer)(w, req) - } -} - -func CNAME(target string) dns.HandlerFunc { - return func(w dns.ResponseWriter, req *dns.Msg) { - answer := &dns.CNAME{ - Hdr: dns.RR_Header{Name: req.Question[0].Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 1}, - Target: dns.Fqdn(target), - } - - Answer(answer)(w, req) - } -} - -func Noop(w dns.ResponseWriter, req *dns.Msg) { - _ = w.WriteMsg(new(dns.Msg).SetReply(req)) -} - -func Error(rcode int) dns.HandlerFunc { - return func(w dns.ResponseWriter, req *dns.Msg) { - _ = w.WriteMsg(new(dns.Msg).SetRcode(req, rcode)) - } -} - -func Answer(answer ...dns.RR) func(w dns.ResponseWriter, req *dns.Msg) { - return func(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg).SetReply(req) - - m.Answer = answer - - err := w.WriteMsg(m) - if err != nil { - panic(err.Error()) - } - } -} diff --git a/platform/tester/dnsmock/handlers_test.go b/platform/tester/dnsmock/handlers_test.go deleted file mode 100644 index 13cdc0e2d..000000000 --- a/platform/tester/dnsmock/handlers_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package dnsmock - -import ( - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSOA_self(t *testing.T) { - addr := NewServer(). - Query("example.com. SOA", SOA("")). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - expectedSOA := []dns.RR{&dns.SOA{ - Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 56}, - Ns: "ns1.example.com.", - Mbox: "admin.example.com.", - Serial: 2016022801, - Refresh: 28800, - Retry: 7200, - Expire: 2419200, - Minttl: 1200, - }} - - require.Equal(t, dns.RcodeSuccess, r.Rcode) - assert.Equal(t, expectedSOA, r.Answer) - assert.Equal(t, m.Question, r.Question) -} - -func TestSOA_differentDomain(t *testing.T) { - addr := NewServer(). - Query("example.com. SOA", SOA("example.org.")). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - - expectedSOA := []dns.RR{&dns.SOA{ - Hdr: dns.RR_Header{Name: "example.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 56}, - Ns: "ns1.example.org.", - Mbox: "admin.example.org.", - Serial: 2016022801, - Refresh: 28800, - Retry: 7200, - Expire: 2419200, - Minttl: 1200, - }} - - assert.Equal(t, expectedSOA, r.Answer) - assert.Equal(t, m.Question, r.Question) -} - -func TestSOA_tld(t *testing.T) { - addr := NewServer(). - Query("com. SOA", SOA("")). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("com.", dns.TypeSOA) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - - expectedSOA := []dns.RR{&dns.SOA{ - Hdr: dns.RR_Header{Name: "com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 48}, - Ns: "ns1.nic.com.", - Mbox: "admin.nic.com.", - Serial: 2016022801, - Refresh: 28800, - Retry: 7200, - Expire: 2419200, - Minttl: 1200, - }} - - assert.Equal(t, expectedSOA, r.Answer) - assert.Equal(t, m.Question, r.Question) -} - -func TestCNAME(t *testing.T) { - addr := NewServer(). - Query("example.com. CNAME", CNAME("example.org.")). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - - expectedCNAME := []dns.RR{&dns.CNAME{ - Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 1, Rdlength: 13}, - Target: "example.org.", - }} - - assert.Equal(t, expectedCNAME, r.Answer) - assert.Equal(t, m.Question, r.Question) -} - -func TestNoop(t *testing.T) { - addr := NewServer(). - Query("example.com. CNAME", Noop). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeSuccess, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} - -func TestError(t *testing.T) { - addr := NewServer(). - Query("example.com. CNAME", Error(dns.RcodeNameError)). - Build(t) - - client := &dns.Client{Timeout: 1 * time.Second} - - m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) - - r, _, err := client.Exchange(m, addr.String()) - require.NoError(t, err) - - require.Equalf(t, dns.RcodeNameError, r.Rcode, - "expected %s, got %s", dns.RcodeToString[dns.RcodeNameError], dns.RcodeToString[r.Rcode]) - assert.Equal(t, m.Question, r.Question) -} diff --git a/platform/tester/env.go b/platform/tester/env.go index a12c32ef8..26788be3b 100644 --- a/platform/tester/env.go +++ b/platform/tester/env.go @@ -21,7 +21,6 @@ type EnvTest struct { // NewEnvTest Creates an EnvTest. func NewEnvTest(keys ...string) *EnvTest { values := make(map[string]string) - for _, key := range keys { value := os.Getenv(key) if value != "" { @@ -40,7 +39,6 @@ func NewEnvTest(keys ...string) *EnvTest { func (e *EnvTest) WithDomain(key string) *EnvTest { e.domainKey = key e.domain = os.Getenv(key) - return e } diff --git a/platform/tester/env_test.go b/platform/tester/env_test.go index 4d9e4e7d1..d5084056f 100644 --- a/platform/tester/env_test.go +++ b/platform/tester/env_test.go @@ -18,7 +18,6 @@ const ( func TestMain(m *testing.M) { exitCode := m.Run() - clearEnv() os.Exit(exitCode) } @@ -40,7 +39,6 @@ func clearEnv() { os.Unsetenv(strings.Split(key, "=")[0]) } } - os.Unsetenv("EXTRA_LEGO_TEST") } @@ -327,7 +325,6 @@ func TestEnvTest(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer clearEnv() - applyEnv(test.envVars) envTest := test.envTestSetup() diff --git a/platform/tester/servermock/builder.go b/platform/tester/servermock/builder.go index b5a9d909b..e3b41e5c3 100644 --- a/platform/tester/servermock/builder.go +++ b/platform/tester/servermock/builder.go @@ -70,15 +70,3 @@ func (b *Builder[T]) Build(t *testing.T) T { return client } - -func (b *Builder[T]) BuildHTTPS(t *testing.T) T { - t.Helper() - - server := httptest.NewTLSServer(b.mux) - t.Cleanup(server.Close) - - client, err := b.clientBuilder(server) - require.NoError(t, err) - - return client -} diff --git a/platform/tester/servermock/link_form.go b/platform/tester/servermock/link_form.go index 581e27d66..e7541cefa 100644 --- a/platform/tester/servermock/link_form.go +++ b/platform/tester/servermock/link_form.go @@ -43,7 +43,6 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if len(form) != len(l.values)+len(l.regexes) { msg := fmt.Sprintf("invalid query parameters, got %v, want %v", req.Form, l.values) http.Error(rw, msg, l.statusCode) - return } } @@ -53,7 +52,6 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if !slices.Equal(v, value) { msg := fmt.Sprintf("invalid %q form value, got %q, want %q", k, value, v) http.Error(rw, msg, l.statusCode) - return } } @@ -63,7 +61,6 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q form value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) - return } } diff --git a/platform/tester/servermock/link_headers.go b/platform/tester/servermock/link_headers.go index 0ca519958..821c737fe 100644 --- a/platform/tester/servermock/link_headers.go +++ b/platform/tester/servermock/link_headers.go @@ -55,7 +55,6 @@ func (l *HeaderLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q header value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) - return } } diff --git a/platform/tester/servermock/link_query.go b/platform/tester/servermock/link_query.go index 14f776515..00d7450ae 100644 --- a/platform/tester/servermock/link_query.go +++ b/platform/tester/servermock/link_query.go @@ -32,7 +32,6 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if len(query) != len(l.values)+len(l.regexes) { msg := fmt.Sprintf("invalid query parameters, got %v, want %v", query, l.values) http.Error(rw, msg, l.statusCode) - return } } @@ -42,7 +41,6 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if p != v { msg := fmt.Sprintf("invalid %q query parameter value, got %q, want %q", k, p, v) http.Error(rw, msg, l.statusCode) - return } } @@ -52,7 +50,6 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q query parameter value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) - return } } diff --git a/platform/tester/servermock/link_request_body.go b/platform/tester/servermock/link_request_body.go index d6b2d9efd..b58c3cc79 100644 --- a/platform/tester/servermock/link_request_body.go +++ b/platform/tester/servermock/link_request_body.go @@ -76,7 +76,6 @@ func (l *RequestBodyLink) Bind(next http.Handler) http.Handler { msg := fmt.Sprintf("%s: request body differences: got: %s, want: %s", req.URL.Path, string(bytes.TrimSpace(body)), string(bytes.TrimSpace(expectedRaw))) http.Error(rw, msg, http.StatusBadRequest) - return } diff --git a/platform/tester/servermock/link_request_body_json.go b/platform/tester/servermock/link_request_body_json.go index ed5a117ba..3dc2f0cfa 100644 --- a/platform/tester/servermock/link_request_body_json.go +++ b/platform/tester/servermock/link_request_body_json.go @@ -90,7 +90,6 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { if err != nil { msg := fmt.Sprintf("%s: the expected request body is not valid JSON: %v", req.URL.Path, err) http.Error(rw, msg, http.StatusBadRequest) - return } @@ -98,14 +97,12 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { if err != nil { msg := fmt.Sprintf("%s: request body is not valid JSON: %v", req.URL.Path, err) http.Error(rw, msg, http.StatusBadRequest) - return } if !cmp.Equal(actual, expected) { msg := fmt.Sprintf("%s: request body differences: %s", req.URL.Path, cmp.Diff(actual, expected)) http.Error(rw, msg, http.StatusBadRequest) - return } diff --git a/platform/wait/wait.go b/platform/wait/wait.go index c66f57446..6ad817b26 100644 --- a/platform/wait/wait.go +++ b/platform/wait/wait.go @@ -1,11 +1,9 @@ package wait import ( - "context" "fmt" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/log" ) @@ -14,25 +12,21 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval) var lastErr error - timeUp := time.After(timeout) - for { select { case <-timeUp: if lastErr == nil { return fmt.Errorf("%s: time limit exceeded", msg) } - return fmt.Errorf("%s: time limit exceeded: last error: %w", msg, lastErr) default: } stop, err := f() if stop { - return err + return nil } - if err != nil { lastErr = err } @@ -40,13 +34,3 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er time.Sleep(interval) } } - -// Retry retries the given operation until it succeeds or the context is canceled. -// Similar to [backoff.Retry] but with a different signature. -func Retry(ctx context.Context, operation func() error, opts ...backoff.RetryOption) error { - _, err := backoff.Retry(ctx, func() (any, error) { - return nil, operation() - }, opts...) - - return err -} diff --git a/platform/wait/wait_test.go b/platform/wait/wait_test.go index 36dbffe69..9722e6f2e 100644 --- a/platform/wait/wait_test.go +++ b/platform/wait/wait_test.go @@ -1,121 +1,26 @@ package wait import ( - "errors" - "sync/atomic" "testing" "time" - - "github.com/stretchr/testify/require" ) -// TODO(ldez): rewrite those tests when upgrading to go1.25 as minimum Go version. - -func TestFor_timeout(t *testing.T) { - var io atomic.Int64 - +func TestForTimeout(t *testing.T) { c := make(chan error) - go func() { - c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { - io.Add(1) - - if io.Load() == 1 { - return false, nil - } - + c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { return false, nil }) }() timeout := time.After(6 * time.Second) - select { case <-timeout: t.Fatal("timeout exceeded") case err := <-c: - require.EqualError(t, err, "test: time limit exceeded") + if err == nil { + t.Errorf("expected timeout error; got %v", err) + } + t.Logf("%v", err) } - - require.EqualValues(t, 3, io.Load()) -} - -func TestFor_timeout_with_error(t *testing.T) { - var io atomic.Int64 - - c := make(chan error) - - go func() { - c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { - io.Add(1) - - // This allows be sure that the latest previous error is returned. - if io.Load() == 1 { - return false, errors.New("oops") - } - - return false, nil - }) - }() - - timeout := time.After(6 * time.Second) - - select { - case <-timeout: - t.Fatal("timeout exceeded") - case err := <-c: - require.EqualError(t, err, "test: time limit exceeded: last error: oops") - } - - require.EqualValues(t, 3, io.Load()) -} - -func TestFor_stop(t *testing.T) { - var io atomic.Int64 - - c := make(chan error) - - go func() { - c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { - io.Add(1) - - return true, nil - }) - }() - - timeout := time.After(6 * time.Second) - - select { - case <-timeout: - t.Fatal("timeout exceeded") - case err := <-c: - require.NoError(t, err) - } - - require.EqualValues(t, 1, io.Load()) -} - -func TestFor_stop_with_error(t *testing.T) { - var io atomic.Int64 - - c := make(chan error) - - go func() { - c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { - io.Add(1) - - return true, errors.New("oops") - }) - }() - - timeout := time.After(6 * time.Second) - - select { - case <-timeout: - t.Fatal("timeout exceeded") - case err := <-c: - require.EqualError(t, err, "oops") - } - - require.EqualValues(t, 1, io.Load()) } diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 8f1f16842..9663a656b 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -114,7 +114,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. -// // Deprecated: use [NewDNSProviderConfig] instead. func NewDNSProviderClient(client acmeDNSClient, store goacmedns.Storage) (*DNSProvider, error) { if client == nil { diff --git a/providers/dns/acmedns/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/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index a3ab59d59..e50c89d56 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -167,11 +167,11 @@ func TestPresent_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - provider := servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.StorageBaseURL = server.URL + config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { + cfg := NewDefaultConfig() + cfg.StorageBaseURL = server.URL - return NewDNSProviderConfig(config) + return cfg, nil }). // Fetch Route("GET /example.com", servermock.Noop().WithStatusCode(http.StatusNotFound)). @@ -179,12 +179,15 @@ func TestPresent_httpStorage(t *testing.T) { Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). Build(t) - client := newMockClient().WithRegisterAccount(egTestAccount) - provider.client = client + p, err := NewDNSProviderConfig(config) + require.NoError(t, err) - err := provider.Present(egDomain, "foo", egKeyAuth) + client := newMockClient().WithRegisterAccount(egTestAccount) + p.client = client + + err = p.Present(egDomain, "foo", egKeyAuth) if test.ExpectedError != nil { - assert.EqualError(t, err, test.ExpectedError.Error()) + assert.Equal(t, test.ExpectedError, err) assert.True(t, client.registerAccountCalled) assert.False(t, client.updateTXTRecordCalled) } else { @@ -219,19 +222,22 @@ func TestRegister_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.Name, func(t *testing.T) { - provider := servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.StorageBaseURL = server.URL + config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { + cfg := NewDefaultConfig() + cfg.StorageBaseURL = server.URL - return NewDNSProviderConfig(config) + return cfg, nil }). // Put Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). Build(t) - provider.client = newMockClient().WithRegisterAccount(egTestAccount) + p, err := NewDNSProviderConfig(config) + require.NoError(t, err) - acc, err := provider.register(t.Context(), egDomain, egFQDN) + p.client = newMockClient().WithRegisterAccount(egTestAccount) + + acc, err := p.register(t.Context(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { diff --git a/providers/dns/acmedns/internal/fixtures/fetch_all.json b/providers/dns/acmedns/internal/fixtures/fetch-all.json similarity index 100% rename from providers/dns/acmedns/internal/fixtures/fetch_all.json rename to providers/dns/acmedns/internal/fixtures/fetch-all.json diff --git a/providers/dns/acmedns/internal/fixtures/fetch-request.json b/providers/dns/acmedns/internal/fixtures/request-body.json similarity index 100% rename from providers/dns/acmedns/internal/fixtures/fetch-request.json rename to providers/dns/acmedns/internal/fixtures/request-body.json diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index 5c166b47f..abc3c0cde 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -58,7 +58,7 @@ func TestHTTPStorage_Fetch_error(t *testing.T) { func TestHTTPStorage_FetchAll(t *testing.T) { storage := mockBuilder(). - Route("GET /", servermock.ResponseFromFixture("fetch_all.json")). + Route("GET /", servermock.ResponseFromFixture("fetch-all.json")). Build(t) account, err := storage.FetchAll(t.Context()) @@ -98,7 +98,7 @@ func TestHTTPStorage_FetchAll_error(t *testing.T) { func TestHTTPStorage_Put(t *testing.T) { storage := mockBuilder(). Route("POST /example.com", nil, - servermock.CheckRequestJSONBodyFromFixture("fetch-request.json")). + servermock.CheckRequestJSONBodyFromFixture("request-body.json")). Build(t) account := goacmedns.Account{ @@ -137,7 +137,7 @@ func TestHTTPStorage_Put_CNAME_created(t *testing.T) { Route("POST /example.com", servermock.Noop(). WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFixture("fetch-request.json")). + servermock.CheckRequestJSONBodyFromFixture("request-body.json")). Build(t) account := goacmedns.Account{ diff --git a/providers/dns/acmedns/mock_test.go b/providers/dns/acmedns/mock_test.go index a09a3ca91..629f25a0c 100644 --- a/providers/dns/acmedns/mock_test.go +++ b/providers/dns/acmedns/mock_test.go @@ -107,7 +107,6 @@ func newMockStorage() *mockStorage { if acct, ok := m.accounts[domain]; ok { return acct, nil } - return goacmedns.Account{}, storage.ErrDomainNotFound } diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go index 0b925de6a..1acd72f61 100644 --- a/providers/dns/active24/active24.go +++ b/providers/dns/active24/active24.go @@ -2,12 +2,13 @@ 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" @@ -29,7 +30,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 +54,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 +78,81 @@ 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 + } + + 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 +161,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..d7d2c5535 100644 --- a/providers/dns/active24/active24_test.go +++ b/providers/dns/active24/active24_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -60,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.client) } else { require.EqualError(t, err, test.expected) } @@ -109,7 +109,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) } @@ -123,7 +124,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +137,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index cdd8e75e0..660098d4a 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -2,13 +2,11 @@ package alidns import ( - "context" "errors" "fmt" "time" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" - "github.com/alibabacloud-go/tea/dara" "github.com/aliyun/credentials-go/credentials" alidns "github.com/go-acme/alidns-20150109/v4/client" "github.com/go-acme/lego/v4/challenge" @@ -27,7 +25,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 +43,6 @@ type Config struct { SecretKey string SecurityToken string RegionID string - Line string PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -76,7 +72,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 { @@ -155,11 +150,9 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - zoneName, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneName, err := d.getHostedZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -169,26 +162,23 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - _, err = alidns.AddDomainRecordWithContext(ctx, d.client, recordRequest, &dara.RuntimeOptions{}) + _, err = alidns.AddDomainRecord(d.client, recordRequest) if err != nil { return fmt.Errorf("alicloud: API call failed: %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) - records, err := d.findTxtRecords(ctx, info.EffectiveFQDN) + records, err := d.findTxtRecords(info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } - _, err = d.getHostedZone(ctx, info.EffectiveFQDN) + _, err = d.getHostedZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -198,16 +188,15 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { RecordId: rec.RecordId, } - _, err = alidns.DeleteDomainRecordWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) + _, err = alidns.DeleteDomainRecord(d.client, request) if err != nil { return fmt.Errorf("alicloud: %w", err) } } - return nil } -func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, error) { +func (d *DNSProvider) getHostedZone(domain string) (string, error) { request := new(alidns.DescribeDomainsRequest) var domains []*alidns.DescribeDomainsResponseBodyDomainsDomain @@ -217,7 +206,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, for { request.SetPageNumber(startPage) - response, err := alidns.DescribeDomainsWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) + response, err := alidns.DescribeDomains(d.client, request) if err != nil { return "", fmt.Errorf("API call failed: %w", err) } @@ -237,7 +226,6 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, } var hostedZone *alidns.DescribeDomainsResponseBodyDomainsDomain - for _, zone := range domains { if ptr.Deref(zone.DomainName) == dns01.UnFqdn(authZone) || ptr.Deref(zone.PunyCode) == dns01.UnFqdn(authZone) { hostedZone = zone @@ -257,22 +245,16 @@ 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) { - zoneName, err := d.getHostedZone(ctx, fqdn) +func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord, error) { + zoneName, err := d.getHostedZone(fqdn) if err != nil { return nil, err } @@ -283,7 +265,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]*alidn var records []*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord - result, err := alidns.DescribeDomainRecordsWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) + result, err := alidns.DescribeDomainRecords(d.client, request) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) } @@ -298,7 +280,6 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]*alidn records = append(records, record) } } - return records, nil } 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/alidns/alidns_test.go b/providers/dns/alidns/alidns_test.go index b1e482d2d..487997813 100644 --- a/providers/dns/alidns/alidns_test.go +++ b/providers/dns/alidns/alidns_test.go @@ -64,7 +64,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -143,7 +142,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -157,7 +155,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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..b1a40ae64 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -11,10 +11,8 @@ 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" ) // Environment variables names. @@ -94,16 +92,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } - identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) - client := internal.NewClient(config.Login) if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, identifier: identifier, @@ -122,20 +116,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 +144,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 +162,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) @@ -177,33 +171,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("allinkl: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } _, 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..af85f8c54 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" ) @@ -62,7 +53,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -131,7 +121,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -145,115 +134,9 @@ func TestLiveCleanUp(t *testing.T) { } 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.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..02a5a2c8f 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,13 @@ 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 +69,13 @@ 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 +89,13 @@ 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 +121,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 +129,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 +136,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..dc55506f2 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,8 +12,7 @@ import ( func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { client := NewIdentifier("user", "secret") - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() + client.authEndpoint = server.URL return client, nil } @@ -27,13 +25,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 +36,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..b5c6ba0d1 100644 --- a/providers/dns/allinkl/internal/types.go +++ b/providers/dns/allinkl/internal/types.go @@ -17,7 +17,6 @@ func (tr Trimmer) Token() (xml.Token, error) { if cd, ok := t.(xml.CharData); ok { t = xml.CharData(bytes.TrimSpace(cd)) } - return t, err } @@ -26,11 +25,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. @@ -55,7 +53,6 @@ func decodeXML[T any](reader io.Reader) (*T, error) { } var result T - err = xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(raw))}).Decode(&result) if err != nil { return nil, fmt.Errorf("decode XML response: %w", err) diff --git a/providers/dns/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.go b/providers/dns/anexia/anexia.go deleted file mode 100644 index 3ce7e2208..000000000 --- a/providers/dns/anexia/anexia.go +++ /dev/null @@ -1,237 +0,0 @@ -// Package anexia implements a DNS provider for solving the DNS-01 challenge using Anexia CloudDNS. -package anexia - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/cenkalti/backoff/v5" - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/anexia/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "ANEXIA_" - - EnvToken = envNamespace + "TOKEN" - EnvAPIURL = envNamespace + "API_URL" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const defaultTTL = 300 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Token string - APIURL string - - TTL int - PropagationTimeout time.Duration - PollingInterval time.Duration - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for Anexia CloudDNS. -// Credentials must be passed in the environment variable: ANEXIA_TOKEN. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvToken) - if err != nil { - return nil, fmt.Errorf("anexia: %w", err) - } - - config := NewDefaultConfig() - config.Token = values[EnvToken] - config.APIURL = env.GetOrFile(EnvAPIURL) - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Anexia CloudDNS. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("anexia: the configuration of the DNS provider is nil") - } - - if config.Token == "" { - return nil, errors.New("anexia: incomplete credentials, missing token") - } - - client, err := internal.NewClient(config.Token) - if err != nil { - return nil, fmt.Errorf("anexia: %w", err) - } - - if config.APIURL != "" { - var err error - - client.BaseURL, err = url.Parse(config.APIURL) - if err != nil { - return nil, fmt.Errorf("anexia: %w", err) - } - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - 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 to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("anexia: could not find zone for domain %q: %w", domain, err) - } - - recordName, err := extractRecordName(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("anexia: %w", err) - } - - zoneName := dns01.UnFqdn(authZone) - - recordReq := internal.Record{ - Name: recordName, - Type: "TXT", - RData: info.Value, - TTL: d.config.TTL, - } - - // Ignores returned zone, because of UUID unstability. - // https://github.com/go-acme/lego/pull/2675#issuecomment-3418678194 - _, err = d.client.CreateRecord(ctx, zoneName, recordReq) - if err != nil { - return fmt.Errorf("anexia: new record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("anexia: could not find zone for domain %q: %w", domain, err) - } - - recordName, err := extractRecordName(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("anexia: %w", err) - } - - recordID, err := d.findRecordID(ctx, dns01.UnFqdn(authZone), recordName, info.Value) - if err != nil { - return fmt.Errorf("anexia: %w", err) - } - - err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("anexia: delete TXT record: %w", err) - } - - return nil -} - -// findRecordID attempts to find the record ID from the zone response. -// If the record is not immediately available in the response, it retries by querying the zone. -func (d *DNSProvider) findRecordID(ctx context.Context, zoneName, recordName, rdata string) (string, error) { - return backoff.Retry(ctx, - func() (string, error) { - currentZone, err := d.client.GetZone(ctx, zoneName) - if err != nil { - return "", backoff.Permanent(fmt.Errorf("get zone: %w", err)) - } - - recordID := findRecordIdentifier(currentZone, recordName, rdata) - if recordID == "" { - return "", fmt.Errorf("get record identifier: %w", err) - } - - return recordID, nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(5*time.Second)), - backoff.WithMaxElapsedTime(300*time.Second), - ) -} - -func findRecordIdentifier(zone *internal.Zone, recordName, rdata string) string { - if len(zone.Revisions) == 0 { - return "" - } - - // Check the first revision (index 0) which should be the current one - - for _, record := range zone.Revisions[0].Records { - if record.Name != recordName || record.Type != "TXT" { - continue - } - - if record.RData == rdata || record.RData == strconv.Quote(rdata) { - return record.Identifier - } - } - - return "" -} - -func extractRecordName(fqdn, authZone string) (string, error) { - if dns01.UnFqdn(fqdn) == dns01.UnFqdn(authZone) { - // "@" for the root domain instead of an empty string. - return "@", nil - } - - return dns01.ExtractSubDomain(fqdn, authZone) -} diff --git a/providers/dns/anexia/anexia.toml b/providers/dns/anexia/anexia.toml deleted file mode 100644 index 332f0b8b1..000000000 --- a/providers/dns/anexia/anexia.toml +++ /dev/null @@ -1,31 +0,0 @@ -Name = "Anexia CloudDNS" -Description = '''''' -URL = "https://www.anexia-it.com/" -Code = "anexia" -Since = "v4.28.0" - -Example = ''' -ANEXIA_TOKEN=xxx \ -lego --dns anexia -d '*.example.com' -d example.com run -''' - -Additional = ''' -## Description - -You need to create an API token in the [Anexia Engine](https://engine.anexia-it.com/). - -The token must have permissions to manage DNS zones and records. -''' - -[Configuration] - [Configuration.Credentials] - ANEXIA_TOKEN = "API token for Anexia Engine" - [Configuration.Additional] - ANEXIA_API_URL = "API endpoint URL (default: https://engine.anexia-it.com)" - ANEXIA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - ANEXIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" - ANEXIA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" - ANEXIA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://engine.anexia-it.com/docs/en/module/clouddns/api" diff --git a/providers/dns/anexia/anexia_test.go b/providers/dns/anexia/anexia_test.go deleted file mode 100644 index 9960c14d1..000000000 --- a/providers/dns/anexia/anexia_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package anexia - -import ( - "net/http/httptest" - "testing" - "time" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvToken, - EnvAPIURL). - WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success with token", - envVars: map[string]string{ - EnvToken: "secret", - }, - }, - { - desc: "missing token", - envVars: map[string]string{ - EnvToken: "", - }, - expected: "anexia: some credentials information are missing: ANEXIA_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - token string - expected string - }{ - { - desc: "success with token", - token: "secret", - }, - { - desc: "missing token", - token: "", - expected: "anexia: incomplete credentials, missing token", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Token = test.token - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - time.Sleep(2 * time.Second) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.Token = "secret" - config.APIURL = server.URL - config.HTTPClient = server.Client() - - return NewDNSProviderConfig(config) - }, - servermock.CheckHeader(). - WithAuthorization("Token secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /api/clouddns/v1/zone.json/example.com/records", - servermock.ResponseFromInternal("create_record.json"), - servermock.CheckHeader(). - WithContentType("application/json; charset=utf-8"), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/clouddns/v1/zone.json/example.com", - servermock.ResponseFromInternal("get_zone.json")). - Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", - servermock.Noop()). - Build(t) - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/anexia/internal/client.go b/providers/dns/anexia/internal/client.go deleted file mode 100644 index 1a4159be0..000000000 --- a/providers/dns/anexia/internal/client.go +++ /dev/null @@ -1,158 +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://engine.anexia-it.com" - -// Client the Anexia CloudDNS API client. -type Client struct { - token string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(token string) (*Client, error) { - if token == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - token: token, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) CreateRecord(ctx context.Context, zoneName string, record Record) (*Zone, error) { - endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return nil, err - } - - var zone Zone - - err = c.do(req, &zone) - if err != nil { - return nil, err - } - - return &zone, nil -} - -func (c *Client) DeleteRecord(ctx context.Context, zoneName, recordID string) error { - endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records", recordID) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) GetZone(ctx context.Context, zoneName string) (*Zone, error) { - endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var zone Zone - - err = c.do(req, &zone) - if err != nil { - return nil, err - } - - return &zone, nil -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.token)) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json; charset=utf-8") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/anexia/internal/client_test.go b/providers/dns/anexia/internal/client_test.go deleted file mode 100644 index be33d6f88..000000000 --- a/providers/dns/anexia/internal/client_test.go +++ /dev/null @@ -1,133 +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(). - WithAuthorization("Token secret"), - ) -} - -func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /api/clouddns/v1/zone.json/example.com/records", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckHeader(). - WithContentType("application/json; charset=utf-8"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := Record{ - Name: "_acme-challenge", - RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 300, - Type: "TXT", - } - - zone, err := client.CreateRecord(t.Context(), "example.com", record) - require.NoError(t, err) - - expected := &Zone{ - Name: "example.com", - TTL: 86400, - ZoneName: "example.com", - Revisions: []Revision{{ - Identifier: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - Records: []Record{{ - Identifier: "12345678-1234-1234-1234-123456789abc", - Name: "_acme-challenge", - RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - TTL: 300, - Type: "TXT", - }}, - State: "deployed", - }}, - } - - assert.Equal(t, expected, zone) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", - servermock.Noop()). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", "12345678-1234-1234-1234-123456789abc") - require.NoError(t, err) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", "12345678-1234-1234-1234-123456789abc") - require.EqualError(t, err, "401: Unauthorized") -} - -func TestClient_GetZone(t *testing.T) { - client := mockBuilder(). - Route("GET /api/clouddns/v1/zone.json/example.com", - servermock.ResponseFromFixture("get_zone.json")). - Build(t) - - zone, err := client.GetZone(t.Context(), "example.com") - require.NoError(t, err) - - expected := &Zone{ - Identifier: "fdb355ffd07c48aba3d4f6bf6a116296", - Name: "example.com", - TTL: 3600, - ZoneName: "", - Revisions: []Revision{{ - Identifier: "eeed7e08-f1ad-442b-9e75-369a0958c7d8", - Records: []Record{ - { - Identifier: "5ced498b-c89d-4487-824d-c03ded84f849", - Immutable: true, - Name: "@", - RData: "acns02.xaas.systems.", - Region: "9a1609af9dae4ce1a4ef63f51d305321", - TTL: 3600, - Type: "NS", - }, - { - Identifier: "12345678-1234-1234-1234-123456789abc", - Immutable: false, - Name: "_acme-challenge", - RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - Region: "", - TTL: 300, - Type: "TXT", - }, - }, - State: "active", - }}, - } - - assert.Equal(t, expected, zone) -} diff --git a/providers/dns/anexia/internal/fixtures/create_record-request.json b/providers/dns/anexia/internal/fixtures/create_record-request.json deleted file mode 100644 index e82add260..000000000 --- a/providers/dns/anexia/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "_acme-challenge", - "type": "TXT", - "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "region": "", - "ttl": 300 -} diff --git a/providers/dns/anexia/internal/fixtures/create_record.json b/providers/dns/anexia/internal/fixtures/create_record.json deleted file mode 100644 index 8c4f2c149..000000000 --- a/providers/dns/anexia/internal/fixtures/create_record.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "example.com", - "zone_name": "example.com", - "master": true, - "dnssec_mode": "managed", - "admin_email": "admin@example.com", - "refresh": 10800, - "retry": 3600, - "expire": 604800, - "ttl": 86400, - "customer": "ANX12345", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "published_at": "0001-01-01T00:00:00Z", - "is_editable": true, - "validation_level": 0, - "deployment_level": 0, - "revisions": [ - { - "created_at": "0001-01-01T00:00:00Z", - "identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "modified_at": "0001-01-01T00:00:00Z", - "records": [ - { - "identifier": "12345678-1234-1234-1234-123456789abc", - "immutable": false, - "name": "_acme-challenge", - "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "region": "", - "ttl": 300, - "type": "TXT" - } - ], - "serial": 1, - "state": "deployed" - } - ] -} diff --git a/providers/dns/anexia/internal/fixtures/create_record_incomplete.json b/providers/dns/anexia/internal/fixtures/create_record_incomplete.json deleted file mode 100644 index 0515fcde3..000000000 --- a/providers/dns/anexia/internal/fixtures/create_record_incomplete.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "example.com", - "zone_name": "example.com", - "master": true, - "dnssec_mode": "managed", - "admin_email": "admin@example.com", - "refresh": 10800, - "retry": 3600, - "expire": 604800, - "ttl": 86400, - "customer": "ANX12345", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "published_at": "0001-01-01T00:00:00Z", - "is_editable": true, - "validation_level": 0, - "deployment_level": 0, - "revisions": [ - { - "created_at": "0001-01-01T00:00:00Z", - "identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "modified_at": "0001-01-01T00:00:00Z", - "records": [ - { - "immutable": false, - "name": "_acme-challenge", - "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "region": "", - "ttl": 300, - "type": "TXT" - } - ], - "serial": 1, - "state": "deployed" - } - ] -} diff --git a/providers/dns/anexia/internal/fixtures/error.json b/providers/dns/anexia/internal/fixtures/error.json deleted file mode 100644 index afed571fa..000000000 --- a/providers/dns/anexia/internal/fixtures/error.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": 401, - "message": "Unauthorized" - } -} diff --git a/providers/dns/anexia/internal/fixtures/get_zone.json b/providers/dns/anexia/internal/fixtures/get_zone.json deleted file mode 100644 index 6e54594ff..000000000 --- a/providers/dns/anexia/internal/fixtures/get_zone.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "identifier": "fdb355ffd07c48aba3d4f6bf6a116296", - "admin_email": "admin@example.com", - "created_at": "2019-02-06T10:02:07.000Z", - "current_revision": "eeed7e08-f1ad-442b-9e75-369a0958c7d8", - "deployment_level": 100, - "dns_servers": [ - { - "server": "acns01.xaas.systems", - "alias": null - }, - { - "server": "acns04.xaas.systems", - "alias": null - }, - { - "server": "acns02.xaas.systems", - "alias": null - }, - { - "server": "acns03.xaas.systems", - "alias": null - }, - { - "server": "acns05.xaas.systems", - "alias": null - } - ], - "dnsCluster": null, - "dnssec_ksk": null, - "dnssec_mode": "unvalidated", - "dnssec_sig_expires_at": null, - "dnssec_zsk": null, - "expire": 604800, - "inherit_ns_from": null, - "nameserver_set": null, - "master": true, - "master_ns": "acns02.xaas.systems.", - "name": "example.com", - "notify_allowed_ips": [ - "127.0.0.1" - ], - "published_at": "2023-06-20T08:41:06.000Z", - "refresh": 14400, - "revisions": [ - { - "created_at": "2023-06-20T08:41:06.000000Z", - "identifier": "eeed7e08-f1ad-442b-9e75-369a0958c7d8", - "modified_at": "2023-06-20T08:41:06.000000Z", - "records": [ - { - "identifier": "5ced498b-c89d-4487-824d-c03ded84f849", - "immutable": true, - "name": "@", - "rdata": "acns02.xaas.systems.", - "region": "9a1609af9dae4ce1a4ef63f51d305321", - "ttl": 3600, - "type": "NS", - "options": null - }, - { - "identifier": "12345678-1234-1234-1234-123456789abc", - "immutable": false, - "name": "_acme-challenge", - "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "region": "", - "ttl": 300, - "Type": "TXT" - } - ], - "serial": 14, - "state": "active" - } - ], - "retry": 3600, - "ttl": 3600, - "updated_at": "2020-06-04T18:34:22.000Z", - "validation_level": 100, - "whitelabel_config": null, - "is_editable": true, - "deploy_zone": "49459f420f614eb2a979fc7e961f83e6" -} diff --git a/providers/dns/anexia/internal/types.go b/providers/dns/anexia/internal/types.go deleted file mode 100644 index f5546ca98..000000000 --- a/providers/dns/anexia/internal/types.go +++ /dev/null @@ -1,38 +0,0 @@ -package internal - -import "fmt" - -type APIError struct { - Details struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"error"` -} - -func (a *APIError) Error() string { - return fmt.Sprintf("%d: %s", a.Details.Code, a.Details.Message) -} - -type Zone struct { - Identifier string `json:"identifier,omitempty"` - Name string `json:"name,omitempty"` - TTL int `json:"ttl,omitempty"` - ZoneName string `json:"zone_name,omitempty"` - Revisions []Revision `json:"revisions,omitempty"` -} - -type Revision struct { - Identifier string `json:"identifier,omitempty"` - Records []Record `json:"records,omitempty"` - State string `json:"state,omitempty"` -} - -type Record struct { - Identifier string `json:"identifier,omitempty"` - Immutable bool `json:"immutable,omitempty"` - Name string `json:"name,omitempty"` - RData string `json:"rdata,omitempty"` - Region string `json:"region"` - TTL int `json:"ttl,omitempty"` - Type string `json:"type,omitempty"` -} diff --git a/providers/dns/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.go b/providers/dns/arvancloud/arvancloud.go index ed1d5ff7a..3dd4eee70 100644 --- a/providers/dns/arvancloud/arvancloud.go +++ b/providers/dns/arvancloud/arvancloud.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/arvancloud/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -96,8 +95,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -167,7 +164,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("arvancloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/arvancloud/arvancloud.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/arvancloud/arvancloud_test.go b/providers/dns/arvancloud/arvancloud_test.go index 24013c437..c31edf021 100644 --- a/providers/dns/arvancloud/arvancloud_test.go +++ b/providers/dns/arvancloud/arvancloud_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -105,7 +104,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,7 +117,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/arvancloud/internal/client.go b/providers/dns/arvancloud/internal/client.go index b447d97c4..3caff392a 100644 --- a/providers/dns/arvancloud/internal/client.go +++ b/providers/dns/arvancloud/internal/client.go @@ -70,7 +70,6 @@ func (c *Client) getRecords(ctx context.Context, domain, search string) ([]DNSRe } response := &apiResponse[[]DNSRecord]{} - err = c.do(req, http.StatusOK, response) if err != nil { return nil, fmt.Errorf("could not get records %s: Domain: %s: %w", search, domain, err) @@ -90,7 +89,6 @@ func (c *Client) CreateRecord(ctx context.Context, domain string, record DNSReco } response := &apiResponse[*DNSRecord]{} - err = c.do(req, http.StatusCreated, response) if err != nil { return nil, fmt.Errorf("could not create record; Domain: %s: %w", domain, err) diff --git a/providers/dns/arvancloud/internal/client_test.go b/providers/dns/arvancloud/internal/client_test.go index 183a8acfd..d13bc6f34 100644 --- a/providers/dns/arvancloud/internal/client_test.go +++ b/providers/dns/arvancloud/internal/client_test.go @@ -81,10 +81,8 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { const apiKey = "myKeyC" - const ( - domain = "example.com" - recordID = "recordId" - ) + const domain = "example.com" + const recordID = "recordId" client := mockBuilder(apiKey). Route("DELETE /cdn/4.0/domains/"+domain+"/dns-records/"+recordID, nil). diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 50d2fbc25..8a497ffa4 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -10,8 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/miekg/dns" "github.com/nrdcg/auroradns" ) @@ -53,11 +51,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. @@ -96,7 +93,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("aurora: %w", err) } - client, err := auroradns.NewClient(clientdebug.Wrap(tr.Client()), auroradns.WithBaseURL(config.BaseURL)) + client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL)) if err != nil { return nil, fmt.Errorf("aurora: %w", err) } @@ -164,7 +161,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("aurora: unknown recordID for %q", info.EffectiveFQDN) } - authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("aurora: could not find zone for domain %q: %w", domain, err) } 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/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go index 8a9835d9c..1619ee586 100644 --- a/providers/dns/auroradns/auroradns_test.go +++ b/providers/dns/auroradns/auroradns_test.go @@ -71,7 +71,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -161,7 +160,7 @@ func TestDNSProvider_Present(t *testing.T) { Build(t) err := provider.Present("example.com", "", "foobar") - require.NoError(t, err) + require.NoError(t, err, "fail to create TXT record") } func TestDNSProvider_CleanUp(t *testing.T) { @@ -186,8 +185,8 @@ func TestDNSProvider_CleanUp(t *testing.T) { Build(t) err := provider.Present("example.com", "", "foobar") - require.NoError(t, err) + require.NoError(t, err, "fail to create TXT record") err = provider.CleanUp("example.com", "", "foobar") - require.NoError(t, err) + require.NoError(t, err, "fail to remove TXT record") } diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go index 8a9361bc0..61f3005f1 100644 --- a/providers/dns/autodns/autodns.go +++ b/providers/dns/autodns/autodns.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/autodns/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -106,8 +105,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -128,9 +125,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 +144,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/autodns_test.go b/providers/dns/autodns/autodns_test.go index 632d24705..bc9f3067e 100644 --- a/providers/dns/autodns/autodns_test.go +++ b/providers/dns/autodns/autodns_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -132,7 +131,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -146,7 +144,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/autodns/internal/client.go b/providers/dns/autodns/internal/client.go index d92490a60..1fc9589ea 100644 --- a/providers/dns/autodns/internal/client.go +++ b/providers/dns/autodns/internal/client.go @@ -43,22 +43,23 @@ 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 +67,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 +87,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 +130,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.go b/providers/dns/axelname/axelname.go index 96d26236e..033ccc92b 100644 --- a/providers/dns/axelname/axelname.go +++ b/providers/dns/axelname/axelname.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/axelname/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -85,8 +84,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/axelname/axelname_test.go b/providers/dns/axelname/axelname_test.go index 1a8bac971..52c4f38b9 100644 --- a/providers/dns/axelname/axelname_test.go +++ b/providers/dns/axelname/axelname_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,7 +120,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +133,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/axelname/internal/client.go b/providers/dns/axelname/internal/client.go index f2defec87..f6cf079e6 100644 --- a/providers/dns/axelname/internal/client.go +++ b/providers/dns/axelname/internal/client.go @@ -174,7 +174,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 5584ece0b..b319e1779 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -6,12 +6,12 @@ import ( "errors" "fmt" "net/http" + "sync" "time" "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -55,6 +55,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. @@ -89,13 +92,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { clientConfig.HTTPClient = config.HTTPClient } - clientConfig.HTTPClient = clientdebug.Wrap(clientConfig.HTTPClient) - client := idns.NewAPIClient(clientConfig) return &DNSProvider{ - config: config, - client: client, + config: config, + client: client, + recordIDs: make(map[string]int32), }, nil } @@ -132,7 +134,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record.SetTtl(int32(d.config.TTL)) var resp *idns.PostOrPutRecordResponse - if existingRecord != nil { // Update existing record by adding the new value to the existing ones record.SetAnswersList(append(existingRecord.GetAnswersList(), info.Value)) @@ -156,6 +157,11 @@ 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 +181,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) @@ -187,7 +200,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { currentAnswers := existingRecord.GetAnswersList() var updatedAnswers []string - for _, answer := range currentAnswers { if answer != info.Value { updatedAnswers = append(updatedAnswers, answer) diff --git a/providers/dns/azion/azion.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/azion/azion_test.go b/providers/dns/azion/azion_test.go index 517594cdc..b3b553114 100644 --- a/providers/dns/azion/azion_test.go +++ b/providers/dns/azion/azion_test.go @@ -40,7 +40,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -100,7 +99,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -114,7 +112,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azure/azure.go b/providers/dns/azure/azure.go index 8bfc6cfe1..5702acd8a 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) @@ -92,7 +89,6 @@ type DNSProvider struct { // If the credentials are _not_ set via the environment, // then it will attempt to get a bearer token via the instance metadata service. // see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42 -// // Deprecated: use azuredns instead. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() @@ -100,7 +96,6 @@ func NewDNSProvider() (*DNSProvider, error) { environmentName := env.GetOrFile(EnvEnvironment) if environmentName != "" { var environment aazure.Environment - switch environmentName { case "china": environment = aazure.ChinaCloud @@ -129,25 +124,12 @@ func NewDNSProvider() (*DNSProvider, error) { } // NewDNSProviderConfig return a DNSProvider instance configured for Azure. -// // Deprecated: use azuredns instead. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { 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} } @@ -166,7 +148,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if subsID == "" { return nil, errors.New("azure: SubscriptionID is missing") } - config.SubscriptionID = subsID } @@ -179,7 +160,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if resGroup == "" { return nil, errors.New("azure: ResourceGroup is missing") } - config.ResourceGroup = resGroup } diff --git a/providers/dns/azure/azure_test.go b/providers/dns/azure/azure_test.go index c4fec4359..496168362 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, @@ -55,11 +54,8 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() - test.envVars[EnvLegoAzureBypassDeprecation] = "true" - envTest.Apply(test.envVars) p, err := NewDNSProvider() @@ -143,11 +139,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() @@ -167,7 +158,6 @@ func TestNewDNSProviderConfig(t *testing.T) { } else { mux.HandleFunc("/", test.handler) } - config.MetadataEndpoint = server.URL p, err := NewDNSProviderConfig(config) @@ -196,7 +186,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -210,7 +199,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azure/private.go b/providers/dns/azure/private.go index f7c6a75b7..d6c9fc7bd 100644 --- a/providers/dns/azure/private.go +++ b/providers/dns/azure/private.go @@ -54,7 +54,6 @@ func (d *dnsProviderPrivate) Present(domain, token, keyAuth string) error { // Construct unique TXT records using map uniqRecords := map[string]struct{}{info.Value: {}} - if rset.RecordSetProperties != nil && rset.TxtRecords != nil { for _, txtRecord := range *rset.TxtRecords { // Assume Value doesn't contain multiple strings @@ -82,7 +81,6 @@ func (d *dnsProviderPrivate) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } - return nil } @@ -108,7 +106,6 @@ func (d *dnsProviderPrivate) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } - return nil } diff --git a/providers/dns/azure/public.go b/providers/dns/azure/public.go index 194956c9c..8e6fa182a 100644 --- a/providers/dns/azure/public.go +++ b/providers/dns/azure/public.go @@ -54,7 +54,6 @@ func (d *dnsProviderPublic) Present(domain, token, keyAuth string) error { // Construct unique TXT records using map uniqRecords := map[string]struct{}{info.Value: {}} - if rset.RecordSetProperties != nil && rset.TxtRecords != nil { for _, txtRecord := range *rset.TxtRecords { // Assume Value doesn't contain multiple strings @@ -82,7 +81,6 @@ func (d *dnsProviderPublic) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } - return nil } @@ -108,7 +106,6 @@ func (d *dnsProviderPublic) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } - return nil } diff --git a/providers/dns/azuredns/azuredns.go b/providers/dns/azuredns/azuredns.go index b8effadea..860d19691 100644 --- a/providers/dns/azuredns/azuredns.go +++ b/providers/dns/azuredns/azuredns.go @@ -3,15 +3,20 @@ package azuredns import ( + "context" "errors" "fmt" "net/http" + "strings" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -28,21 +33,10 @@ const ( EnvClientID = envNamespace + "CLIENT_ID" EnvClientSecret = envNamespace + "CLIENT_SECRET" - EnvOIDCToken = envNamespace + "OIDC_TOKEN" - EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH" - EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL" - EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL" - altEnvArmOIDCRequestURL = "ARM_OIDC_REQUEST_URL" - EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN" - EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" - altEnvArmOIDCRequestToken = "ARM_OIDC_REQUEST_TOKEN" - - EnvServiceConnectionID = envNamespace + "SERVICE_CONNECTION_ID" - altEnvServiceConnectionID = "SERVICE_CONNECTION_ID" - altEnvArmAdoPipelineServiceConnectionID = "ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID" - altEnvArmOIDCAzureServiceConnectionID = "ARM_OIDC_AZURE_SERVICE_CONNECTION_ID" - EnvSystemAccessToken = envNamespace + "SYSTEM_ACCESS_TOKEN" - altEnvSystemAccessToken = "SYSTEM_ACCESSTOKEN" + EnvOIDCToken = envNamespace + "OIDC_TOKEN" + EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH" + EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL" + EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN" EnvAuthMethod = envNamespace + "AUTH_METHOD" EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT" @@ -52,6 +46,9 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + + EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL" + EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -76,9 +73,6 @@ type Config struct { OIDCRequestURL string OIDCRequestToken string - ServiceConnectionID string - SystemAccessToken string - AuthMethod string AuthMSITimeout time.Duration @@ -140,22 +134,13 @@ func NewDNSProvider() (*DNSProvider, error) { config.ServiceDiscoveryFilter = env.GetOrFile(EnvServiceDiscoveryFilter) oidcValues, _ := env.GetWithFallback( - []string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL, altEnvArmOIDCRequestURL}, - []string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken, altEnvArmOIDCRequestToken}, + []string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL}, + []string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken}, ) config.OIDCRequestURL = oidcValues[EnvOIDCRequestURL] config.OIDCRequestToken = oidcValues[EnvOIDCRequestToken] - // https://registry.terraform.io/providers/hashicorp/Azurerm/latest/docs/guides/service_principal_oidc - pipelineValues, _ := env.GetWithFallback( - []string{EnvServiceConnectionID, altEnvServiceConnectionID, altEnvArmAdoPipelineServiceConnectionID, altEnvArmOIDCAzureServiceConnectionID}, - []string{EnvSystemAccessToken, altEnvArmOIDCRequestToken, altEnvSystemAccessToken}, - ) - - config.ServiceConnectionID = pipelineValues[EnvServiceConnectionID] - config.SystemAccessToken = pipelineValues[EnvSystemAccessToken] - config.AuthMethod = env.GetOrFile(EnvAuthMethod) config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second) @@ -172,8 +157,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.HTTPClient = &http.Client{Timeout: 5 * time.Second} } - config.HTTPClient = clientdebug.Wrap(config.HTTPClient) - credentials, err := getCredentials(config) if err != nil { return nil, fmt.Errorf("azuredns: Unable to retrieve valid credentials: %w", err) @@ -210,3 +193,88 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return d.provider.CleanUp(domain, token, keyAuth) } + +func getCredentials(config *Config) (azcore.TokenCredential, error) { + clientOptions := azcore.ClientOptions{Cloud: config.Environment} + + switch strings.ToLower(config.AuthMethod) { + case "env": + if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { + return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, + &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions}) + } + + return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions}) + + case "wli": + return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions}) + + case "msi": + cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions}) + if err != nil { + return nil, err + } + + return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil + + case "cli": + var credOptions *azidentity.AzureCLICredentialOptions + if config.TenantID != "" { + credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID} + } + return azidentity.NewAzureCLICredential(credOptions) + + case "oidc": + err := checkOIDCConfig(config) + if err != nil { + return nil, err + } + + return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions}) + + default: + return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions}) + } +} + +// timeoutTokenCredential wraps a TokenCredential to add a timeout. +type timeoutTokenCredential struct { + cred azcore.TokenCredential + timeout time.Duration +} + +// GetToken implements the azcore.TokenCredential interface. +func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { + if w.timeout <= 0 { + return w.cred.GetToken(ctx, opts) + } + + ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout) + defer cancel() + + tk, err := w.cred.GetToken(ctxTimeout, opts) + if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) { + return tk, azidentity.NewCredentialUnavailableError("managed identity timed out") + } + + w.timeout = 0 + + return tk, err +} + +func getZoneName(config *Config, fqdn string) (string, error) { + if config.ZoneName != "" { + return config.ZoneName, nil + } + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) + } + + if authZone == "" { + return "", errors.New("empty zone name") + } + + return authZone, nil +} diff --git a/providers/dns/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index 7c800ce7e..8d14105cb 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 ''' @@ -174,10 +174,6 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider. It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`. -### Azure DevOps Pipelines - -It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`. - ''' [Configuration] diff --git a/providers/dns/azuredns/azuredns_test.go b/providers/dns/azuredns/azuredns_test.go index 594a0d6a3..7ddb4de45 100644 --- a/providers/dns/azuredns/azuredns_test.go +++ b/providers/dns/azuredns/azuredns_test.go @@ -35,7 +35,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -62,7 +61,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -76,7 +74,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azuredns/credentials.go b/providers/dns/azuredns/credentials.go deleted file mode 100644 index a38b3f7dd..000000000 --- a/providers/dns/azuredns/credentials.go +++ /dev/null @@ -1,136 +0,0 @@ -package azuredns - -import ( - "context" - "errors" - "fmt" - "strings" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/go-acme/lego/v4/challenge/dns01" -) - -const ( - authMethodEnv = "env" - authMethodWLI = "wli" - authMethodMSI = "msi" - authMethodCLI = "cli" - authMethodOIDC = "oidc" - authMethodPipeline = "pipeline" -) - -//nolint:gocyclo // The complexity is related to the number of possible configurations. -func getCredentials(config *Config) (azcore.TokenCredential, error) { - clientOptions := azcore.ClientOptions{Cloud: config.Environment} - - switch strings.ToLower(config.AuthMethod) { - case authMethodEnv: - if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { - return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, - &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions}) - } - - return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions}) - - case authMethodWLI: - return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions}) - - case authMethodMSI: - cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions}) - if err != nil { - return nil, err - } - - return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil - - case authMethodCLI: - var credOptions *azidentity.AzureCLICredentialOptions - if config.TenantID != "" { - credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID} - } - - return azidentity.NewAzureCLICredential(credOptions) - - case authMethodOIDC: - err := checkOIDCConfig(config) - if err != nil { - return nil, err - } - - return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions}) - - case authMethodPipeline: - err := checkPipelineConfig(config) - if err != nil { - return nil, err - } - - // Uses the env var `SYSTEM_OIDCREQUESTURI`, - // but the constant is not exported, - // and there is no way to set it programmatically. - // https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L22 - // https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L79 - - return azidentity.NewAzurePipelinesCredential(config.TenantID, config.ClientID, config.ServiceConnectionID, config.SystemAccessToken, &azidentity.AzurePipelinesCredentialOptions{ClientOptions: clientOptions}) - - default: - return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions}) - } -} - -// timeoutTokenCredential wraps a TokenCredential to add a timeout. -type timeoutTokenCredential struct { - cred azcore.TokenCredential - timeout time.Duration -} - -// GetToken implements the azcore.TokenCredential interface. -func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { - if w.timeout <= 0 { - return w.cred.GetToken(ctx, opts) - } - - ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout) - defer cancel() - - tk, err := w.cred.GetToken(ctxTimeout, opts) - if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) { - return tk, azidentity.NewCredentialUnavailableError("managed identity timed out") - } - - w.timeout = 0 - - return tk, err -} - -func getZoneName(config *Config, fqdn string) (string, error) { - if config.ZoneName != "" { - return config.ZoneName, nil - } - - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) - } - - if authZone == "" { - return "", errors.New("empty zone name") - } - - return authZone, nil -} - -func checkPipelineConfig(config *Config) error { - if config.ServiceConnectionID == "" { - return errors.New("azuredns: ServiceConnectionID is missing") - } - - if config.SystemAccessToken == "" { - return errors.New("azuredns: SystemAccessToken is missing") - } - - return nil -} diff --git a/providers/dns/azuredns/private.go b/providers/dns/azuredns/private.go index 43b39ed14..24fb3d5ee 100644 --- a/providers/dns/azuredns/private.go +++ b/providers/dns/azuredns/private.go @@ -181,7 +181,6 @@ func (c privateZoneClient) Delete(ctx context.Context, subDomain string) (armpri func privateUniqueRecords(recordSet armprivatedns.RecordSet, value string) map[string]struct{} { uniqRecords := map[string]struct{}{value: {}} - if recordSet.Properties != nil && recordSet.Properties.TxtRecords != nil { for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings diff --git a/providers/dns/azuredns/public.go b/providers/dns/azuredns/public.go index 79b6e783f..f7e46150d 100644 --- a/providers/dns/azuredns/public.go +++ b/providers/dns/azuredns/public.go @@ -179,7 +179,6 @@ func (c publicZoneClient) Delete(ctx context.Context, subDomain string) (armdns. func publicUniqueRecords(recordSet armdns.RecordSet, value string) map[string]struct{} { uniqRecords := map[string]struct{}{value: {}} - if recordSet.Properties != nil && recordSet.Properties.TxtRecords != nil { for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings diff --git a/providers/dns/azuredns/servicediscovery.go b/providers/dns/azuredns/servicediscovery.go index 50a41da37..882e19241 100644 --- a/providers/dns/azuredns/servicediscovery.go +++ b/providers/dns/azuredns/servicediscovery.go @@ -46,7 +46,6 @@ func discoverDNSZones(ctx context.Context, config *Config, credentials azcore.To } zones := map[string]ServiceDiscoveryZone{} - for { // create the query request request := armresourcegraph.QueryRequest{ diff --git a/providers/dns/baiducloud/baiducloud.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/baiducloud/baiducloud_test.go b/providers/dns/baiducloud/baiducloud_test.go index 483bfaf5e..3cc411323 100644 --- a/providers/dns/baiducloud/baiducloud_test.go +++ b/providers/dns/baiducloud/baiducloud_test.go @@ -48,7 +48,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/beget/beget.go b/providers/dns/beget/beget.go deleted file mode 100644 index d4449deb8..000000000 --- a/providers/dns/beget/beget.go +++ /dev/null @@ -1,164 +0,0 @@ -// Package beget implements a DNS provider for solving the DNS-01 challenge using beget.com DNS. -package beget - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/beget/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "BEGET_" - - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - Username string - Password string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 300), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for beget.com. -// Credentials must be passed in the environment variables: -// BEGET_USERNAME and BEGET_PASSWORD. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsername, EnvPassword) - if err != nil { - return nil, fmt.Errorf("beget: %w", err) - } - - config := NewDefaultConfig() - config.Username = values[EnvUsername] - config.Password = values[EnvPassword] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for beget.com. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("beget: the configuration of the DNS provider is nil") - } - - if config.Username == "" || config.Password == "" { - return nil, errors.New("beget: incomplete credentials, missing username and/or password") - } - - client := internal.NewClient(config.Username, config.Password) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - 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 { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - records, err := d.client.GetTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("beget: get TXT records: %w", err) - } - - records = append(records, internal.Record{ - Value: info.Value, - Data: "", // NOTE: there are 2 fields in the API for the same thing. - Priority: 10, - TTL: d.config.TTL, - }) - - err = d.client.ChangeTXTRecord(ctx, dns01.UnFqdn(info.EffectiveFQDN), records) - if err != nil { - return fmt.Errorf("beget: failed to create TXT records [domain: %s]: %w", - dns01.UnFqdn(info.EffectiveFQDN), err) - } - - 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) - - records, err := d.client.GetTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("beget: get TXT records: %w", err) - } - - if len(records) == 0 { - return nil - } - - var updatedRecords []internal.Record - - for _, record := range records { - if record.Data == info.Value { - continue - } - - updatedRecords = append(updatedRecords, record) - } - - err = d.client.ChangeTXTRecord(ctx, dns01.UnFqdn(info.EffectiveFQDN), updatedRecords) - if err != nil { - return fmt.Errorf("beget: failed to remove TXT records [domain: %s]: %w", - dns01.UnFqdn(info.EffectiveFQDN), err) - } - - return nil -} diff --git a/providers/dns/beget/beget.toml b/providers/dns/beget/beget.toml deleted file mode 100644 index 4ed26d850..000000000 --- a/providers/dns/beget/beget.toml +++ /dev/null @@ -1,24 +0,0 @@ -Name = "Beget.com" -Description = '''''' -URL = "https://beget.com/" -Code = "beget" -Since = "v4.27.0" - -Example = ''' -BEGET_USERNAME=xxxxxx \ -BEGET_PASSWORD=yyyyyy \ -lego --dns beget -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - BEGET_USERNAME = "API username" - BEGET_PASSWORD = "API password" - [Configuration.Additional] - BEGET_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" - BEGET_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" - BEGET_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - BEGET_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://beget.com/ru/kb/api/funkczii-upravleniya-dns" diff --git a/providers/dns/beget/beget_test.go b/providers/dns/beget/beget_test.go deleted file mode 100644 index 3cfb3c0b4..000000000 --- a/providers/dns/beget/beget_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package beget - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvUsername, EnvPassword).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvUsername: "123", - EnvPassword: "456", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvUsername: "", - EnvPassword: "", - }, - expected: "beget: some credentials information are missing: BEGET_USERNAME,BEGET_PASSWORD", - }, - { - desc: "missing username", - envVars: map[string]string{ - EnvUsername: "", - EnvPassword: "456", - }, - expected: "beget: some credentials information are missing: BEGET_USERNAME", - }, - { - desc: "missing password", - envVars: map[string]string{ - EnvUsername: "123", - EnvPassword: "", - }, - expected: "beget: some credentials information are missing: BEGET_PASSWORD", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - username string - password string - expected string - }{ - { - desc: "success", - username: "123", - password: "456", - }, - { - desc: "missing credentials", - username: "", - password: "", - expected: "beget: incomplete credentials, missing username and/or password", - }, - { - desc: "missing username", - username: "", - password: "456", - expected: "beget: incomplete credentials, missing username and/or password", - }, - { - desc: "missing password", - username: "123", - password: "", - expected: "beget: incomplete credentials, missing username and/or password", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - assert.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - assert.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - assert.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - assert.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.Username = "user" - config.Password = "secret" - 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.CheckQueryParameter(). - With("login", "user"). - With("passwd", "secret"). - With("input_format", "json"). - With("output_format", "json"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/getData", - servermock.ResponseFromInternal("getData-real.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), - ). - Route("GET /dns/changeRecords", - servermock.ResponseFromInternal("changeRecords-doc.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"_acme-challenge.example.com","records":{"TXT":[{"txtdata":"v=spf1 redirect=beget.com","ttl":300},{"value":"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY","priority":10,"ttl":300}]}}`), - ). - Build(t) - - err := provider.Present("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/getData", - servermock.ResponseFromInternal("getData.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), - ). - Route("GET /dns/changeRecords", - servermock.ResponseFromInternal("changeRecords-doc.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"_acme-challenge.example.com","records":{"TXT":[{"txtdata":"foo","ttl":300}]}}`), - ). - Build(t) - - err := provider.CleanUp("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp_empty(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/getData", - servermock.ResponseFromInternal("getData_empty.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), - ). - Route("/", - servermock.Noop().WithStatusCode(http.StatusInternalServerError)). - Build(t) - - err := provider.CleanUp("example.com", "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/beget/internal/client.go b/providers/dns/beget/internal/client.go deleted file mode 100644 index 9b9746ba2..000000000 --- a/providers/dns/beget/internal/client.go +++ /dev/null @@ -1,137 +0,0 @@ -package internal - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" -) - -const defaultBaseURL = "https://api.beget.com/api/" - -// Client the beget.com client. -type Client struct { - login string - password string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient Creates a beget.com client. -func NewClient(login, password string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - login: login, - password: password, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 5 * time.Second}, - } -} - -// GetTXTRecords returns TXT records. -// https://beget.com/ru/kb/api/funkczii-upravleniya-dns#getdata -func (c *Client) GetTXTRecords(ctx context.Context, domain string) ([]Record, error) { - request := GetRecordsRequest{Fqdn: domain} - - resp, err := c.doRequest(ctx, request, "dns", "getData") - if err != nil { - return nil, err - } - - err = resp.HasError() - if err != nil { - return nil, err - } - - result := GetRecordsResult{} - - err = json.Unmarshal(resp.Answer.Result, &result) - if err != nil { - return nil, fmt.Errorf("unmarshal result: %s: %w", string(resp.Answer.Result), err) - } - - return result.Records.TXT, nil -} - -// ChangeTXTRecord changes TXT records. -// https://beget.com/ru/kb/api/funkczii-upravleniya-dns#changerecords -func (c *Client) ChangeTXTRecord(ctx context.Context, domain string, records []Record) error { - request := ChangeRecordsRequest{ - Fqdn: domain, - Records: RecordList{TXT: records}, - } - - resp, err := c.doRequest(ctx, request, "dns", "changeRecords") - if err != nil { - return err - } - - return resp.HasError() -} - -func (c *Client) doRequest(ctx context.Context, data any, fragments ...string) (*APIResponse, error) { - endpoint := c.BaseURL.JoinPath(fragments...) - - inputData, err := json.Marshal(data) - if err != nil { - return nil, fmt.Errorf("failed to mashall input data: %w", err) - } - - query := endpoint.Query() - query.Add("input_data", string(inputData)) - query.Add("login", c.login) - query.Add("passwd", c.password) - query.Add("input_format", "json") - query.Add("output_format", "json") - endpoint.RawQuery = query.Encode() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return nil, errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return nil, parseError(req, resp) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - var apiResp APIResponse - - err = json.Unmarshal(raw, &apiResp) - if err != nil { - return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return &apiResp, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var apiResp APIResponse - - err := json.Unmarshal(raw, &apiResp) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return fmt.Errorf("[status code %d] %w", resp.StatusCode, apiResp) -} diff --git a/providers/dns/beget/internal/client_test.go b/providers/dns/beget/internal/client_test.go deleted file mode 100644 index 4c127abf1..000000000 --- a/providers/dns/beget/internal/client_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package internal - -import ( - "context" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder[*Client]( - func(server *httptest.Server) (*Client, error) { - client := NewClient("user", "secret") - - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client, nil - }, - servermock.CheckQueryParameter(). - With("login", "user"). - With("passwd", "secret"). - With("input_format", "json"). - With("output_format", "json"), - ) -} - -func TestClient_GetTXTRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/getData", - servermock.ResponseFromFixture("getData-real.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"example.com"}`), - ). - Build(t) - - data, err := client.GetTXTRecords(context.Background(), "example.com") - require.NoError(t, err) - - expected := []Record{{Data: "v=spf1 redirect=beget.com", TTL: 300}} - - assert.Equal(t, expected, data) -} - -func TestClient_ChangeTXTRecord(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/changeRecords", - servermock.ResponseFromFixture("changeRecords-doc.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"sub.example.com","records":{"TXT":[{"value":"txtTXTtxt","priority":10,"ttl":300}]}}`), - ). - Build(t) - - records := []Record{{Value: "txtTXTtxt", TTL: 300, Priority: 10}} - - err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) - require.NoError(t, err) -} - -func TestClient_ChangeTXTRecord_error(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/changeRecords", - servermock.ResponseFromFixture("error.json")). - Build(t) - - records := []Record{{Data: "txtTXTtxt", TTL: 300}} - - err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) - require.Error(t, err) - - require.EqualError(t, err, "API error: NO_SUCH_METHOD: No such method") -} - -func TestClient_ChangeTXTRecord_answer_error(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/changeRecords", - servermock.ResponseFromFixture("answer_error.json")). - Build(t) - - records := []Record{{Data: "txtTXTtxt", TTL: 300}} - - err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) - require.Error(t, err) - - require.EqualError(t, err, "API answer error: INVALID_DATA: Login length cannot be greater than 12 characters") -} - -func TestClient_ChangeTXTRecord_remove(t *testing.T) { - client := mockBuilder(). - Route("GET /dns/changeRecords", - servermock.ResponseFromFixture("changeRecords-doc.json"), - servermock.CheckQueryParameter(). - With("input_data", `{"fqdn":"sub.example.com","records":{}}`), - ). - Build(t) - - err := client.ChangeTXTRecord(context.Background(), "sub.example.com", nil) - require.NoError(t, err) -} diff --git a/providers/dns/beget/internal/fixtures/answer_error.json b/providers/dns/beget/internal/fixtures/answer_error.json deleted file mode 100644 index 12f5fdda7..000000000 --- a/providers/dns/beget/internal/fixtures/answer_error.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "error", - "errors": [ - { - "error_code": "INVALID_DATA", - "error_text": "Login length cannot be greater than 12 characters" - } - ] - } -} diff --git a/providers/dns/beget/internal/fixtures/changeRecords-doc.json b/providers/dns/beget/internal/fixtures/changeRecords-doc.json deleted file mode 100644 index 4c182d4e6..000000000 --- a/providers/dns/beget/internal/fixtures/changeRecords-doc.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "success", - "result": { - "A": [ - { - "priority": 10, - "value": "127.0.0.1" - } - ], - "MX": [ - { - "priority": 10, - "value": "mx1.beget.ru" - }, - { - "priority": 20, - "value": "mx2.beget.ru" - } - ], - "TXT": [ - { - "priority": 10, - "value": "TXT record" - } - ] - } - } -} - diff --git a/providers/dns/beget/internal/fixtures/error.json b/providers/dns/beget/internal/fixtures/error.json deleted file mode 100644 index 1dd2a111e..000000000 --- a/providers/dns/beget/internal/fixtures/error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "status": "error", - "error_text": "No such method", - "error_code": "NO_SUCH_METHOD" -} diff --git a/providers/dns/beget/internal/fixtures/getData-doc.json b/providers/dns/beget/internal/fixtures/getData-doc.json deleted file mode 100644 index bed5b7461..000000000 --- a/providers/dns/beget/internal/fixtures/getData-doc.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "success", - "result": { - "is_under_control": 1, - "is_beget_dns": 1, - "is_subdomain": 0, - "fqdn": "beget.ru", - "records": { - "DNS": [ - { - "value": "ns1.beget.ru", - "priority": 10 - }, - { - "value": "ns2.beget.ru", - "priority": 20 - } - ], - "DNS_IP": [ - { - "value": null, - "priority": 10 - }, - { - "value": null, - "priority": 20 - } - ], - "A": [ - { - "value": "91.106.201.65", - "priority": "0" - } - ], - "MX": [ - { - "value": "mx1.beget.ru", - "priority": "10" - }, - { - "value": "mx2.beget.ru", - "priority": "20" - } - ], - "TXT": [ - { - "value": "", - "priority": 0 - } - ] - }, - "set_type": 1 - } - } -} - diff --git a/providers/dns/beget/internal/fixtures/getData-real.json b/providers/dns/beget/internal/fixtures/getData-real.json deleted file mode 100644 index 700c756e8..000000000 --- a/providers/dns/beget/internal/fixtures/getData-real.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "success", - "result": { - "is_under_control": true, - "is_beget_dns": true, - "is_subdomain": false, - "fqdn": "example.com", - "records": { - "MX": [ - { - "ttl": 300, - "exchange": "mx2.beget.com.", - "preference": 20 - }, - { - "ttl": 300, - "exchange": "mx1.beget.com.", - "preference": 10 - } - ], - "TXT": [ - { - "ttl": 300, - "txtdata": "v=spf1 redirect=beget.com" - } - ], - "A": [ - { - "ttl": 300, - "address": "1.2.3.4" - } - ], - "DNS": [ - { - "value": "ns1.beget.pro" - }, - { - "value": "ns2.beget.pro" - }, - { - "value": "ns1.beget.com" - }, - { - "value": "ns2.beget.com" - } - ], - "DNS_IP": [ - { - "value": "" - }, - { - "value": "" - }, - { - "value": "" - }, - { - "value": "" - } - ] - }, - "set_type": 1 - } - } -} diff --git a/providers/dns/beget/internal/fixtures/getData.json b/providers/dns/beget/internal/fixtures/getData.json deleted file mode 100644 index 571b6ac31..000000000 --- a/providers/dns/beget/internal/fixtures/getData.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "success", - "result": { - "is_under_control": true, - "is_beget_dns": true, - "is_subdomain": false, - "fqdn": "_acme-challenge.example.com", - "records": { - "MX": [ - { - "ttl": 300, - "exchange": "mx2.beget.com.", - "preference": 20 - }, - { - "ttl": 300, - "exchange": "mx1.beget.com.", - "preference": 10 - } - ], - "TXT": [ - { - "ttl": 300, - "txtdata": "foo" - } - ], - "A": [ - { - "ttl": 300, - "address": "1.2.3.4" - } - ], - "DNS": [ - { - "value": "ns1.beget.pro" - }, - { - "value": "ns2.beget.pro" - }, - { - "value": "ns1.beget.com" - }, - { - "value": "ns2.beget.com" - } - ], - "DNS_IP": [ - { - "value": "" - }, - { - "value": "" - }, - { - "value": "" - }, - { - "value": "" - } - ] - }, - "set_type": 1 - } - } -} diff --git a/providers/dns/beget/internal/fixtures/getData_empty.json b/providers/dns/beget/internal/fixtures/getData_empty.json deleted file mode 100644 index ea819eeca..000000000 --- a/providers/dns/beget/internal/fixtures/getData_empty.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "status": "success", - "answer": { - "status": "success", - "result": { - "is_under_control": true, - "is_beget_dns": true, - "is_subdomain": false, - "fqdn": "_acme-challenge.example.com", - "set_type": 1 - } - } -} diff --git a/providers/dns/beget/internal/types.go b/providers/dns/beget/internal/types.go deleted file mode 100644 index f453bf628..000000000 --- a/providers/dns/beget/internal/types.go +++ /dev/null @@ -1,100 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "strings" -) - -const successResult = "success" - -// APIResponse is the representation of an API response. -type APIResponse struct { - Status string `json:"status"` - - Answer *Answer `json:"answer,omitempty"` - - ErrorCode string `json:"error_code,omitempty"` - ErrorText string `json:"error_text,omitempty"` -} - -func (a APIResponse) Error() string { - return fmt.Sprintf("API %s: %s: %s", a.Status, a.ErrorCode, a.ErrorText) -} - -// HasError returns an error is the response contains an error. -func (a APIResponse) HasError() error { - if a.Status != successResult { - return a - } - - if a.Answer == nil || a.Status != successResult || a.Answer.Status != successResult { - return a.Answer - } - - return nil -} - -// Answer is the representation of an API response answer. -type Answer struct { - Status string `json:"status,omitempty"` - Result json.RawMessage `json:"result,omitempty"` - - Errors []AnswerError `json:"errors,omitempty"` - ErrorCode string `json:"error_code,omitempty"` - ErrorText string `json:"error_text,omitempty"` -} - -type AnswerError struct { - ErrorCode string `json:"error_code,omitempty"` - ErrorText string `json:"error_text,omitempty"` -} - -func (a Answer) Error() string { - parts := []string{fmt.Sprintf("API answer %s", a.Status)} - - if a.ErrorCode != "" { - parts = append(parts, a.ErrorCode) - } - - if a.ErrorText != "" { - parts = append(parts, a.ErrorText) - } - - if len(a.Errors) > 0 { - for _, e := range a.Errors { - parts = append(parts, e.ErrorCode, e.ErrorText) - } - } - - return strings.Join(parts, ": ") -} - -// GetRecordsRequest data representation for data get request. -type GetRecordsRequest struct { - Fqdn string `json:"fqdn,omitempty"` -} - -// ChangeRecordsRequest data representation for data change request. -type ChangeRecordsRequest struct { - Fqdn string `json:"fqdn,omitempty"` - Records RecordList `json:"records"` -} - -// RecordList List of entries (in this case only described TXT). -type RecordList struct { - TXT []Record `json:"TXT,omitempty"` -} - -// Record data representation for TXT record. -type Record struct { - Value string `json:"value,omitempty"` - Data string `json:"txtdata,omitempty"` - Priority int `json:"priority,omitempty"` - TTL int `json:"ttl,omitempty"` -} - -type GetRecordsResult struct { - Fqdn string `json:"fqdn"` - Records RecordList `json:"records"` -} diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go deleted file mode 100644 index 5bbb7a16a..000000000 --- a/providers/dns/binarylane/binarylane.go +++ /dev/null @@ -1,165 +0,0 @@ -// Package binarylane implements a DNS provider for solving the DNS-01 challenge using Binary Lane. -package binarylane - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/binarylane/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "BINARYLANE_" - - EnvAPIToken = envNamespace + "API_TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIToken string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 3600), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]int64 - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Binary Lane. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIToken) - if err != nil { - return nil, fmt.Errorf("binarylane: %w", err) - } - - config := NewDefaultConfig() - config.APIToken = values[EnvAPIToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Binary Lane. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("binarylane: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIToken) - if err != nil { - return nil, fmt.Errorf("binarylane: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int64), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("binarylane: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("binarylane: %w", err) - } - - record := internal.Record{ - Type: "TXT", - Name: subDomain, - Data: info.Value, - TTL: d.config.TTL, - } - - response, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) - if err != nil { - return fmt.Errorf("binarylane: create record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = response.ID - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("binarylane: could not find zone for domain %q: %w", domain, err) - } - - // get the record's unique ID from when we created it - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("binarylane: unknown record ID for '%s'", info.EffectiveFQDN) - } - - err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("binarylane: delete record: %w", err) - } - - 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/binarylane/binarylane.toml b/providers/dns/binarylane/binarylane.toml deleted file mode 100644 index 8b382f3b2..000000000 --- a/providers/dns/binarylane/binarylane.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Binary Lane" -Description = '''''' -URL = "https://www.binarylane.com.au/" -Code = "binarylane" -Since = "v4.26.0" - -Example = ''' -BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns binarylane -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - BINARYLANE_API_TOKEN = "API token" - [Configuration.Additional] - BINARYLANE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - BINARYLANE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - BINARYLANE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - BINARYLANE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://api.binarylane.com.au/reference/#tag/Domains" diff --git a/providers/dns/binarylane/binarylane_test.go b/providers/dns/binarylane/binarylane_test.go deleted file mode 100644 index 4f2cfd230..000000000 --- a/providers/dns/binarylane/binarylane_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package binarylane - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIToken: "secret", - }, - }, - { - desc: "missing API token", - envVars: map[string]string{ - EnvAPIToken: "", - }, - expected: "binarylane: some credentials information are missing: BINARYLANE_API_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "secret", - }, - { - desc: "missing API token", - expected: "binarylane: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/binarylane/internal/client.go b/providers/dns/binarylane/internal/client.go deleted file mode 100644 index 3f10e9f8b..000000000 --- a/providers/dns/binarylane/internal/client.go +++ /dev/null @@ -1,148 +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" -) - -const defaultBaseURL = "https://api.binarylane.com.au/v2/" - -const authorizationHeader = "Authorization" - -// Client the Binary Lane API client. -type Client struct { - apiToken string - - baseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(apiToken string) (*Client, error) { - if apiToken == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiToken: apiToken, - baseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -// CreateRecord Creates a new domain record. -// https://api.binarylane.com.au/reference/#tag/Domains/paths/~1v2~1domains~1%7Bdomain_name%7D~1records/post -func (c *Client) CreateRecord(ctx context.Context, domain string, record Record) (*Record, error) { - endpoint := c.baseURL.JoinPath("domains", domain, "records") - - if record.Name == "" { - record.Name = "@" - } - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return nil, err - } - - var result APIResponse - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.DomainRecord, nil -} - -// DeleteRecord Deletes an existing domain record. -// https://api.binarylane.com.au/reference/#tag/Domains/paths/~1v2~1domains~1%7Bdomain_name%7D~1records~1%7Brecord_id%7D/delete -func (c *Client) DeleteRecord(ctx context.Context, domainName string, recordID int64) error { - endpoint := c.baseURL.JoinPath("domains", domainName, "records", strconv.FormatInt(recordID, 10)) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - req.Header.Set(authorizationHeader, "Bearer "+c.apiToken) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/binarylane/internal/client_test.go b/providers/dns/binarylane/internal/client_test.go deleted file mode 100644 index 0398d5adf..000000000 --- a/providers/dns/binarylane/internal/client_test.go +++ /dev/null @@ -1,97 +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/require" -) - -func mockBuilder() *servermock.Builder[*Client] { - return servermock.NewBuilder( - func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret") - if err != nil { - return nil, err - } - - client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader().WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/example.com/records", - servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). - Build(t) - - record := Record{ - Type: "TXT", - Name: "foo", - Data: "txtTXTtxt", - TTL: 300, - } - - rec, err := client.CreateRecord(t.Context(), "example.com", record) - require.NoError(t, err) - - expected := &Record{ - ID: 123, - Type: "TXT", - Name: "foo", - Data: "txtTXTtxt", - TTL: 300, - } - - require.Equal(t, expected, rec) -} - -func TestClient_CreateRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/example.com/records", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - record := Record{ - Type: "TXT", - Name: "foo", - Data: "txtTXTtxt", - TTL: 300, - } - - _, err := client.CreateRecord(t.Context(), "example.com", record) - require.EqualError(t, err, "400: type: title: detail: instance: property1: a") -} - -func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/records/123", - servermock.Noop(). - WithStatusCode(http.StatusNoContent)). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", 123) - require.NoError(t, err) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/records/123", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - err := client.DeleteRecord(t.Context(), "example.com", 123) - require.EqualError(t, err, "400: type: title: detail: instance: property1: a") -} diff --git a/providers/dns/binarylane/internal/fixtures/create_record-request.json b/providers/dns/binarylane/internal/fixtures/create_record-request.json deleted file mode 100644 index 98a349650..000000000 --- a/providers/dns/binarylane/internal/fixtures/create_record-request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "TXT", - "name": "foo", - "data": "txtTXTtxt", - "ttl": 300 -} diff --git a/providers/dns/binarylane/internal/fixtures/create_record.json b/providers/dns/binarylane/internal/fixtures/create_record.json deleted file mode 100644 index 709bef23e..000000000 --- a/providers/dns/binarylane/internal/fixtures/create_record.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain_record": { - "id": 123, - "type": "TXT", - "name": "foo", - "data": "txtTXTtxt", - "ttl": 300 - } -} diff --git a/providers/dns/binarylane/internal/fixtures/error.json b/providers/dns/binarylane/internal/fixtures/error.json deleted file mode 100644 index 79d115f74..000000000 --- a/providers/dns/binarylane/internal/fixtures/error.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "type", - "title": "title", - "status": 400, - "detail": "detail", - "instance": "instance", - "errors": { - "property1": [ - "a" - ] - }, - "property1": null, - "property2": null -} diff --git a/providers/dns/binarylane/internal/types.go b/providers/dns/binarylane/internal/types.go deleted file mode 100644 index 06d4be5c0..000000000 --- a/providers/dns/binarylane/internal/types.go +++ /dev/null @@ -1,44 +0,0 @@ -package internal - -import ( - "fmt" - "strings" -) - -type APIError struct { - Type string `json:"type"` - Title string `json:"title"` - Status int `json:"status"` - Detail string `json:"detail"` - Instance string `json:"instance"` - Errors map[string][]string `json:"errors"` -} - -func (a *APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%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, ", ")) - } - - return msg.String() -} - -type Record struct { - ID int64 `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Data string `json:"data,omitempty"` - Priority int `json:"priority,omitempty"` - Port int `json:"port,omitempty"` - TTL int `json:"ttl,omitempty"` - Weight int `json:"weight,omitempty"` - Flags int `json:"flags,omitempty"` - Tag string `json:"tag,omitempty"` -} - -type APIResponse struct { - DomainRecord *Record `json:"domain_record"` -} diff --git a/providers/dns/bindman/bindman.go b/providers/dns/bindman/bindman.go index c529cb63c..fbaddcbec 100644 --- a/providers/dns/bindman/bindman.go +++ b/providers/dns/bindman/bindman.go @@ -10,8 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - bindman "github.com/labbsr0x/bindman-dns-webhook/src/client" + "github.com/labbsr0x/bindman-dns-webhook/src/client" ) // Environment variables names. @@ -49,7 +48,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *bindman.DNSWebhookClient + client *client.DNSWebhookClient } // NewDNSProvider returns a DNSProvider instance configured for Bindman. @@ -76,17 +75,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("bindman: bindman manager address missing") } - // Because the client.New uses the http.DefaultClient. - if config.HTTPClient == nil { - config.HTTPClient = &http.Client{Timeout: time.Minute} - } - - client, err := bindman.New(config.BaseURL, clientdebug.Wrap(config.HTTPClient)) + bClient, err := client.New(config.BaseURL, config.HTTPClient) if err != nil { return nil, fmt.Errorf("bindman: %w", err) } - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{config: config, client: bClient}, nil } // Present creates a TXT record using the specified parameters. @@ -98,7 +92,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err := d.client.AddRecord(info.EffectiveFQDN, "TXT", info.Value); err != nil { return fmt.Errorf("bindman: %w", err) } - return nil } @@ -109,7 +102,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err := d.client.RemoveRecord(info.EffectiveFQDN, "TXT"); err != nil { return fmt.Errorf("bindman: %w", err) } - return nil } diff --git a/providers/dns/bindman/bindman.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/bindman/bindman_test.go b/providers/dns/bindman/bindman_test.go index 978a1d006..a0db025e7 100644 --- a/providers/dns/bindman/bindman_test.go +++ b/providers/dns/bindman/bindman_test.go @@ -1,13 +1,14 @@ +// Package bindman implements a DNS provider for solving the DNS-01 challenge. package bindman import ( + "errors" "net/http" - "net/http/httptest" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" + bindmanClient "github.com/labbsr0x/bindman-dns-webhook/src/client" "github.com/stretchr/testify/require" ) @@ -46,7 +47,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,24 +106,10 @@ func TestNewDNSProviderConfig(t *testing.T) { } } -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.BaseURL = server.URL - config.HTTPClient = server.Client() - - return NewDNSProviderConfig(config) - }, - servermock.CheckHeader(). - WithJSONHeaders(). - With("User-Agent", "bindman-dns-webhook-client")) -} - func TestDNSProvider_Present(t *testing.T) { testCases := []struct { name string - mock *servermock.Builder[*DNSProvider] + client *bindmanClient.DNSWebhookClient domain string token string keyAuth string @@ -131,31 +117,28 @@ func TestDNSProvider_Present(t *testing.T) { }{ { name: "success when add record function return no error", - mock: mockBuilder(). - Route("POST /records", - servermock.Noop().WithStatusCode(http.StatusNoContent), - servermock.CheckRequestJSONBodyFromFixture("add_record-request.json"), - ), - domain: "example.com", + client: &bindmanClient.DNSWebhookClient{ + ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent}, + }, + domain: "hello.test.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: false, }, { name: "error when add record function return an error", - mock: mockBuilder(). - Route("POST /records", - servermock.ResponseFromFixture("error.json"), - ), - domain: "example.com", + client: &bindmanClient.DNSWebhookClient{ + ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")}, + }, + domain: "hello.test.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: true, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - provider := test.mock.Build(t) + d := &DNSProvider{client: test.client} - err := provider.Present(test.domain, test.token, test.keyAuth) + err := d.Present(test.domain, test.token, test.keyAuth) if test.expectError { require.Error(t, err) } else { @@ -168,7 +151,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { testCases := []struct { name string - mock *servermock.Builder[*DNSProvider] + client *bindmanClient.DNSWebhookClient domain string token string keyAuth string @@ -176,33 +159,30 @@ func TestDNSProvider_CleanUp(t *testing.T) { }{ { name: "success when remove record function return no error", - mock: mockBuilder(). - Route("DELETE /records/_acme-challenge.example.com./TXT", - servermock.Noop().WithStatusCode(http.StatusNoContent), - ), - domain: "example.com", + client: &bindmanClient.DNSWebhookClient{ + ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent}, + }, + domain: "hello.test.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: false, }, { name: "error when remove record function return an error", - mock: mockBuilder(). - Route("DELETE /records/_acme-challenge.example.com./TXT", - servermock.ResponseFromFixture("error.json"), - ), - domain: "example.com", + client: &bindmanClient.DNSWebhookClient{ + ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")}, + }, + domain: "hello.test.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: true, }, } - for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - provider := test.mock.Build(t) + d := &DNSProvider{client: test.client} - err := provider.CleanUp(test.domain, test.token, test.keyAuth) + err := d.CleanUp(test.domain, test.token, test.keyAuth) if test.expectError { - require.ErrorContains(t, err, "bindman: ERROR (400): bar; ") + require.Error(t, err) } else { require.NoError(t, err) } @@ -216,7 +196,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -230,7 +209,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -239,3 +217,25 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +type MockHTTPClientAPI struct { + Data []byte + Status int + Error error +} + +func (m *MockHTTPClientAPI) Put(url string, data []byte) (*http.Response, []byte, error) { + return &http.Response{StatusCode: m.Status}, m.Data, m.Error +} + +func (m *MockHTTPClientAPI) Post(url string, data []byte) (*http.Response, []byte, error) { + return &http.Response{StatusCode: m.Status}, m.Data, m.Error +} + +func (m *MockHTTPClientAPI) Get(url string) (*http.Response, []byte, error) { + return &http.Response{StatusCode: m.Status}, m.Data, m.Error +} + +func (m *MockHTTPClientAPI) Delete(url string) (*http.Response, []byte, error) { + return &http.Response{StatusCode: m.Status}, m.Data, m.Error +} diff --git a/providers/dns/bindman/fixtures/add_record-request.json b/providers/dns/bindman/fixtures/add_record-request.json deleted file mode 100644 index 9585565b8..000000000 --- a/providers/dns/bindman/fixtures/add_record-request.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "_acme-challenge.example.com.", - "value": "_EYMkjukXEMcXbnvpT6WLESzfYhxH190NKTBo3cpu-E", - "type": "TXT" -} diff --git a/providers/dns/bindman/fixtures/error.json b/providers/dns/bindman/fixtures/error.json deleted file mode 100644 index c8a014510..000000000 --- a/providers/dns/bindman/fixtures/error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "message": "bar", - "code": 400, - "details": ["foo"] -} diff --git a/providers/dns/bluecat/bluecat.go b/providers/dns/bluecat/bluecat.go index b26fab8be..8ba026f49 100644 --- a/providers/dns/bluecat/bluecat.go +++ b/providers/dns/bluecat/bluecat.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/bluecat/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -111,8 +110,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/bluecat/bluecat_test.go b/providers/dns/bluecat/bluecat_test.go index 38b110470..5a3670e3a 100644 --- a/providers/dns/bluecat/bluecat_test.go +++ b/providers/dns/bluecat/bluecat_test.go @@ -105,7 +105,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -220,7 +219,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -234,7 +232,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/bluecat/internal/client.go b/providers/dns/bluecat/internal/client.go index d517ea857..de31579ea 100644 --- a/providers/dns/bluecat/internal/client.go +++ b/providers/dns/bluecat/internal/client.go @@ -106,7 +106,6 @@ func (c *Client) AddEntity(ctx context.Context, parentID uint, entity Entity) (u // addEntity responds only with body text containing the ID of the created record addTxtResp := string(raw) - id, err := strconv.ParseUint(addTxtResp, 10, 64) if err != nil { return 0, fmt.Errorf("addEntity request failed: %s", addTxtResp) @@ -148,7 +147,6 @@ func (c *Client) GetEntityByName(ctx context.Context, parentID uint, name, objTy } var entity EntityResponse - err = json.Unmarshal(raw, &entity) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/bluecat/internal/client_test.go b/providers/dns/bluecat/internal/client_test.go index d4776b8a1..9d79f46b3 100644 --- a/providers/dns/bluecat/internal/client_test.go +++ b/providers/dns/bluecat/internal/client_test.go @@ -31,7 +31,6 @@ func TestClient_LookupParentZoneID(t *testing.T) { Type: ZoneType, Properties: "test", }) - return } 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.go b/providers/dns/bookmyname/bookmyname.go index 6f42dfd78..991420619 100644 --- a/providers/dns/bookmyname/bookmyname.go +++ b/providers/dns/bookmyname/bookmyname.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/bookmyname/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -88,8 +87,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/bookmyname/bookmyname_test.go b/providers/dns/bookmyname/bookmyname_test.go index 8b3fa21e6..dd02d63d7 100644 --- a/providers/dns/bookmyname/bookmyname_test.go +++ b/providers/dns/bookmyname/bookmyname_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/brandit/brandit.go b/providers/dns/brandit/brandit.go index fe3b52239..437d1642a 100644 --- a/providers/dns/brandit/brandit.go +++ b/providers/dns/brandit/brandit.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/brandit/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -93,8 +92,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -168,7 +165,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordsMu.Lock() dnsRecord, ok := d.records[token] d.recordsMu.Unlock() - if !ok { return fmt.Errorf("brandit: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -187,7 +183,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordID int - for i, r := range records.RR { if r == dnsRecord { recordID = i diff --git a/providers/dns/brandit/brandit.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/brandit/brandit_test.go b/providers/dns/brandit/brandit_test.go index 40abdd3d0..156e7c3f4 100644 --- a/providers/dns/brandit/brandit_test.go +++ b/providers/dns/brandit/brandit_test.go @@ -48,7 +48,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,7 +120,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +133,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/brandit/internal/client.go b/providers/dns/brandit/internal/client.go index cda3be5a2..59c57419a 100644 --- a/providers/dns/brandit/internal/client.go +++ b/providers/dns/brandit/internal/client.go @@ -62,7 +62,6 @@ func (c *Client) ListRecords(ctx context.Context, account, dnsZone string) (*Lis query.Add("first", strconv.Itoa(result.Response.Last[0]+1)) tmp := &Response[*ListRecordsResponse]{} - err := c.do(ctx, query, tmp) if err != nil { return nil, err @@ -157,7 +156,6 @@ func (c *Client) do(ctx context.Context, query url.Values, result any) error { // Unmarshal the error response, because the API returns a 200 OK even if there is an error. var apiError APIError - err = json.Unmarshal(raw, &apiError) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -185,7 +183,6 @@ func sign(apiUsername, apiKey string, query url.Values) (url.Values, error) { canonicalRequest := fmt.Sprintf("%s%s%s", apiUsername, timestamp, defaultBaseURL) mac := hmac.New(sha256.New, []byte(apiKey)) - _, err := mac.Write([]byte(canonicalRequest)) if err != nil { return nil, err diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 29949608b..1489d1c5e 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -5,16 +5,13 @@ import ( "context" "errors" "fmt" - "net/http" "slices" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/ptr" - "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/nrdcg/bunny-go" "golang.org/x/net/publicsuffix" ) @@ -28,7 +25,6 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const minTTL = 60 @@ -37,12 +33,10 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string - + APIKey string PropagationTimeout time.Duration PollingInterval time.Duration TTL int - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -51,9 +45,6 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } @@ -91,19 +82,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("bunny: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } - if config.HTTPClient == nil { - config.HTTPClient = &http.Client{Timeout: 30 * time.Second} - } + client := bunny.NewClient(config.APIKey) - config.HTTPClient = clientdebug.Wrap(config.HTTPClient) - - return &DNSProvider{ - config: config, - client: bunny.NewClient(config.APIKey, - bunny.WithUserAgent(useragent.Get()), - bunny.WithHTTPClient(config.HTTPClient), - ), - }, nil + return &DNSProvider{config: config, client: client}, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -159,12 +140,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var record *bunny.DNSRecord - for _, r := range zone.Records { if ptr.Deref(r.Name) == subDomain && ptr.Deref(r.Type) == bunny.DNSRecordTypeTXT { r := r record = &r - break } } @@ -200,7 +179,6 @@ func findZone(zones *bunny.DNSZones, domain string) *bunny.DNSZone { var domainLength int var zone *bunny.DNSZone - for _, item := range zones.Items { if item == nil { continue diff --git a/providers/dns/bunny/bunny.toml b/providers/dns/bunny/bunny.toml index 758c4f202..bdbbf3177 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] @@ -16,7 +16,6 @@ lego --dns bunny -d '*.example.com' -d example.com run BUNNY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" BUNNY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" BUNNY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" - BUNNY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs.bunny.net/reference/dnszonepublic_index" diff --git a/providers/dns/bunny/bunny_test.go b/providers/dns/bunny/bunny_test.go index ca4e821e0..4cf0f6b01 100644 --- a/providers/dns/bunny/bunny_test.go +++ b/providers/dns/bunny/bunny_test.go @@ -40,7 +40,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -108,7 +107,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -122,7 +120,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/checkdomain/checkdomain.go b/providers/dns/checkdomain/checkdomain.go index 4bc926ed9..e2d7a05aa 100644 --- a/providers/dns/checkdomain/checkdomain.go +++ b/providers/dns/checkdomain/checkdomain.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/checkdomain/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -73,7 +72,6 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("checkdomain: invalid %s: %w", EnvEndpoint, err) } - config.Endpoint = endpoint return NewDNSProviderConfig(config) @@ -88,11 +86,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("checkdomain: missing token") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), - ), - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token)) if config.Endpoint != nil { client.BaseURL = config.Endpoint 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/checkdomain_test.go b/providers/dns/checkdomain/checkdomain_test.go index b2c940f7a..d9d0b62a6 100644 --- a/providers/dns/checkdomain/checkdomain_test.go +++ b/providers/dns/checkdomain/checkdomain_test.go @@ -46,7 +46,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -109,7 +108,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -123,7 +121,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/checkdomain/internal/client.go b/providers/dns/checkdomain/internal/client.go index 68d090755..74189dee4 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. @@ -63,7 +63,6 @@ func (c *Client) GetDomainIDByName(ctx context.Context, name string) (int, error c.domainIDMu.Lock() id, ok := c.domainIDMapping[name] c.domainIDMu.Unlock() - if ok { return id, nil } @@ -101,7 +100,6 @@ func (c *Client) listDomains(ctx context.Context) ([]*Domain, error) { totalPages := maxInt var domainList []*Domain - for currentPage <= totalPages { q.Set("page", strconv.Itoa(currentPage)) endpoint.RawQuery = q.Encode() @@ -153,7 +151,6 @@ func (c *Client) CheckNameservers(ctx context.Context, domainID int) error { } var found1, found2 bool - for _, item := range info.Nameservers { switch item.Name { case ns1: @@ -232,7 +229,6 @@ func (c *Client) getDomainInfo(ctx context.Context, domainID int) (*DomainRespon } var res DomainResponse - err = c.do(req, &res) if err != nil { return nil, err @@ -246,7 +242,6 @@ func (c *Client) listRecords(ctx context.Context, domainID int, recordType strin q := endpoint.Query() q.Set("limit", strconv.Itoa(maxLimit)) - if recordType != "" { q.Set("type", recordType) } @@ -255,7 +250,6 @@ func (c *Client) listRecords(ctx context.Context, domainID int, recordType strin totalPages := maxInt var recordList []*Record - for currentPage <= totalPages { q.Set("page", strconv.Itoa(currentPage)) endpoint.RawQuery = q.Encode() diff --git a/providers/dns/civo/civo.go b/providers/dns/civo/civo.go index dfb7c307f..46c474b52 100644 --- a/providers/dns/civo/civo.go +++ b/providers/dns/civo/civo.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/civo/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,11 +91,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // Create a Civo client - DNS is region independent, we can use any region - client, err := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), - ), - "LON1") + client, err := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), "LON1") if err != nil { return nil, fmt.Errorf("civo: %w", err) } @@ -169,7 +164,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var dnsRecord internal.Record - for _, entry := range dnsRecords { if entry.Name == subDomain && entry.Value == info.Value { dnsRecord = entry diff --git a/providers/dns/civo/civo.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/civo/civo_test.go b/providers/dns/civo/civo_test.go index 416dbac1d..eb215fbcb 100644 --- a/providers/dns/civo/civo_test.go +++ b/providers/dns/civo/civo_test.go @@ -42,7 +42,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -107,7 +106,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -121,7 +119,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +132,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.Token = "secret" p, err := NewDNSProviderConfig(config) diff --git a/providers/dns/civo/internal/client.go b/providers/dns/civo/internal/client.go index dc1d57793..25a11ef60 100644 --- a/providers/dns/civo/internal/client.go +++ b/providers/dns/civo/internal/client.go @@ -188,7 +188,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/civo/internal/client_test.go b/providers/dns/civo/internal/client_test.go index ad56b75de..5b47c185e 100644 --- a/providers/dns/civo/internal/client_test.go +++ b/providers/dns/civo/internal/client_test.go @@ -93,6 +93,7 @@ func TestClient_ListDNSRecords_error_raw(t *testing.T) { // > So, for example, 404 Not Found pages are a standard page of text // > but 403 Unauthorized requests may have a reason attribute available in the JSON object. // https://www.civo.com/api#parameters-and-responses + client := mockBuilder(). Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", servermock.RawStringResponse(http.StatusText(http.StatusNotFound)). diff --git a/providers/dns/clouddns/clouddns.go b/providers/dns/clouddns/clouddns.go index 77b673738..379dd3cf2 100644 --- a/providers/dns/clouddns/clouddns.go +++ b/providers/dns/clouddns/clouddns.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/clouddns/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -94,8 +93,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/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/clouddns/clouddns_test.go b/providers/dns/clouddns/clouddns_test.go index f1e2a196e..d7bfc4a1f 100644 --- a/providers/dns/clouddns/clouddns_test.go +++ b/providers/dns/clouddns/clouddns_test.go @@ -63,7 +63,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -149,7 +148,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -163,7 +161,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/clouddns/internal/client.go b/providers/dns/clouddns/internal/client.go index 9fb6902de..cd3da50c7 100644 --- a/providers/dns/clouddns/internal/client.go +++ b/providers/dns/clouddns/internal/client.go @@ -122,7 +122,6 @@ func (c *Client) getDomain(ctx context.Context, zone string) (Domain, error) { } var result SearchResponse - err = c.do(req, &result) if err != nil { return Domain{}, err @@ -144,7 +143,6 @@ func (c *Client) getRecord(ctx context.Context, domainID, recordName string) (Re } var result DomainInfo - err = c.do(req, &result) if err != nil { return Record{}, err @@ -234,7 +232,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/clouddns/internal/identity.go b/providers/dns/clouddns/internal/identity.go index 6b20ad814..4ea5c5049 100644 --- a/providers/dns/clouddns/internal/identity.go +++ b/providers/dns/clouddns/internal/identity.go @@ -20,7 +20,6 @@ func (c *Client) login(ctx context.Context) (*AuthResponse, error) { } var result AuthResponse - err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/clouddns/internal/types.go b/providers/dns/clouddns/internal/types.go index 9de11d848..a53c958a7 100644 --- a/providers/dns/clouddns/internal/types.go +++ b/providers/dns/clouddns/internal/types.go @@ -21,7 +21,7 @@ type Authorization struct { } type AuthResponse struct { - Auth Auth `json:"auth"` + Auth Auth `json:"auth,omitempty"` } type Auth struct { diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 98b3495bb..5fd350925 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -104,7 +104,6 @@ func NewDNSProvider() (*DNSProvider, error) { ) if err != nil { var errT error - values, errT = env.GetWithFallback( []string{EnvDNSAPIToken, altEnvName(EnvDNSAPIToken)}, []string{EnvZoneAPIToken, altEnvName(EnvZoneAPIToken), EnvDNSAPIToken, altEnvName(EnvDNSAPIToken)}, @@ -155,10 +154,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) + ctx := context.Background() + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) @@ -192,8 +191,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -201,7 +198,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) } - zoneID, err := d.client.ZoneIDByName(ctx, authZone) + zoneID, err := d.client.ZoneIDByName(context.Background(), authZone) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %w", authZone, err) } @@ -210,12 +207,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("cloudflare: unknown record ID for '%s'", info.EffectiveFQDN) } - err = d.client.DeleteDNSRecord(ctx, zoneID, recordID) + err = d.client.DeleteDNSRecord(context.Background(), zoneID, recordID) if err != nil { log.Printf("cloudflare: failed to delete TXT record: %v", err) } 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/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index 8de9dd848..10e96503a 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -81,7 +81,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -178,18 +177,15 @@ func TestNewDNSProviderWithToken(t *testing.T) { } defer envTest.RestoreEnv() - localEnvTest := tester.NewEnvTest( EnvDNSAPIToken, altEnvName(EnvDNSAPIToken), EnvZoneAPIToken, altEnvName(EnvZoneAPIToken), ).WithDomain(envDomain) - envTest.ClearEnv() for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer localEnvTest.RestoreEnv() - localEnvTest.ClearEnv() localEnvTest.Apply(test.envVars) @@ -204,7 +200,6 @@ func TestNewDNSProviderWithToken(t *testing.T) { require.NotNil(t, p) assert.Equal(t, test.expected.dnsToken, p.config.AuthToken) assert.Equal(t, test.expected.zoneToken, p.config.ZoneToken) - if test.expected.sameClient { assert.Equal(t, p.client.clientRead, p.client.clientEdit) } else { @@ -280,7 +275,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -294,7 +288,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -311,7 +304,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.AuthEmail = "foo@example.com" config.AuthKey = "secret" config.BaseURL = server.URL - config.HTTPClient = server.Client() return NewDNSProviderConfig(config) }, diff --git a/providers/dns/cloudflare/internal/client.go b/providers/dns/cloudflare/internal/client.go index b63612ce2..495ba5618 100644 --- a/providers/dns/cloudflare/internal/client.go +++ b/providers/dns/cloudflare/internal/client.go @@ -17,7 +17,6 @@ import ( "net/url" "time" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" ) @@ -62,8 +61,6 @@ func NewClient(opts ...Option) (*Client, error) { return nil, errors.New("invalid credentials: authEmail and authKey must be set together") } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return client, nil } @@ -87,7 +84,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, zoneID string, record Reco return &result.Result, nil } -// DeleteDNSRecord deletes DNS record. +// DeleteDNSRecord Delete DNS record. // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/ func (c *Client) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { endpoint := c.baseURL.JoinPath("zones", zoneID, "dns_records", recordID) @@ -100,7 +97,6 @@ func (c *Client) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) e return c.do(req, nil) } -// ZonesByName returns a list of zones matching the given name. // https://developers.cloudflare.com/api/resources/zones/methods/list/ func (c *Client) ZonesByName(ctx context.Context, name string) ([]Zone, error) { endpoint := c.baseURL.JoinPath("zones") @@ -192,7 +188,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIResponse[any] - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/cloudflare/internal/types.go b/providers/dns/cloudflare/internal/types.go index 50a7bbbf9..2b6d2e2b6 100644 --- a/providers/dns/cloudflare/internal/types.go +++ b/providers/dns/cloudflare/internal/types.go @@ -1,9 +1,6 @@ package internal -import ( - "fmt" - "strings" -) +import "fmt" type Record struct { ID string `json:"id,omitempty"` @@ -42,17 +39,17 @@ type ErrorChain struct { type Errors []Message func (e Errors) Error() string { - msg := new(strings.Builder) + var msg string for _, item := range e { - _, _ = fmt.Fprintf(msg, "%d: %s", item.Code, item.Message) + msg = fmt.Sprintf("%d: %s", item.Code, item.Message) for _, link := range item.ErrorChain { - _, _ = fmt.Fprintf(msg, "; %d: %s", link.Code, link.Message) + msg += fmt.Sprintf("; %d: %s", link.Code, link.Message) } } - return msg.String() + return msg } type ResultInfo struct { diff --git a/providers/dns/cloudflare/wrapper.go b/providers/dns/cloudflare/wrapper.go index 286c20ecd..1ab36800d 100644 --- a/providers/dns/cloudflare/wrapper.go +++ b/providers/dns/cloudflare/wrapper.go @@ -99,7 +99,6 @@ func (m *metaClient) ZoneIDByName(ctx context.Context, fdqn string) (string, err m.zonesMu.Lock() m.zones[fdqn] = id m.zonesMu.Unlock() - return id, nil } diff --git a/providers/dns/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index 916d73bde..ef6524c4d 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -8,14 +8,12 @@ import ( "net/http" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/cloudns/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -68,7 +66,6 @@ type DNSProvider struct { // CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD. func NewDNSProvider() (*DNSProvider, error) { var subAuthID string - authID := env.GetOrFile(EnvAuthID) if authID == "" { subAuthID = env.GetOrFile(EnvSubAuthID) @@ -102,11 +99,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("ClouDNS: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + client.HTTPClient = config.HTTPClient return &DNSProvider{client: client, config: config}, nil } @@ -169,22 +162,14 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // waitNameservers At the time of writing 4 servers are found as authoritative, but 8 are reported during the sync. // If this is not done, the secondary verification done by Let's Encrypt server will fail quire a bit. func (d *DNSProvider) waitNameservers(ctx context.Context, domain string, zone *internal.Zone) error { - return wait.Retry(ctx, - func() error { - syncProgress, err := d.client.GetUpdateStatus(ctx, zone.Name) - if err != nil { - return fmt.Errorf("nameserver sync on %s: %w", domain, err) - } + return wait.For("Nameserver sync on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { + syncProgress, err := d.client.GetUpdateStatus(ctx, zone.Name) + if err != nil { + return false, err + } - log.Infof("[%s] Sync %d/%d complete", domain, syncProgress.Updated, syncProgress.Total) + log.Infof("[%s] Sync %d/%d complete", domain, syncProgress.Updated, syncProgress.Total) - if !syncProgress.Complete { - return fmt.Errorf("nameserver sync on %s not complete", domain) - } - - return nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), - ) + return syncProgress.Complete, nil + }) } 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/cloudns/cloudns_test.go b/providers/dns/cloudns/cloudns_test.go index 024bd93d8..ea4f25c95 100644 --- a/providers/dns/cloudns/cloudns_test.go +++ b/providers/dns/cloudns/cloudns_test.go @@ -79,7 +79,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -170,7 +169,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -184,7 +182,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cloudns/internal/client.go b/providers/dns/cloudns/internal/client.go index 278b8de49..60d7e6bbe 100644 --- a/providers/dns/cloudns/internal/client.go +++ b/providers/dns/cloudns/internal/client.go @@ -171,7 +171,6 @@ func (c *Client) ListTxtRecords(ctx context.Context, zoneName, fqdn string) ([]T } var records []TXTRecord - for _, record := range raw { if record.Host == subDomain && record.Type == "TXT" { records = append(records, record) @@ -280,7 +279,6 @@ func (c *Client) GetUpdateStatus(ctx context.Context, zoneName string) (*SyncPro } updatedCount := 0 - for _, record := range records { if record.Updated { updatedCount++ diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index b9f6c5431..dbfa32aee 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -19,7 +19,6 @@ func setupClient(subAuthID string) func(server *httptest.Server) (*Client, error client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() - return client, nil } } diff --git a/providers/dns/cloudru/cloudru.go b/providers/dns/cloudru/cloudru.go index dd597952a..314c20445 100644 --- a/providers/dns/cloudru/cloudru.go +++ b/providers/dns/cloudru/cloudru.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/cloudru/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -61,9 +60,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 } @@ -101,8 +99,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/cloudru/cloudru_test.go b/providers/dns/cloudru/cloudru_test.go index 3e506cb1c..88addde93 100644 --- a/providers/dns/cloudru/cloudru_test.go +++ b/providers/dns/cloudru/cloudru_test.go @@ -67,7 +67,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -154,7 +153,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -168,7 +166,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cloudru/internal/client.go b/providers/dns/cloudru/internal/client.go index a00ae6ea8..cb62c5bca 100644 --- a/providers/dns/cloudru/internal/client.go +++ b/providers/dns/cloudru/internal/client.go @@ -61,7 +61,6 @@ func (c *Client) GetZones(ctx context.Context, parentID string) ([]Zone, error) } var zones APIResponse[Zone] - err = c.do(req, &zones) if err != nil { return nil, err @@ -79,7 +78,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]Record, error } var records APIResponse[Record] - err = c.do(req, &records) if err != nil { return nil, err @@ -97,7 +95,6 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record Record) } var result Record - err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/cloudru/internal/identity.go b/providers/dns/cloudru/internal/identity.go index 3bb09f3fa..79df3c297 100644 --- a/providers/dns/cloudru/internal/identity.go +++ b/providers/dns/cloudru/internal/identity.go @@ -49,7 +49,6 @@ func (c *Client) obtainToken(ctx context.Context) (*Token, error) { } tok := Token{} - err = json.Unmarshal(raw, &tok) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -89,7 +88,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &authResponseError{} - err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/cloudru/internal/types.go b/providers/dns/cloudru/internal/types.go index 713fd459a..d233c73bc 100644 --- a/providers/dns/cloudru/internal/types.go +++ b/providers/dns/cloudru/internal/types.go @@ -38,9 +38,9 @@ type Zone struct { Valid bool `json:"valid,omitempty"` ValidationText string `json:"validationText,omitempty"` Delegated bool `json:"delegated,omitempty"` - LastCheck time.Time `json:"lastCheck,omitzero"` - CreatedAt time.Time `json:"created_at,omitzero"` - UpdatedAt time.Time `json:"updated_at,omitzero"` + LastCheck time.Time `json:"lastCheck,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` } type Record struct { 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/conoha/conoha.go b/providers/dns/conoha/conoha.go index f7658647c..aa6c68ce9 100644 --- a/providers/dns/conoha/conoha.go +++ b/providers/dns/conoha/conoha.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/conoha/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -99,8 +98,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } - identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) - auth := internal.Auth{ TenantID: config.TenantID, PasswordCredentials: internal.PasswordCredentials{ @@ -123,8 +120,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/conoha/conoha_test.go b/providers/dns/conoha/conoha_test.go index c1c445d48..9db5ba79f 100644 --- a/providers/dns/conoha/conoha_test.go +++ b/providers/dns/conoha/conoha_test.go @@ -72,7 +72,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -156,7 +155,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -170,7 +168,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/conoha/internal/client.go b/providers/dns/conoha/internal/client.go index 2f039489b..60d7fd6dc 100644 --- a/providers/dns/conoha/internal/client.go +++ b/providers/dns/conoha/internal/client.go @@ -124,7 +124,6 @@ func (c *Client) createRecord(ctx context.Context, domainID string, record Recor } newRecord := &Record{} - err = c.do(req, newRecord) if err != nil { return nil, err diff --git a/providers/dns/conoha/internal/client_test.go b/providers/dns/conoha/internal/client_test.go index 5e06ffc1d..0b9242c08 100644 --- a/providers/dns/conoha/internal/client_test.go +++ b/providers/dns/conoha/internal/client_test.go @@ -97,7 +97,6 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusBadRequest) return } - defer func() { _ = req.Body.Close() }() if string(bytes.TrimSpace(raw)) != `{"name":"lego.com.","type":"TXT","data":"txtTXTtxt","ttl":300}` { @@ -110,7 +109,6 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) return } - defer func() { _ = file.Close() }() _, _ = io.Copy(rw, file) diff --git a/providers/dns/conohav3/conohav3.go b/providers/dns/conohav3/conohav3.go index c1eace827..a6cb12cb1 100644 --- a/providers/dns/conohav3/conohav3.go +++ b/providers/dns/conohav3/conohav3.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/conohav3/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -99,8 +98,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } - identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) - auth := internal.Auth{ Identity: internal.Identity{ Methods: []string{"password"}, @@ -132,8 +129,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/conohav3/conohav3.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/conohav3/conohav3_test.go b/providers/dns/conohav3/conohav3_test.go index d68ea3ebb..7bba8f0b5 100644 --- a/providers/dns/conohav3/conohav3_test.go +++ b/providers/dns/conohav3/conohav3_test.go @@ -72,7 +72,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -156,7 +155,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -170,7 +168,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/conohav3/internal/client.go b/providers/dns/conohav3/internal/client.go index 2a9e7c2bc..fcbd7f5ac 100644 --- a/providers/dns/conohav3/internal/client.go +++ b/providers/dns/conohav3/internal/client.go @@ -124,7 +124,6 @@ func (c *Client) createRecord(ctx context.Context, domainID string, record Recor } newRecord := &Record{} - err = c.do(req, newRecord) if err != nil { return nil, err diff --git a/providers/dns/conohav3/internal/client_test.go b/providers/dns/conohav3/internal/client_test.go index 66babae49..babdadf7e 100644 --- a/providers/dns/conohav3/internal/client_test.go +++ b/providers/dns/conohav3/internal/client_test.go @@ -98,7 +98,6 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusBadRequest) return } - defer func() { _ = req.Body.Close() }() if string(bytes.TrimSpace(raw)) != `{"name":"lego.com.","type":"TXT","data":"txtTXTtxt","ttl":300}` { @@ -111,7 +110,6 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) return } - defer func() { _ = file.Close() }() _, _ = io.Copy(rw, file) diff --git a/providers/dns/conohav3/internal/identity.go b/providers/dns/conohav3/internal/identity.go index 6a9ad7f1e..3bb7355ae 100644 --- a/providers/dns/conohav3/internal/identity.go +++ b/providers/dns/conohav3/internal/identity.go @@ -53,7 +53,6 @@ func (c *Identifier) do(req *http.Request) (string, error) { if err != nil { return "", errutils.NewHTTPDoError(req, err) } - defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated { diff --git a/providers/dns/constellix/constellix.go b/providers/dns/constellix/constellix.go index 777e93308..f981b4974 100644 --- a/providers/dns/constellix/constellix.go +++ b/providers/dns/constellix/constellix.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/constellix/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/hashicorp/go-retryablehttp" ) @@ -97,7 +96,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { retryClient.HTTPClient = tr.Wrap(config.HTTPClient) retryClient.Backoff = backoff - client := internal.NewClient(clientdebug.Wrap(retryClient.StandardClient())) + client := internal.NewClient(retryClient.StandardClient()) return &DNSProvider{config: config, client: client}, nil } @@ -200,7 +199,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("constellix: failed to delete TXT records: %w", err) } - return nil } diff --git a/providers/dns/constellix/constellix.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/constellix/constellix_test.go b/providers/dns/constellix/constellix_test.go index e38258292..e3a30caca 100644 --- a/providers/dns/constellix/constellix_test.go +++ b/providers/dns/constellix/constellix_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,7 +129,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,7 +142,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/constellix/internal/auth.go b/providers/dns/constellix/internal/auth.go index 9193572eb..1a136012d 100644 --- a/providers/dns/constellix/internal/auth.go +++ b/providers/dns/constellix/internal/auth.go @@ -28,7 +28,6 @@ func NewTokenTransport(apiKey, secretKey string) (*TokenTransport, error) { if apiKey == "" { return nil, errors.New("credentials missing: API key") } - if secretKey == "" { return nil, errors.New("credentials missing: secret key") } @@ -58,7 +57,6 @@ func (t *TokenTransport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } - return http.DefaultTransport } diff --git a/providers/dns/constellix/internal/domains.go b/providers/dns/constellix/internal/domains.go index fa7027f55..485f0d537 100644 --- a/providers/dns/constellix/internal/domains.go +++ b/providers/dns/constellix/internal/domains.go @@ -30,12 +30,10 @@ func (s *DomainService) GetAll(ctx context.Context, params *PaginationParameters if errQ != nil { return nil, errQ } - req.URL.RawQuery = v.Encode() } var domains []Domain - err = s.client.do(req, &domains) if err != nil { return nil, err @@ -80,7 +78,6 @@ func (s *DomainService) Search(ctx context.Context, filter searchFilter, value s req.URL.RawQuery = query.Encode() var domains []Domain - err = s.client.do(req, &domains) if err != nil { var nf *NotFound diff --git a/providers/dns/constellix/internal/domains_test.go b/providers/dns/constellix/internal/domains_test.go index 468db4613..2d92fb8f3 100644 --- a/providers/dns/constellix/internal/domains_test.go +++ b/providers/dns/constellix/internal/domains_test.go @@ -30,10 +30,10 @@ func TestDomainService_GetAll(t *testing.T) { require.NoError(t, err) expected := []Domain{ - {ID: 273301, Name: "aaa.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273302, Name: "bbb.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273303, Name: "ccc.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273304, Name: "ddd.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273301, Name: "aaa.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "bbb.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273303, Name: "ccc.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273304, Name: "ddd.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) @@ -44,14 +44,14 @@ func TestDomainService_Search(t *testing.T) { Route("GET /v1/domains/search", servermock.ResponseFromFixture("domains-Search.json"), servermock.CheckQueryParameter().Strict(). - With("exact", "example.com")). + With("exact", "lego.wtf")). Build(t) - data, err := client.Domains.Search(t.Context(), Exact, "example.com") + data, err := client.Domains.Search(t.Context(), Exact, "lego.wtf") require.NoError(t, err) expected := []Domain{ - {ID: 273302, Name: "example.com", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "lego.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) diff --git a/providers/dns/constellix/internal/fixtures/domains-GetAll.json b/providers/dns/constellix/internal/fixtures/domains-GetAll.json index 8ccb4e52c..5ff2ad41d 100644 --- a/providers/dns/constellix/internal/fixtures/domains-GetAll.json +++ b/providers/dns/constellix/internal/fixtures/domains-GetAll.json @@ -1,7 +1,7 @@ [ { "id": 273301, - "name": "aaa.example", + "name": "aaa.wtf", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -36,7 +36,7 @@ }, { "id": 273302, - "name": "bbb.example", + "name": "bbb.wtf", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -71,7 +71,7 @@ }, { "id": 273303, - "name": "ccc.example", + "name": "ccc.wtf", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -106,7 +106,7 @@ }, { "id": 273304, - "name": "ddd.example", + "name": "ddd.wtf", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/constellix/internal/fixtures/domains-Search.json b/providers/dns/constellix/internal/fixtures/domains-Search.json index c33272515..5d018a39a 100644 --- a/providers/dns/constellix/internal/fixtures/domains-Search.json +++ b/providers/dns/constellix/internal/fixtures/domains-Search.json @@ -1,7 +1,7 @@ [ { "id": 273302, - "name": "example.com", + "name": "lego.wtf", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/constellix/internal/txtrecords.go b/providers/dns/constellix/internal/txtrecords.go index bd00d84b7..7880da4d2 100644 --- a/providers/dns/constellix/internal/txtrecords.go +++ b/providers/dns/constellix/internal/txtrecords.go @@ -32,7 +32,6 @@ func (s *TxtRecordService) Create(ctx context.Context, domainID int64, record Re } var records []Record - err = s.client.do(req, &records) if err != nil { return nil, err @@ -55,7 +54,6 @@ func (s *TxtRecordService) GetAll(ctx context.Context, domainID int64) ([]Record } var records []Record - err = s.client.do(req, &records) if err != nil { return nil, err @@ -78,7 +76,6 @@ func (s *TxtRecordService) Get(ctx context.Context, domainID, recordID int64) (* } var records Record - err = s.client.do(req, &records) if err != nil { return nil, err @@ -106,7 +103,6 @@ func (s *TxtRecordService) Update(ctx context.Context, domainID, recordID int64, } var msg SuccessMessage - err = s.client.do(req, &msg) if err != nil { return nil, err @@ -129,7 +125,6 @@ func (s *TxtRecordService) Delete(ctx context.Context, domainID, recordID int64) } var msg *SuccessMessage - err = s.client.do(req, &msg) if err != nil { return nil, err diff --git a/providers/dns/corenetworks/corenetworks.go b/providers/dns/corenetworks/corenetworks.go index cde58a2bf..119b3c16b 100644 --- a/providers/dns/corenetworks/corenetworks.go +++ b/providers/dns/corenetworks/corenetworks.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/corenetworks/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -91,8 +90,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/corenetworks/corenetworks_test.go b/providers/dns/corenetworks/corenetworks_test.go index 911693468..3cd80f88d 100644 --- a/providers/dns/corenetworks/corenetworks_test.go +++ b/providers/dns/corenetworks/corenetworks_test.go @@ -43,7 +43,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -112,7 +111,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -126,7 +124,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/corenetworks/internal/client.go b/providers/dns/corenetworks/internal/client.go index bdc17f2c1..ea2d7efa2 100644 --- a/providers/dns/corenetworks/internal/client.go +++ b/providers/dns/corenetworks/internal/client.go @@ -47,7 +47,6 @@ func (c *Client) ListZone(ctx context.Context) ([]Zone, error) { } var zones []Zone - err = c.do(req, &zones) if err != nil { return nil, err @@ -67,7 +66,6 @@ func (c *Client) GetZoneDetails(ctx context.Context, zone string) (*ZoneDetails, } var details ZoneDetails - err = c.do(req, &details) if err != nil { return nil, err @@ -87,7 +85,6 @@ func (c *Client) ListRecords(ctx context.Context, zone string) ([]Record, error) } var records []Record - err = c.do(req, &records) if err != nil { return nil, err diff --git a/providers/dns/corenetworks/internal/identity.go b/providers/dns/corenetworks/internal/identity.go index a7e7448c0..8f5a2ee9a 100644 --- a/providers/dns/corenetworks/internal/identity.go +++ b/providers/dns/corenetworks/internal/identity.go @@ -22,7 +22,6 @@ func (c *Client) CreateAuthenticationToken(ctx context.Context) (*Token, error) } var token Token - err = c.do(req, &token) if err != nil { return nil, err diff --git a/providers/dns/cpanel/cpanel.go b/providers/dns/cpanel/cpanel.go index f335c0a8c..4c80e4db8 100644 --- a/providers/dns/cpanel/cpanel.go +++ b/providers/dns/cpanel/cpanel.go @@ -17,7 +17,6 @@ import ( "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/cpanel" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/shared" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/whm" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -147,16 +146,12 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { valueB64 := base64.StdEncoding.EncodeToString([]byte(info.Value)) - var ( - found bool - existingRecord shared.ZoneRecord - ) - + var found bool + var existingRecord shared.ZoneRecord for _, record := range zoneInfo { if slices.Contains(record.DataB64, valueB64) { existingRecord = record found = true - break } } @@ -225,16 +220,12 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { valueB64 := base64.StdEncoding.EncodeToString([]byte(info.Value)) - var ( - found bool - existingRecord shared.ZoneRecord - ) - + var found bool + var existingRecord shared.ZoneRecord for _, record := range zoneInfo { if slices.Contains(record.DataB64, valueB64) { existingRecord = record found = true - break } } @@ -244,7 +235,6 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { } var newData []string - for _, dataB64 := range existingRecord.DataB64 { if dataB64 == valueB64 { continue @@ -301,7 +291,6 @@ func getZoneSerial(zoneFqdn string, zoneInfo []shared.ZoneRecord) (uint32, error } var newSerial uint32 - _, err = fmt.Sscan(string(data), &newSerial) if err != nil { return 0, fmt.Errorf("decode serial DNameB64, invalid serial value %q: %w", string(data), err) @@ -325,8 +314,6 @@ func createClient(config *Config) (apiClient, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return client, nil case "whm": @@ -339,8 +326,6 @@ func createClient(config *Config) (apiClient, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return client, nil default: diff --git a/providers/dns/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/cpanel/cpanel_test.go b/providers/dns/cpanel/cpanel_test.go index 5d85b8b5b..614b9e1c7 100644 --- a/providers/dns/cpanel/cpanel_test.go +++ b/providers/dns/cpanel/cpanel_test.go @@ -75,7 +75,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -283,7 +282,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -297,7 +295,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cpanel/internal/cpanel/types.go b/providers/dns/cpanel/internal/cpanel/types.go index 0a3053647..cb4dbd535 100644 --- a/providers/dns/cpanel/internal/cpanel/types.go +++ b/providers/dns/cpanel/internal/cpanel/types.go @@ -6,7 +6,7 @@ import ( ) type APIResponse[T any] struct { - Metadata Metadata `json:"metadata"` + Metadata Metadata `json:"metadata,omitempty"` Data T `json:"data,omitempty"` Status int `json:"status,omitempty"` diff --git a/providers/dns/cpanel/internal/whm/types.go b/providers/dns/cpanel/internal/whm/types.go index d0604a565..f1884a04d 100644 --- a/providers/dns/cpanel/internal/whm/types.go +++ b/providers/dns/cpanel/internal/whm/types.go @@ -7,7 +7,7 @@ import ( ) type APIResponse[T any] struct { - Metadata Metadata `json:"metadata"` + Metadata Metadata `json:"metadata,omitempty"` Data T `json:"data,omitempty"` } 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.go b/providers/dns/derak/derak.go index 78165b936..6e726620a 100644 --- a/providers/dns/derak/derak.go +++ b/providers/dns/derak/derak.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/derak/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" ) @@ -95,8 +94,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -163,7 +160,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("derak: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/derak/derak.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/derak/derak_test.go b/providers/dns/derak/derak_test.go index b83eb2c8c..e58cfb6c1 100644 --- a/providers/dns/derak/derak_test.go +++ b/providers/dns/derak/derak_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/derak/internal/client.go b/providers/dns/derak/internal/client.go index 4352e198b..a7c11f895 100644 --- a/providers/dns/derak/internal/client.go +++ b/providers/dns/derak/internal/client.go @@ -44,7 +44,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, params *GetRecor if err != nil { return nil, err } - endpoint.RawQuery = v.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -53,7 +52,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, params *GetRecor } response := &GetRecordsResponse{} - err = c.do(req, response) if err != nil { return nil, err @@ -72,7 +70,6 @@ func (c *Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Recor } response := &Record{} - err = c.do(req, response) if err != nil { return nil, err @@ -91,7 +88,6 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record Record) } response := &Record{} - err = c.do(req, response) if err != nil { return nil, err @@ -110,7 +106,6 @@ func (c *Client) EditRecord(ctx context.Context, zoneID, recordID string, record } response := &Record{} - err = c.do(req, response) if err != nil { return nil, err @@ -152,7 +147,6 @@ func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { } response := &APIResponse[[]Zone]{} - err = c.do(req, response) if err != nil { return nil, err @@ -227,7 +221,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIResponse[any] - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/derak/internal/types.go b/providers/dns/derak/internal/types.go index 02116314f..15ed00617 100644 --- a/providers/dns/derak/internal/types.go +++ b/providers/dns/derak/internal/types.go @@ -46,7 +46,7 @@ type Zone struct { HumanReadable string `json:"humanReadable,omitempty"` Serial string `json:"serial,omitempty"` CreationTime int64 `json:"creationTime,omitempty"` - CreationTimeDate time.Time `json:"creationTimeDate,omitzero"` + CreationTimeDate time.Time `json:"creationTimeDate,omitempty"` Status string `json:"status,omitempty"` IsMoved bool `json:"is_moved,omitempty"` Paused bool `json:"paused,omitempty"` diff --git a/providers/dns/desec/desec.go b/providers/dns/desec/desec.go index 9cc54f65e..9d1e20e53 100644 --- a/providers/dns/desec/desec.go +++ b/providers/dns/desec/desec.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/desec" ) @@ -89,9 +88,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.HTTPClient != nil { opts.HTTPClient = config.HTTPClient } - - opts.HTTPClient = clientdebug.Wrap(opts.HTTPClient) - opts.Logger = log.Default() client := desec.New(config.Token, opts) @@ -180,7 +176,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } records := make([]string, 0) - for _, record := range rrSet.Records { if record != fmt.Sprintf(`%q`, info.Value) { records = append(records, record) diff --git a/providers/dns/desec/desec.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/desec/desec_test.go b/providers/dns/desec/desec_test.go index 93d9bd010..f91f9e82a 100644 --- a/providers/dns/desec/desec_test.go +++ b/providers/dns/desec/desec_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,7 +93,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,7 +106,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/designate/designate.go b/providers/dns/designate/designate.go index 41bf251f6..c58baaace 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 } @@ -86,6 +85,7 @@ func NewDNSProvider() (*DNSProvider, error) { opts, erro := clientconfig.AuthOptions(&clientconfig.ClientOpts{ Cloud: val[EnvCloud], }) + if erro != nil { return nil, fmt.Errorf("designate: %w", erro) } @@ -202,7 +202,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("designate: error for %s in CleanUp: %w", info.EffectiveFQDN, err) } - return nil } @@ -242,7 +241,6 @@ func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) e } result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts) - return result.Err } diff --git a/providers/dns/designate/designate.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/designate/designate_test.go b/providers/dns/designate/designate_test.go index e5edf81f8..1045baa95 100644 --- a/providers/dns/designate/designate_test.go +++ b/providers/dns/designate/designate_test.go @@ -105,7 +105,6 @@ func TestNewDNSProvider_fromEnv(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -193,7 +192,6 @@ func TestNewDNSProvider_fromCloud(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(map[string]string{ @@ -333,7 +331,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -347,7 +344,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index 26c6fb9d4..0b68aa5c9 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/digitalocean/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -89,15 +88,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("digitalocean: credentials missing") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), - ), - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken)) if config.BaseURL != "" { var err error - client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("digitalocean: %w", err) @@ -153,7 +147,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("digitalocean: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/digitalocean/digitalocean.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/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index d066e12db..a01906812 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -51,7 +51,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -139,5 +138,5 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider.recordIDsMu.Unlock() err := provider.CleanUp("example.com", "token", "") - require.NoError(t, err) + require.NoError(t, err, "fail to remove TXT record") } diff --git a/providers/dns/digitalocean/internal/client.go b/providers/dns/digitalocean/internal/client.go index 395de478c..e7dd181b2 100644 --- a/providers/dns/digitalocean/internal/client.go +++ b/providers/dns/digitalocean/internal/client.go @@ -45,7 +45,6 @@ func (c *Client) AddTxtRecord(ctx context.Context, zone string, record Record) ( } respData := &TxtRecordResponse{} - err = c.do(req, respData) if err != nil { return nil, err @@ -121,7 +120,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errInfo APIError - err := json.Unmarshal(raw, &errInfo) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/directadmin/directadmin.go b/providers/dns/directadmin/directadmin.go index 8dfa132ae..de9b14945 100644 --- a/providers/dns/directadmin/directadmin.go +++ b/providers/dns/directadmin/directadmin.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/directadmin/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -100,8 +99,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/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/directadmin/directadmin_test.go b/providers/dns/directadmin/directadmin_test.go index aed3ba505..10c079f73 100644 --- a/providers/dns/directadmin/directadmin_test.go +++ b/providers/dns/directadmin/directadmin_test.go @@ -59,7 +59,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -136,7 +135,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -150,7 +148,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/directadmin/internal/client.go b/providers/dns/directadmin/internal/client.go index 64409a79d..bf6d64371 100644 --- a/providers/dns/directadmin/internal/client.go +++ b/providers/dns/directadmin/internal/client.go @@ -94,7 +94,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errInfo APIError - err := json.Unmarshal(raw, &errInfo) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/dns_providers_test.go b/providers/dns/dns_providers_test.go index 3b82784b4..1f39e2bdd 100644 --- a/providers/dns/dns_providers_test.go +++ b/providers/dns/dns_providers_test.go @@ -13,7 +13,6 @@ var envTest = tester.NewEnvTest("EXEC_PATH") func TestKnownDNSProviderSuccess(t *testing.T) { defer envTest.RestoreEnv() - envTest.Apply(map[string]string{ "EXEC_PATH": "abc", }) @@ -27,7 +26,6 @@ func TestKnownDNSProviderSuccess(t *testing.T) { func TestKnownDNSProviderError(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider, err := NewDNSChallengeProviderByName("exec") 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.go b/providers/dns/dnshomede/dnshomede.go index c76ed6de2..91b0b11e3 100644 --- a/providers/dns/dnshomede/dnshomede.go +++ b/providers/dns/dnshomede/dnshomede.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dnshomede/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -57,7 +56,6 @@ type DNSProvider struct { // Credentials must be passed in the environment variable: DNSHOMEDE_CREDENTIALS. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() - values, err := env.Get(EnvCredentials) if err != nil { return nil, fmt.Errorf("dnshomede: %w", err) @@ -94,12 +92,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Credentials) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/dnshomede/dnshomede_test.go b/providers/dns/dnshomede/dnshomede_test.go index 5035a7837..bdb42f172 100644 --- a/providers/dns/dnshomede/dnshomede_test.go +++ b/providers/dns/dnshomede/dnshomede_test.go @@ -69,7 +69,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -145,7 +144,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -159,7 +157,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index adf7d48e2..737f214e9 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "golang.org/x/oauth2" ) @@ -80,14 +79,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("dnsimple: OAuth token is missing") } - client := dnsimple.NewClient( - clientdebug.Wrap( - oauth2.NewClient( - context.Background(), - oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken}), - ), - ), - ) + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken}) + client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts)) client.SetUserAgent(useragent.Get()) if config.BaseURL != "" { @@ -101,16 +94,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - zoneName, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneName, err := d.getHostedZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("dnsimple: %w", err) } - accountID, err := d.getAccountID(ctx) + accountID, err := d.getAccountID() if err != nil { return fmt.Errorf("dnsimple: %w", err) } @@ -120,7 +111,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("dnsimple: %w", err) } - _, err = d.client.Zones.CreateRecord(ctx, accountID, zoneName, recordAttributes) + _, err = d.client.Zones.CreateRecord(context.Background(), accountID, zoneName, recordAttributes) if err != nil { return fmt.Errorf("dnsimple: API call failed: %w", err) } @@ -130,24 +121,21 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - records, err := d.findTxtRecords(ctx, info.EffectiveFQDN) + records, err := d.findTxtRecords(info.EffectiveFQDN) if err != nil { return fmt.Errorf("dnsimple: %w", err) } - accountID, err := d.getAccountID(ctx) + accountID, err := d.getAccountID() if err != nil { return fmt.Errorf("dnsimple: %w", err) } var lastErr error - for _, rec := range records { - _, err := d.client.Zones.DeleteRecord(ctx, accountID, rec.ZoneID, rec.ID) + _, err := d.client.Zones.DeleteRecord(context.Background(), accountID, rec.ZoneID, rec.ID) if err != nil { lastErr = fmt.Errorf("dnsimple: %w", err) } @@ -162,18 +150,18 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, error) { +func (d *DNSProvider) getHostedZone(domain string) (string, error) { authZone, err := dns01.FindZoneByFqdn(domain) if err != nil { return "", fmt.Errorf("could not find zone for FQDN %q: %w", domain, err) } - accountID, err := d.getAccountID(ctx) + accountID, err := d.getAccountID() if err != nil { return "", err } - hostedZone, err := d.client.Zones.GetZone(ctx, accountID, dns01.UnFqdn(authZone)) + hostedZone, err := d.client.Zones.GetZone(context.Background(), accountID, dns01.UnFqdn(authZone)) if err != nil { return "", fmt.Errorf("get zone: %w", err) } @@ -185,13 +173,13 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, return hostedZone.Data.Name, nil } -func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]dnsimple.ZoneRecord, error) { - zoneName, err := d.getHostedZone(ctx, fqdn) +func (d *DNSProvider) findTxtRecords(fqdn string) ([]dnsimple.ZoneRecord, error) { + zoneName, err := d.getHostedZone(fqdn) if err != nil { return nil, err } - accountID, err := d.getAccountID(ctx) + accountID, err := d.getAccountID() if err != nil { return nil, err } @@ -201,7 +189,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]dnsimp return nil, err } - result, err := d.client.Zones.ListRecords(ctx, accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: &subDomain, Type: dnsimple.String("TXT"), ListOptions: dnsimple.ListOptions{}}) + result, err := d.client.Zones.ListRecords(context.Background(), accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: &subDomain, Type: dnsimple.String("TXT"), ListOptions: dnsimple.ListOptions{}}) if err != nil { return nil, fmt.Errorf("API call has failed: %w", err) } @@ -223,8 +211,8 @@ func newTxtRecord(zoneName, fqdn, value string, ttl int) (dnsimple.ZoneRecordAtt }, nil } -func (d *DNSProvider) getAccountID(ctx context.Context) (string, error) { - whoamiResponse, err := d.client.Identity.Whoami(ctx) +func (d *DNSProvider) getAccountID() (string, error) { + whoamiResponse, err := d.client.Identity.Whoami(context.Background()) if err != nil { return "", err } 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/dnsimple/dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go index 2a52dd2de..c07f965b4 100644 --- a/providers/dns/dnsimple/dnsimple_test.go +++ b/providers/dns/dnsimple/dnsimple_test.go @@ -51,7 +51,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 69f2096fb..fcfe6714c 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -15,7 +15,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -113,12 +112,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("dnsmadeeasy: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - + client.HTTPClient = config.HTTPClient client.BaseURL, err = url.Parse(baseURL) if err != nil { return nil, err @@ -155,7 +149,6 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { if err != nil { return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %w", name, err) } - return nil } @@ -178,7 +171,6 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { // find matching records name := strings.Replace(info.EffectiveFQDN, "."+authZone, "", 1) - records, err := d.client.GetRecords(ctx, domain, name, "TXT") if err != nil { return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %w", domain.Name, err) @@ -186,7 +178,6 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { // delete records var lastError error - for _, record := range *records { err = d.client.DeleteRecord(ctx, record) if err != nil { diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.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/dnsmadeeasy/dnsmadeeasy_test.go b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go index f6fc2e273..5c508e60d 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go @@ -59,7 +59,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -136,7 +135,6 @@ func TestLivePresentAndCleanup(t *testing.T) { os.Setenv(EnvSandbox, "true") envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dnsmadeeasy/internal/client.go b/providers/dns/dnsmadeeasy/internal/client.go index 7963ad614..cb6f9d2cb 100644 --- a/providers/dns/dnsmadeeasy/internal/client.go +++ b/providers/dns/dnsmadeeasy/internal/client.go @@ -68,7 +68,6 @@ func (c *Client) GetDomain(ctx context.Context, authZone string) (*Domain, error } domain := &Domain{} - err = c.do(req, domain) if err != nil { return nil, err @@ -92,7 +91,6 @@ func (c *Client) GetRecords(ctx context.Context, domain *Domain, recordName, rec } records := &recordsResponse{} - err = c.do(req, records) if err != nil { return nil, err @@ -174,12 +172,10 @@ func (c *Client) sign(req *http.Request, timestamp string) error { func computeHMAC(message, secret string) (string, error) { key := []byte(secret) h := hmac.New(sha1.New, key) - _, err := h.Write([]byte(message)) if err != nil { return "", err } - return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index 52a873c7b..ab8f20c8d 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/dnspod-go" ) @@ -83,12 +82,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { params := dnspod.CommonParams{LoginToken: config.LoginToken, Format: "json"} client := dnspod.NewClient(params) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + client.HTTPClient = config.HTTPClient return &DNSProvider{client: client, config: config}, nil } @@ -135,7 +129,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return err } } - return nil } @@ -157,7 +150,6 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) { } var hostedZone dnspod.Domain - for _, zone := range zones { if zone.Name == dns01.UnFqdn(authZone) { hostedZone = zone @@ -165,7 +157,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 @@ -193,7 +185,6 @@ func (d *DNSProvider) findTxtRecords(fqdn, zoneID, zoneName string) ([]dnspod.Re } var records []dnspod.Record - result, _, err := d.client.Records.List(zoneID, subDomain) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) diff --git a/providers/dns/dnspod/dnspod.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/dnspod/dnspod_test.go b/providers/dns/dnspod/dnspod_test.go index 5d339353a..640ec34c6 100644 --- a/providers/dns/dnspod/dnspod_test.go +++ b/providers/dns/dnspod/dnspod_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,7 +96,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -111,7 +109,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dode/dode.go b/providers/dns/dode/dode.go index 59ad785e8..9f307f046 100644 --- a/providers/dns/dode/dode.go +++ b/providers/dns/dode/dode.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dode/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,8 +85,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/dode/dode_test.go b/providers/dns/dode/dode_test.go index fefcc79b1..3d8e9395a 100644 --- a/providers/dns/dode/dode_test.go +++ b/providers/dns/dode/dode_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,7 +93,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,7 +106,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dode/internal/client.go b/providers/dns/dode/internal/client.go index 6824e7c48..bbd98b399 100644 --- a/providers/dns/dode/internal/client.go +++ b/providers/dns/dode/internal/client.go @@ -70,7 +70,6 @@ func (c *Client) UpdateTxtRecord(ctx context.Context, fqdn, txt string, clearRec } var response apiResponse - err = json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/domeneshop/domeneshop.go b/providers/dns/domeneshop/domeneshop.go index fb16b442e..c194f5608 100644 --- a/providers/dns/domeneshop/domeneshop.go +++ b/providers/dns/domeneshop/domeneshop.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/domeneshop/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,8 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/domeneshop/domeneshop.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/domeneshop/domeneshop_test.go b/providers/dns/domeneshop/domeneshop_test.go index 086efd44a..389975bca 100644 --- a/providers/dns/domeneshop/domeneshop_test.go +++ b/providers/dns/domeneshop/domeneshop_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -131,7 +130,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -145,7 +143,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dreamhost/dreamhost.go b/providers/dns/dreamhost/dreamhost.go index 8663e18ce..5b4960ee0 100644 --- a/providers/dns/dreamhost/dreamhost.go +++ b/providers/dns/dreamhost/dreamhost.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dreamhost/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,8 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.BaseURL != "" { client.BaseURL = config.BaseURL } @@ -99,7 +96,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.client.AddRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), info.Value) if err != nil { return fmt.Errorf("dreamhost: %w", err) diff --git a/providers/dns/dreamhost/dreamhost.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/dreamhost/dreamhost_test.go b/providers/dns/dreamhost/dreamhost_test.go index 5af0b892d..f85e00da4 100644 --- a/providers/dns/dreamhost/dreamhost_test.go +++ b/providers/dns/dreamhost/dreamhost_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -152,7 +151,7 @@ func TestDNSProvider_Cleanup(t *testing.T) { Build(t) err := provider.CleanUp("example.com", "", fakeChallengeToken) - require.NoError(t, err) + require.NoError(t, err, "failed to remove TXT record") } func TestLivePresentAndCleanUp(t *testing.T) { @@ -161,7 +160,6 @@ func TestLivePresentAndCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dreamhost/internal/client.go b/providers/dns/dreamhost/internal/client.go index 02b33ad57..dee808ac8 100644 --- a/providers/dns/dreamhost/internal/client.go +++ b/providers/dns/dreamhost/internal/client.go @@ -101,7 +101,6 @@ func (c *Client) updateTxtRecord(ctx context.Context, endpoint *url.URL) error { } var response apiResponse - err = json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/duckdns/duckdns.go b/providers/dns/duckdns/duckdns.go index 1aae0a06c..687f5bbac 100644 --- a/providers/dns/duckdns/duckdns.go +++ b/providers/dns/duckdns/duckdns.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/duckdns/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,8 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/duckdns/duckdns_test.go b/providers/dns/duckdns/duckdns_test.go index 769513fbf..b89966a36 100644 --- a/providers/dns/duckdns/duckdns_test.go +++ b/providers/dns/duckdns/duckdns_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,7 +94,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,7 +107,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/duckdns/internal/client.go b/providers/dns/duckdns/internal/client.go index c5d7ef97c..ced5bf187 100644 --- a/providers/dns/duckdns/internal/client.go +++ b/providers/dns/duckdns/internal/client.go @@ -81,7 +81,6 @@ func (c *Client) UpdateTxtRecord(ctx context.Context, domain, txt string, clearR if body != "OK" { return fmt.Errorf("request to change TXT record for DuckDNS returned the following result (%s) this does not match expectation (OK) used url [%s]", body, endpoint) } - return nil } @@ -99,7 +98,6 @@ func getMainDomain(domain string) string { } firstSubDomainIndex := split[len(split)-3] - return domain[firstSubDomainIndex:] } diff --git a/providers/dns/dyn/dyn.go b/providers/dns/dyn/dyn.go index 0cd445c39..627626df6 100644 --- a/providers/dns/dyn/dyn.go +++ b/providers/dns/dyn/dyn.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dyn/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -93,8 +92,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/dyn/dyn.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/dyn/dyn_test.go b/providers/dns/dyn/dyn_test.go index 5b4d1c6b6..25f1f5614 100644 --- a/providers/dns/dyn/dyn_test.go +++ b/providers/dns/dyn/dyn_test.go @@ -71,7 +71,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -156,7 +155,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -170,7 +168,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dyn/internal/client.go b/providers/dns/dyn/internal/client.go index a54113eec..83a1bfc0f 100644 --- a/providers/dns/dyn/internal/client.go +++ b/providers/dns/dyn/internal/client.go @@ -127,7 +127,6 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) { } var response APIResponse - err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/dyn/internal/session.go b/providers/dns/dyn/internal/session.go index 088510152..647080fa8 100644 --- a/providers/dns/dyn/internal/session.go +++ b/providers/dns/dyn/internal/session.go @@ -33,7 +33,6 @@ func (c *Client) login(ctx context.Context) (session, error) { } var s session - err = json.Unmarshal(dynRes.Data, &s) if err != nil { return session{}, errutils.NewUnmarshalError(req, http.StatusOK, dynRes.Data, err) diff --git a/providers/dns/dyndnsfree/dyndnsfree.go b/providers/dns/dyndnsfree/dyndnsfree.go index 09be2bfbd..8c1d87aaa 100644 --- a/providers/dns/dyndnsfree/dyndnsfree.go +++ b/providers/dns/dyndnsfree/dyndnsfree.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dyndnsfree/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -82,8 +81,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -110,6 +107,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Records are deleted automatically. + return nil } diff --git a/providers/dns/dyndnsfree/dyndnsfree.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/dyndnsfree/dyndnsfree_test.go b/providers/dns/dyndnsfree/dyndnsfree_test.go index 0b03bd27f..cb063a029 100644 --- a/providers/dns/dyndnsfree/dyndnsfree_test.go +++ b/providers/dns/dyndnsfree/dyndnsfree_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dynu/dynu.go b/providers/dns/dynu/dynu.go index 11df45281..af602ddfc 100644 --- a/providers/dns/dynu/dynu.go +++ b/providers/dns/dynu/dynu.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dynu/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,8 +86,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := internal.NewClient() - - client.HTTPClient = clientdebug.Wrap(tr.Wrap(config.HTTPClient)) + client.HTTPClient = tr.Wrap(config.HTTPClient) return &DNSProvider{config: config, client: client}, nil } 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/dynu/dynu_test.go b/providers/dns/dynu/dynu_test.go index ffc7c3a00..fe2c22dfb 100644 --- a/providers/dns/dynu/dynu_test.go +++ b/providers/dns/dynu/dynu_test.go @@ -38,7 +38,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,7 +96,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -111,7 +109,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dynu/internal/auth.go b/providers/dns/dynu/internal/auth.go index 0a91445d2..7a21a10e8 100644 --- a/providers/dns/dynu/internal/auth.go +++ b/providers/dns/dynu/internal/auth.go @@ -46,7 +46,6 @@ func (t *TokenTransport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } - return http.DefaultTransport } diff --git a/providers/dns/dynu/internal/client.go b/providers/dns/dynu/internal/client.go index 59e90d592..a51556a0b 100644 --- a/providers/dns/dynu/internal/client.go +++ b/providers/dns/dynu/internal/client.go @@ -12,9 +12,8 @@ import ( "strconv" "time" - "github.com/cenkalti/backoff/v5" + "github.com/cenkalti/backoff/v4" "github.com/go-acme/lego/v4/log" - "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -43,7 +42,6 @@ func (c *Client) GetRecords(ctx context.Context, hostname, recordType string) ([ endpoint.RawQuery = query.Encode() apiResp := RecordsResponse{} - err := c.doRetry(ctx, http.MethodGet, endpoint.String(), nil, &apiResp) if err != nil { return nil, err @@ -66,7 +64,6 @@ func (c *Client) AddNewRecord(ctx context.Context, domainID int64, record DNSRec } apiResp := RecordResponse{} - err = c.doRetry(ctx, http.MethodPost, endpoint.String(), reqBody, &apiResp) if err != nil { return err @@ -84,7 +81,6 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int64) err endpoint := c.baseURL.JoinPath("dns", strconv.FormatInt(domainID, 10), "record", strconv.FormatInt(recordID, 10)) apiResp := APIException{} - err := c.doRetry(ctx, http.MethodDelete, endpoint.String(), nil, &apiResp) if err != nil { return err @@ -102,7 +98,6 @@ func (c *Client) GetRootDomain(ctx context.Context, hostname string) (*DNSHostna endpoint := c.baseURL.JoinPath("dns", "getroot", hostname) apiResp := DNSHostname{} - err := c.doRetry(ctx, http.MethodGet, endpoint.String(), nil, &apiResp) if err != nil { return nil, err @@ -128,7 +123,12 @@ func (c *Client) doRetry(ctx context.Context, method, uri string, body []byte, r bo := backoff.NewExponentialBackOff() bo.InitialInterval = 1 * time.Second - return wait.Retry(ctx, operation, backoff.WithBackOff(bo), backoff.WithNotify(notify)) + err := backoff.RetryNotify(operation, bo, notify) + if err != nil { + return err + } + + return nil } func (c *Client) do(ctx context.Context, method, uri string, body []byte, result any) error { diff --git a/providers/dns/easydns/easydns.go b/providers/dns/easydns/easydns.go index 205063e7b..7e5e219cb 100644 --- a/providers/dns/easydns/easydns.go +++ b/providers/dns/easydns/easydns.go @@ -16,7 +16,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/easydns/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -78,7 +77,6 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("easydns: %w", err) } - config.Endpoint = endpoint values, err := env.Get(EnvToken, EnvKey) @@ -112,8 +110,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.Endpoint != nil { client.BaseURL = config.Endpoint } @@ -190,14 +186,15 @@ 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/easydns/easydns_test.go b/providers/dns/easydns/easydns_test.go index 5517928d7..9a11ef6cc 100644 --- a/providers/dns/easydns/easydns_test.go +++ b/providers/dns/easydns/easydns_test.go @@ -76,7 +76,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -315,7 +314,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -329,7 +327,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/easydns/internal/client.go b/providers/dns/easydns/internal/client.go index 33d7c724e..c044d7e7f 100644 --- a/providers/dns/easydns/internal/client.go +++ b/providers/dns/easydns/internal/client.go @@ -46,7 +46,6 @@ func (c *Client) ListZones(ctx context.Context, domain string) ([]ZoneRecord, er } response := &apiResponse[[]ZoneRecord]{} - err = c.do(req, response) if err != nil { return nil, err @@ -68,7 +67,6 @@ func (c *Client) AddRecord(ctx context.Context, domain string, record ZoneRecord } response := &apiResponse[*ZoneRecord]{} - err = c.do(req, response) if err != nil { return "", err diff --git a/providers/dns/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.go b/providers/dns/edgedns/edgedns.go index b5f4b99c9..10898006a 100644 --- a/providers/dns/edgedns/edgedns.go +++ b/providers/dns/edgedns/edgedns.go @@ -2,17 +2,14 @@ package edgedns import ( - "context" "errors" "fmt" - "net/http" "slices" "strings" "time" - edgegriddns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/dns" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegrid" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session" + configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" @@ -23,8 +20,14 @@ import ( const ( envNamespace = "AKAMAI_" - EnvEdgeRc = envNamespace + "EDGERC" - EnvEdgeRcSection = envNamespace + "EDGERC_SECTION" + EnvEdgeRc = envNamespace + "EDGERC" + EnvEdgeRcSection = envNamespace + "EDGERC_SECTION" + + EnvHost = envNamespace + "HOST" + EnvClientToken = envNamespace + "CLIENT_TOKEN" + EnvClientSecret = envNamespace + "CLIENT_SECRET" + EnvAccessToken = envNamespace + "ACCESS_TOKEN" + EnvAccountSwitchKey = envNamespace + "ACCOUNT_SWITCH_KEY" EnvTTL = envNamespace + "TTL" @@ -32,15 +35,6 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) -// Test Environment variables names (unused). -// TODO(ldez): must be moved into test files. -const ( - EnvHost = envNamespace + "HOST" - EnvClientToken = envNamespace + "CLIENT_TOKEN" - EnvClientSecret = envNamespace + "CLIENT_SECRET" - EnvAccessToken = envNamespace + "ACCESS_TOKEN" -) - const ( defaultPropagationTimeout = 3 * time.Minute defaultPollInterval = 15 * time.Second @@ -52,8 +46,7 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - *edgegrid.Config - + edgegrid.Config PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -65,7 +58,7 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollInterval), - Config: &edgegrid.Config{MaxBody: maxBody}, + Config: edgegrid.Config{MaxBody: maxBody}, } } @@ -80,27 +73,27 @@ type DNSProvider struct { // 1. Section-specific environment variables `AKAMAI_{SECTION}_HOST`, `AKAMAI_{SECTION}_ACCESS_TOKEN`, `AKAMAI_{SECTION}_CLIENT_TOKEN`, `AKAMAI_{SECTION}_CLIENT_SECRET` where `{SECTION}` is specified using `AKAMAI_EDGERC_SECTION` // 2. If `AKAMAI_EDGERC_SECTION` is not defined or is set to `default`: Environment variables `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET` // 3. .edgerc file located at `AKAMAI_EDGERC` (defaults to `~/.edgerc`, sections can be specified using `AKAMAI_EDGERC_SECTION`) +// 4. Default environment variables: `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET` // // See also: https://developer.akamai.com/api/getting-started func NewDNSProvider() (*DNSProvider, error) { - conf, err := edgegrid.New( - edgegrid.WithEnv(true), - edgegrid.WithFile(env.GetOrDefaultString(EnvEdgeRc, "~/.edgerc")), - edgegrid.WithSection(env.GetOrDefaultString(EnvEdgeRcSection, "default")), - ) + config := NewDefaultConfig() + + rcPath := env.GetOrDefaultString(EnvEdgeRc, "") + rcSection := env.GetOrDefaultString(EnvEdgeRcSection, "") + accountSwitchKey := env.GetOrDefaultString(EnvAccountSwitchKey, "") + + conf, err := edgegrid.Init(rcPath, rcSection) if err != nil { return nil, fmt.Errorf("edgedns: %w", err) } conf.MaxBody = maxBody - accountSwitchKey := env.GetOrDefaultString(EnvAccountSwitchKey, "") - if accountSwitchKey != "" { conf.AccountKey = accountSwitchKey } - config := NewDefaultConfig() config.Config = conf return NewDNSProviderConfig(config) @@ -112,10 +105,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("edgedns: the configuration of the DNS provider is nil") } - err := config.Validate() - if err != nil { - return nil, fmt.Errorf("edgedns: %w", err) - } + configdns.Init(config.Config) return &DNSProvider{config: config}, nil } @@ -128,27 +118,14 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - sess, err := session.New(session.WithSigner(d.config)) - if err != nil { - return fmt.Errorf("edgedns: %w", err) - } - - client := edgegriddns.Client(sess) - zone, err := getZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgedns: %w", err) } - record, err := client.GetRecord(ctx, edgegriddns.GetRecordRequest{ - Zone: zone, - Name: info.EffectiveFQDN, - RecordType: "TXT", - }) + record, err := configdns.GetRecord(zone, info.EffectiveFQDN, "TXT") if err != nil && !isNotFound(err) { return fmt.Errorf("edgedns: %w", err) } @@ -168,16 +145,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record.Target = append(record.Target, `"`+info.Value+`"`) record.TTL = d.config.TTL - err = client.UpdateRecord(ctx, edgegriddns.UpdateRecordRequest{ - Record: &edgegriddns.RecordBody{ - Name: record.Name, - RecordType: record.RecordType, - TTL: record.TTL, - Active: record.Active, - Target: record.Target, - }, - Zone: zone, - }) + err = record.Update(zone) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -185,16 +153,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } - err = client.CreateRecord(ctx, edgegriddns.CreateRecordRequest{ - Record: &edgegriddns.RecordBody{ - Name: info.EffectiveFQDN, - RecordType: "TXT", - TTL: d.config.TTL, - Target: []string{`"` + info.Value + `"`}, - }, - Zone: zone, - RecLock: nil, - }) + record = &configdns.RecordBody{ + Name: info.EffectiveFQDN, + RecordType: "TXT", + TTL: d.config.TTL, + Target: []string{`"` + info.Value + `"`}, + } + + err = record.Save(zone) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -204,32 +170,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - sess, err := session.New(session.WithSigner(d.config)) - if err != nil { - return fmt.Errorf("edgedns: %w", err) - } - - client := edgegriddns.Client(sess) - zone, err := getZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgedns: %w", err) } - existingRec, err := client.GetRecord(ctx, edgegriddns.GetRecordRequest{ - Zone: zone, - Name: info.EffectiveFQDN, - RecordType: "TXT", - }) + existingRec, err := configdns.GetRecord(zone, info.EffectiveFQDN, "TXT") if err != nil { if isNotFound(err) { return nil } - return fmt.Errorf("edgedns: %w", err) } @@ -245,21 +197,19 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - newRData := filterRData(existingRec, info) + var newRData []string + for _, val := range existingRec.Target { + val = strings.Trim(val, `"`) + if val == info.Value { + continue + } + newRData = append(newRData, val) + } if len(newRData) > 0 { existingRec.Target = newRData - err = client.UpdateRecord(ctx, edgegriddns.UpdateRecordRequest{ - Record: &edgegriddns.RecordBody{ - Name: existingRec.Name, - RecordType: existingRec.RecordType, - TTL: existingRec.TTL, - Active: existingRec.Active, - Target: existingRec.Target, - }, - Zone: zone, - }) + err = existingRec.Update(zone) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -267,12 +217,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - err = client.DeleteRecord(ctx, edgegriddns.DeleteRecordRequest{ - Zone: zone, - Name: existingRec.Name, - RecordType: "TXT", - RecLock: nil, - }) + err = existingRec.Delete(zone) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -300,22 +245,6 @@ func isNotFound(err error) bool { return false } - var e *edgegriddns.Error - - return errors.As(err, &e) && e.StatusCode == http.StatusNotFound -} - -func filterRData(existingRec *edgegriddns.GetRecordResponse, info dns01.ChallengeInfo) []string { - var newRData []string - - for _, val := range existingRec.Target { - val = strings.Trim(val, `"`) - if val == info.Value { - continue - } - - newRData = append(newRData, val) - } - - return newRData + var e configdns.ConfigDNSError + return errors.As(err, &e) && e.NotFound() } 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/edgedns/edgedns_integration_test.go b/providers/dns/edgedns/edgedns_integration_test.go index d20b8e5aa..e1b3bb7cf 100644 --- a/providers/dns/edgedns/edgedns_integration_test.go +++ b/providers/dns/edgedns/edgedns_integration_test.go @@ -1,13 +1,11 @@ package edgedns import ( - "context" "fmt" "testing" "time" - edgegriddns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/dns" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session" + configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,7 +17,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -37,7 +34,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -73,21 +69,10 @@ func TestLiveTTL(t *testing.T) { zone, err := getZone(fqdn) require.NoError(t, err) - ctx := context.Background() - - sess, err := session.New(session.WithSigner(provider.config)) + resourceRecordSets, err := configdns.GetRecordList(zone, fqdn, "TXT") require.NoError(t, err) - client := edgegriddns.Client(sess) - - resourceRecordSets, err := client.GetRecordList(ctx, edgegriddns.GetRecordListRequest{ - Zone: zone, - RecordType: "TXT", - }) - - require.NoError(t, err) - - for i, rrset := range resourceRecordSets.RecordSets { + for i, rrset := range resourceRecordSets.Recordsets { if rrset.Name != fqdn { continue } diff --git a/providers/dns/edgedns/edgedns_test.go b/providers/dns/edgedns/edgedns_test.go index a64efd6e2..3ac55a4a4 100644 --- a/providers/dns/edgedns/edgedns_test.go +++ b/providers/dns/edgedns/edgedns_test.go @@ -1,10 +1,12 @@ package edgedns import ( + "os" "testing" "time" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegrid" + configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/tester" "github.com/stretchr/testify/require" @@ -19,9 +21,6 @@ const ( ) var envTest = tester.NewEnvTest( - EnvTTL, - EnvPollingInterval, - EnvPropagationTimeout, EnvHost, EnvClientToken, EnvClientSecret, @@ -36,7 +35,7 @@ var envTest = tester.NewEnvTest( WithDomain(envDomain). WithLiveTestRequirements(EnvHost, EnvClientToken, EnvClientSecret, EnvAccessToken, envDomain) -func TestNewDNSProvider(t *testing.T) { +func TestNewDNSProvider_FromEnv(t *testing.T) { testCases := []struct { desc string envVars map[string]string @@ -51,13 +50,13 @@ func TestNewDNSProvider(t *testing.T) { EnvClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", }, - expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { - config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" - config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.MaxBody = maxBody - }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null")), + expectedConfig: &edgegrid.Config{ + Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", + ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + MaxBody: maxBody, + }, }, { desc: "with account switch key", @@ -68,14 +67,14 @@ func TestNewDNSProvider(t *testing.T) { EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", EnvAccountSwitchKey: "F-AC-1234", }, - expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { - config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" - config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.MaxBody = maxBody - config.AccountKey = "F-AC-1234" - }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null")), + expectedConfig: &edgegrid.Config{ + Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", + ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + MaxBody: maxBody, + AccountKey: "F-AC-1234", + }, }, { desc: "with section", @@ -86,17 +85,17 @@ func TestNewDNSProvider(t *testing.T) { envTestClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", envTestAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", }, - expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { - config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" - config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" - config.MaxBody = maxBody - }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null"), edgegrid.WithSection("test")), + expectedConfig: &edgegrid.Config{ + Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", + ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + MaxBody: maxBody, + }, }, { desc: "missing credentials", - expectedErr: `edgedns: unable to load config from environment or .edgerc file`, + expectedErr: "edgedns: Unable to create instance using environment or .edgerc file", }, { desc: "missing host", @@ -106,7 +105,7 @@ func TestNewDNSProvider(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "D", }, - expectedErr: `edgedns: unable to load config from environment or .edgerc file`, + expectedErr: "edgedns: Unable to create instance using environment or .edgerc file", }, { desc: "missing client token", @@ -116,7 +115,7 @@ func TestNewDNSProvider(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "D", }, - expectedErr: `edgedns: unable to load config from environment or .edgerc file`, + expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_TOKEN]", }, { desc: "missing client secret", @@ -126,7 +125,7 @@ func TestNewDNSProvider(t *testing.T) { EnvClientSecret: "", EnvAccessToken: "D", }, - expectedErr: `edgedns: unable to load config from environment or .edgerc file`, + expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_SECRET]", }, { desc: "missing access token", @@ -136,20 +135,18 @@ func TestNewDNSProvider(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "", }, - expectedErr: `edgedns: unable to load config from environment or .edgerc file`, + expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_ACCESS_TOKEN]", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() if test.envVars == nil { test.envVars = map[string]string{} } - test.envVars[EnvEdgeRc] = "/dev/null" envTest.Apply(test.envVars) @@ -157,7 +154,7 @@ func TestNewDNSProvider(t *testing.T) { p, err := NewDNSProvider() if test.expectedErr != "" { - require.ErrorContains(t, err, test.expectedErr) + require.EqualError(t, err, test.expectedErr) return } @@ -166,63 +163,13 @@ func TestNewDNSProvider(t *testing.T) { require.NotNil(t, p.config) if test.expectedConfig != nil { - require.Equal(t, test.expectedConfig, p.config.Config) + require.Equal(t, *test.expectedConfig, configdns.Config) } }) } } -func TestNewDefaultConfig(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected *Config - }{ - { - desc: "default configuration", - expected: &Config{ - TTL: dns01.DefaultTTL, - PropagationTimeout: 3 * time.Minute, - PollingInterval: 15 * time.Second, - Config: &edgegrid.Config{ - MaxBody: maxBody, - }, - }, - }, - { - desc: "custom values", - envVars: map[string]string{ - EnvTTL: "99", - EnvPropagationTimeout: "60", - EnvPollingInterval: "60", - }, - expected: &Config{ - TTL: 99, - PropagationTimeout: 60 * time.Second, - PollingInterval: 60 * time.Second, - Config: &edgegrid.Config{ - MaxBody: maxBody, - }, - }, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - config := NewDefaultConfig() - - require.Equal(t, test.expected, config) - }) - } -} - -func Test_findZone(t *testing.T) { +func TestDNSProvider_findZone(t *testing.T) { testCases := []struct { desc string domain string @@ -251,7 +198,53 @@ func Test_findZone(t *testing.T) { } } -func newEdgeConfig(opts ...edgegrid.Option) *edgegrid.Config { - config, _ := edgegrid.New(opts...) - return config +func TestNewDefaultConfig(t *testing.T) { + defer envTest.RestoreEnv() + + testCases := []struct { + desc string + envVars map[string]string + expected *Config + }{ + { + desc: "default configuration", + expected: &Config{ + TTL: dns01.DefaultTTL, + PropagationTimeout: 3 * time.Minute, + PollingInterval: 15 * time.Second, + Config: edgegrid.Config{ + MaxBody: maxBody, + }, + }, + }, + { + desc: "custom values", + envVars: map[string]string{ + EnvTTL: "99", + EnvPropagationTimeout: "60", + EnvPollingInterval: "60", + }, + expected: &Config{ + TTL: 99, + PropagationTimeout: 60 * time.Second, + PollingInterval: 60 * time.Second, + Config: edgegrid.Config{ + MaxBody: maxBody, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + envTest.ClearEnv() + for key, value := range test.envVars { + os.Setenv(key, value) + } + + config := NewDefaultConfig() + + require.Equal(t, test.expected, config) + }) + } } diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go deleted file mode 100644 index 6931c6715..000000000 --- a/providers/dns/edgeone/edgeone.go +++ /dev/null @@ -1,203 +0,0 @@ -// Package edgeone implements a DNS provider for solving the DNS-01 challenge using Tencent EdgeOne. -package edgeone - -import ( - "context" - "errors" - "fmt" - "math" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/ptr" - teo "github.com/go-acme/tencentedgdeone/v20220901" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" - "golang.org/x/net/idna" -) - -// Environment variables names. -const ( - envNamespace = "EDGEONE_" - - EnvSecretID = envNamespace + "SECRET_ID" - EnvSecretKey = envNamespace + "SECRET_KEY" - EnvRegion = envNamespace + "REGION" - EnvSessionToken = envNamespace + "SESSION_TOKEN" - EnvZonesMapping = envNamespace + "ZONES_MAPPING" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - SecretID string - SecretKey string - Region string - SessionToken string - - ZonesMapping map[string]string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPTimeout time.Duration -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 60), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 20*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *teo.Client - - recordIDs map[string]*string - recordIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Tencent EdgeOne. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvSecretID, EnvSecretKey) - if err != nil { - return nil, fmt.Errorf("edgeone: %w", err) - } - - config := NewDefaultConfig() - config.SecretID = values[EnvSecretID] - config.SecretKey = values[EnvSecretKey] - config.Region = env.GetOrDefaultString(EnvRegion, "") - config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "") - - 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) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Tencent EdgeOne. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("edgeone: the configuration of the DNS provider is nil") - } - - var credential *common.Credential - - switch { - case config.SecretID != "" && config.SecretKey != "" && config.SessionToken != "": - credential = common.NewTokenCredential(config.SecretID, config.SecretKey, config.SessionToken) - case config.SecretID != "" && config.SecretKey != "": - credential = common.NewCredential(config.SecretID, config.SecretKey) - default: - return nil, errors.New("edgeone: credentials missing") - } - - cpf := profile.NewClientProfile() - cpf.HttpProfile.Endpoint = "teo.intl.tencentcloudapi.com" - cpf.HttpProfile.ReqTimeout = int(math.Round(config.HTTPTimeout.Seconds())) - - client, err := teo.NewClient(credential, config.Region, cpf) - if err != nil { - return nil, fmt.Errorf("edgeone: %w", err) - } - - return &DNSProvider{ - config: config, - client: client, - recordIDs: map[string]*string{}, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) - } - - punnyCoded, err := idna.ToASCII(dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("edgeone: fail to convert punycode: %w", err) - } - - request := teo.NewCreateDnsRecordRequest() - request.Name = ptr.Pointer(punnyCoded) - request.ZoneId = zoneID - request.Type = ptr.Pointer("TXT") - request.Content = ptr.Pointer(info.Value) - request.TTL = ptr.Pointer(int64(d.config.TTL)) - - nr, err := teo.CreateDnsRecordWithContext(ctx, d.client, request) - if err != nil { - return fmt.Errorf("edgeone: API call failed: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = nr.Response.RecordId - d.recordIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) - } - - // get the record's unique ID from when we created it - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("edgeone: unknown record ID for '%s'", info.EffectiveFQDN) - } - - request := teo.NewDeleteDnsRecordsRequest() - request.ZoneId = zoneID - request.RecordIds = []*string{recordID} - - _, err = teo.DeleteDnsRecordsWithContext(ctx, d.client, request) - if err != nil { - return fmt.Errorf("edgeone: delete record failed: %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/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml deleted file mode 100644 index 05b8bc516..000000000 --- a/providers/dns/edgeone/edgeone.toml +++ /dev/null @@ -1,28 +0,0 @@ -Name = "Tencent EdgeOne" -Description = '''''' -URL = "https://edgeone.ai" -Code = "edgeone" -Since = "v4.26.0" - -Example = ''' -EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ -EDGEONE_SECRET_KEY=your-secret-key \ -lego --dns edgeone -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - EDGEONE_SECRET_ID = "Access key ID" - EDGEONE_SECRET_KEY = "Access Key secret" - [Configuration.Additional] - EDGEONE_SESSION_TOKEN = "Access Key token" - EDGEONE_REGION = "Region" - EDGEONE_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)" - EDGEONE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://edgeone.ai/document/50454#dns-record-apis" - GoClient = "https://github.com/tencentcloud/tencentcloud-sdk-go" diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go deleted file mode 100644 index 7bd4f6f6d..000000000 --- a/providers/dns/edgeone/edgeone_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package edgeone - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvSecretID, - EnvSecretKey, - EnvZonesMapping, -).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvSecretID: "123", - EnvSecretKey: "456", - }, - }, - { - desc: "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{ - EnvSecretID: "", - EnvSecretKey: "", - }, - expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_ID,EDGEONE_SECRET_KEY", - }, - { - desc: "missing access id", - envVars: map[string]string{ - EnvSecretID: "", - EnvSecretKey: "456", - }, - expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_ID", - }, - { - desc: "missing secret key", - envVars: map[string]string{ - EnvSecretID: "123", - EnvSecretKey: "", - }, - expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_KEY", - }, - { - 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 { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - secretID string - secretKey string - expected string - }{ - { - desc: "success", - secretID: "123", - secretKey: "456", - }, - { - desc: "missing credentials", - expected: "edgeone: credentials missing", - }, - { - desc: "missing secret id", - secretKey: "456", - expected: "edgeone: credentials missing", - }, - { - desc: "missing secret key", - secretID: "123", - expected: "edgeone: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.SecretID = test.secretID - config.SecretKey = test.secretKey - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go deleted file mode 100644 index 53fae9427..000000000 --- a/providers/dns/edgeone/wrapper.go +++ /dev/null @@ -1,58 +0,0 @@ -package edgeone - -import ( - "context" - "fmt" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/providers/dns/internal/ptr" - teo "github.com/go-acme/tencentedgdeone/v20220901" -) - -func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*string, error) { - authZone, err := dns01.FindZoneByFqdn(domain) - if err != nil { - return nil, fmt.Errorf("could not find zone: %w", err) - } - - if d.config.ZonesMapping != nil { - zoneID, ok := d.config.ZonesMapping[authZone] - if ok { - return ptr.Pointer(zoneID), nil - } - } - - request := teo.NewDescribeZonesRequest() - - var zones []*teo.Zone - - for { - response, err := teo.DescribeZonesWithContext(ctx, d.client, request) - if err != nil { - return nil, fmt.Errorf("API call failed: %w", err) - } - - zones = append(zones, response.Response.Zones...) - - if int64(len(zones)) >= ptr.Deref(response.Response.TotalCount) { - break - } - - request.Offset = ptr.Pointer(int64(len(zones))) - } - - var hostedZone *teo.Zone - - for _, zone := range zones { - unfqdn := dns01.UnFqdn(authZone) - if ptr.Deref(zone.ZoneName) == unfqdn { - hostedZone = zone - } - } - - if hostedZone == nil { - return nil, fmt.Errorf("zone %s not found for domain %s", authZone, domain) - } - - return hostedZone.ZoneId, nil -} diff --git a/providers/dns/efficientip/efficientip.go b/providers/dns/efficientip/efficientip.go index 81b4530b7..15fa579ed 100644 --- a/providers/dns/efficientip/efficientip.go +++ b/providers/dns/efficientip/efficientip.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/efficientip/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,15 +91,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.Username == "" { return nil, errors.New("efficientip: missing username") } - if config.Password == "" { return nil, errors.New("efficientip: missing password") } - if config.Hostname == "" { return nil, errors.New("efficientip: missing hostname") } - if config.DNSName == "" { return nil, errors.New("efficientip: missing dnsname") } @@ -117,8 +113,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, 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/efficientip/efficientip_test.go b/providers/dns/efficientip/efficientip_test.go index c2751a79b..3ee2da777 100644 --- a/providers/dns/efficientip/efficientip_test.go +++ b/providers/dns/efficientip/efficientip_test.go @@ -83,7 +83,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -179,7 +178,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -193,7 +191,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/efficientip/internal/client.go b/providers/dns/efficientip/internal/client.go index 5ccdf3973..cc26c5412 100644 --- a/providers/dns/efficientip/internal/client.go +++ b/providers/dns/efficientip/internal/client.go @@ -108,7 +108,6 @@ func (c *Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) if err != nil { return nil, fmt.Errorf("query parameters: %w", err) } - endpoint.RawQuery = v.Encode() req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -201,7 +200,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/epik/epik.go b/providers/dns/epik/epik.go index ef5de3c4b..58390faa9 100644 --- a/providers/dns/epik/epik.go +++ b/providers/dns/epik/epik.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/epik/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,8 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/epik/epik_test.go b/providers/dns/epik/epik_test.go index b8b3c5c43..c0cd3d43b 100644 --- a/providers/dns/epik/epik_test.go +++ b/providers/dns/epik/epik_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go index 2c3373953..70fb42fa9 100644 --- a/providers/dns/epik/internal/client.go +++ b/providers/dns/epik/internal/client.go @@ -46,7 +46,6 @@ func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, er } var data GetDNSRecordResponse - err = c.do(req, &data) if err != nil { return nil, err @@ -68,7 +67,6 @@ func (c *Client) CreateHostRecord(ctx context.Context, domain string, record Rec } var data Data - err = c.do(req, &data) if err != nil { return nil, err @@ -91,7 +89,6 @@ func (c *Client) RemoveHostRecord(ctx context.Context, domain, recordID string) } var data Data - err = c.do(req, &data) if err != nil { return nil, err @@ -168,7 +165,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var apiErr APIError - err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/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/exec/exec_test.go b/providers/dns/exec/exec_test.go index c1b6da55e..3a2edbbf4 100644 --- a/providers/dns/exec/exec_test.go +++ b/providers/dns/exec/exec_test.go @@ -14,7 +14,6 @@ import ( func TestDNSProvider_Present(t *testing.T) { backupLogger := log.Logger - defer func() { log.Logger = backupLogger }() @@ -63,7 +62,6 @@ func TestDNSProvider_Present(t *testing.T) { } var message string - logRecorder.On("Println", mock.Anything).Run(func(args mock.Arguments) { message = args.String(0) fmt.Fprintln(os.Stdout, "XXX", message) @@ -89,7 +87,6 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { backupLogger := log.Logger - defer func() { log.Logger = backupLogger }() @@ -138,7 +135,6 @@ func TestDNSProvider_CleanUp(t *testing.T) { } var message string - logRecorder.On("Println", mock.Anything).Run(func(args mock.Arguments) { message = args.String(0) fmt.Fprintln(os.Stdout, "XXX", message) diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 05fcb6a6f..1a5f358f5 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" ) @@ -90,7 +89,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client, err := egoscale.NewClient( credentials.NewStaticCredentials(config.APIKey, config.APISecret), egoscale.ClientOptWithEndpoint(egoscale.Endpoint(config.Endpoint)), - egoscale.ClientOptWithHTTPClient(clientdebug.Wrap(&http.Client{Timeout: config.HTTPTimeout})), + egoscale.ClientOptWithHTTPClient(&http.Client{Timeout: config.HTTPTimeout}), egoscale.ClientOptWithUserAgent(useragent.Get()), ) if err != nil { @@ -106,7 +105,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) zoneName, recordName, err := d.findZoneAndRecordName(info.EffectiveFQDN) @@ -114,11 +112,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("exoscale: %w", err) } - zone, err := d.findExistingZone(ctx, zoneName) + zone, err := d.findExistingZone(zoneName) if err != nil { return fmt.Errorf("exoscale: %w", err) } - if zone == nil { return fmt.Errorf("exoscale: zone %q not found", zoneName) } @@ -146,7 +143,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) zoneName, recordName, err := d.findZoneAndRecordName(info.EffectiveFQDN) @@ -154,16 +150,15 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("exoscale: %w", err) } - zone, err := d.findExistingZone(ctx, zoneName) + zone, err := d.findExistingZone(zoneName) if err != nil { return fmt.Errorf("exoscale: %w", err) } - if zone == nil { return fmt.Errorf("exoscale: zone %q not found", zoneName) } - recordID, err := d.findExistingRecordID(ctx, zone.ID, recordName, info.Value) + recordID, err := d.findExistingRecordID(zone.ID, recordName, info.Value) if err != nil { return err } @@ -193,7 +188,9 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // findExistingZone Query Exoscale to find an existing zone for this name. // Returns nil result if no zone could be found. -func (d *DNSProvider) findExistingZone(ctx context.Context, zoneName string) (*egoscale.DNSDomain, error) { +func (d *DNSProvider) findExistingZone(zoneName string) (*egoscale.DNSDomain, error) { + ctx := context.Background() + zones, err := d.client.ListDNSDomains(ctx) if err != nil { return nil, fmt.Errorf("error while retrieving DNS zones: %w", err) @@ -210,7 +207,9 @@ func (d *DNSProvider) findExistingZone(ctx context.Context, zoneName string) (*e // findExistingRecordID Query Exoscale to find an existing record for this name. // Returns empty result if no record could be found. -func (d *DNSProvider) findExistingRecordID(ctx context.Context, zoneID egoscale.UUID, recordName, value string) (egoscale.UUID, error) { +func (d *DNSProvider) findExistingRecordID(zoneID egoscale.UUID, recordName, value string) (egoscale.UUID, error) { + ctx := context.Background() + records, err := d.client.ListDNSDomainRecords(ctx, zoneID) if err != nil { return "", fmt.Errorf("error while retrieving DNS records: %w", err) 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/exoscale/exoscale_test.go b/providers/dns/exoscale/exoscale_test.go index e9f6be602..fa58216a5 100644 --- a/providers/dns/exoscale/exoscale_test.go +++ b/providers/dns/exoscale/exoscale_test.go @@ -58,7 +58,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -179,7 +178,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -197,7 +195,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index 76a6e0262..2ed1f0c4f 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -8,12 +8,10 @@ import ( "net/http" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/f5xc/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -22,7 +20,6 @@ const ( EnvToken = envNamespace + "API_TOKEN" EnvTenantName = envNamespace + "TENANT_NAME" - EnvServer = envNamespace + "SERVER" EnvGroupName = envNamespace + "GROUP_NAME" EnvTTL = envNamespace + "TTL" @@ -35,7 +32,6 @@ const ( type Config struct { APIToken string TenantName string - Server string GroupName string PropagationTimeout time.Duration @@ -73,7 +69,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 +83,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) } @@ -97,8 +92,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -107,8 +100,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -121,7 +112,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("f5xc: %w", err) } - existingRRSet, err := d.client.GetRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") + existingRRSet, err := d.client.GetRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") if err != nil { return fmt.Errorf("f5xc: get RR Set: %w", err) } @@ -137,41 +128,29 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { }, } - return d.waitFor(ctx, func() error { - _, err = d.client.CreateRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, rrSet) + return wait.For("f5xc create", 60*time.Second, 2*time.Second, func() (bool, error) { + _, err = d.client.CreateRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, rrSet) if err != nil { - return fmt.Errorf("create RR set: %w", err) + return false, fmt.Errorf("f5xc: create RR set: %w", err) } - return nil + return true, nil }) } // Update RRSet. existingRRSet.RRSet.TXTRecord.Values = append(existingRRSet.RRSet.TXTRecord.Values, info.Value) - return d.waitFor(ctx, func() error { - _, err = d.client.ReplaceRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) + return wait.For("f5xc replace", 60*time.Second, 2*time.Second, func() (bool, error) { + _, err = d.client.ReplaceRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) if err != nil { - return fmt.Errorf("replace RR set: %w", err) + return false, fmt.Errorf("f5xc: replace RR set: %w", err) } - return nil + return true, nil }) } -func (d *DNSProvider) waitFor(ctx context.Context, operation func() error) error { - err := wait.Retry(ctx, operation, - backoff.WithBackOff(backoff.NewConstantBackOff(2*time.Second)), - backoff.WithMaxElapsedTime(60*time.Second), - ) - if err != nil { - return fmt.Errorf("f5xc: %w", err) - } - - return nil -} - // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) diff --git a/providers/dns/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..a298b9e51 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 { @@ -67,7 +62,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -151,7 +145,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -165,7 +158,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go index 7beab0d03..26eb03db5 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{ @@ -197,7 +201,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) apiErr := APIError{StatusCode: resp.StatusCode} - err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) @@ -205,20 +208,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/f5xc/internal/types.go b/providers/dns/f5xc/internal/types.go index 346283fb7..3122ca4ef 100644 --- a/providers/dns/f5xc/internal/types.go +++ b/providers/dns/f5xc/internal/types.go @@ -27,13 +27,13 @@ type APIRRSet struct { Namespace string `json:"namespace,omitempty"` RecordName string `json:"record_name,omitempty"` Type string `json:"type,omitempty"` - RRSet RRSet `json:"rrset"` + RRSet RRSet `json:"rrset,omitempty"` } type RRSetRequest struct { DNSZoneName string `json:"dns_zone_name,omitempty"` GroupName string `json:"group_name,omitempty"` - RRSet RRSet `json:"rrset"` + RRSet RRSet `json:"rrset,omitempty"` } type RRSet struct { diff --git a/providers/dns/freemyip/freemyip.go b/providers/dns/freemyip/freemyip.go index fb6202e25..7613f2b8d 100644 --- a/providers/dns/freemyip/freemyip.go +++ b/providers/dns/freemyip/freemyip.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/freemyip" ) @@ -89,8 +88,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/freemyip/freemyip.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/freemyip/freemyip_test.go b/providers/dns/freemyip/freemyip_test.go index 24d1b98f7..dcf74dd6c 100644 --- a/providers/dns/freemyip/freemyip_test.go +++ b/providers/dns/freemyip/freemyip_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,7 +94,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,7 +107,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go index bb96a7d0f..dd6622172 100644 --- a/providers/dns/gandi/gandi.go +++ b/providers/dns/gandi/gandi.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/gandi/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -110,8 +109,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/gandi/gandi.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/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 58c25d0db..4c37fb00e 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -39,7 +39,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -127,7 +126,6 @@ func TestDNSProvider(t *testing.T) { func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() config.BaseURL = server.URL + "/" - config.HTTPClient = server.Client() config.APIKey = "123412341234123412341234" return NewDNSProviderConfig(config) @@ -147,6 +145,7 @@ func TestDNSProvider(t *testing.T) { _, errS = io.Copy(rw, strings.NewReader(resp)) require.NoError(t, errS) })). + Route("/", servermock.DumpRequest()). Build(t) fakeKeyAuth := "XXXX" @@ -158,11 +157,9 @@ func TestDNSProvider(t *testing.T) { // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn - t.Cleanup(func() { provider.findZoneByFqdn = savedFindZoneByFqdn }) - provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present diff --git a/providers/dns/gandi/internal/client.go b/providers/dns/gandi/internal/client.go index 6ca46d072..6dc09648c 100644 --- a/providers/dns/gandi/internal/client.go +++ b/providers/dns/gandi/internal/client.go @@ -50,7 +50,6 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (int, error) { } var zoneID int - for _, member := range resp.StructMembers { if member.Name == "zone_id" { zoneID = member.ValueInt @@ -60,7 +59,6 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (int, error) { if zoneID == 0 { return 0, fmt.Errorf("could not find zone_id for %s", domain) } - return zoneID, nil } @@ -90,7 +88,6 @@ func (c *Client) CloneZone(ctx context.Context, zoneID int, name string) (int, e } var newZoneID int - for _, member := range resp.StructMembers { if member.Name == "id" { newZoneID = member.ValueInt @@ -100,7 +97,6 @@ func (c *Client) CloneZone(ctx context.Context, zoneID int, name string) (int, e if newZoneID == 0 { return 0, errors.New("could not determine cloned zone_id") } - return newZoneID, nil } @@ -123,7 +119,6 @@ func (c *Client) NewZoneVersion(ctx context.Context, zoneID int) (int, error) { if resp.Value == 0 { return 0, errors.New("could not create new zone version") } - return resp.Value, nil } @@ -179,7 +174,6 @@ func (c *Client) SetZoneVersion(ctx context.Context, zoneID, version int) error if !resp.Value { return errors.New("could not set zone version") } - return nil } @@ -201,7 +195,6 @@ func (c *Client) SetZone(ctx context.Context, domain string, zoneID int) error { } var respZoneID int - for _, member := range resp.StructMembers { if member.Name == "zone_id" { respZoneID = member.ValueInt @@ -211,7 +204,6 @@ func (c *Client) SetZone(ctx context.Context, domain string, zoneID int) error { if respZoneID != zoneID { return fmt.Errorf("could not set new zone_id for %s", domain) } - return nil } diff --git a/providers/dns/gandi/internal/types.go b/providers/dns/gandi/internal/types.go index 2cde62b53..cdcd0a658 100644 --- a/providers/dns/gandi/internal/types.go +++ b/providers/dns/gandi/internal/types.go @@ -69,7 +69,6 @@ func (r responseFault) faultString() string { return r.FaultString } type responseStruct struct { responseFault - StructMembers []struct { Name string `xml:"name"` ValueInt int `xml:"value>int"` @@ -78,13 +77,11 @@ type responseStruct struct { type responseInt struct { responseFault - Value int `xml:"params>param>value>int"` } type responseBool struct { responseFault - Value bool `xml:"params>param>value>boolean"` } diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go index 15014e207..3c35245de 100644 --- a/providers/dns/gandiv5/gandiv5.go +++ b/providers/dns/gandiv5/gandiv5.go @@ -15,7 +15,6 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/gandiv5/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -114,7 +113,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("gandiv5: %w", err) } - client.BaseURL = baseURL } @@ -122,8 +120,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -164,7 +160,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { authZone: authZone, fieldName: subDomain, } - return nil } @@ -175,7 +170,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() - if _, ok := d.inProgressFQDNs[info.EffectiveFQDN]; !ok { // if there is no cleanup information then just return return nil @@ -190,7 +184,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("gandiv5: %w", err) } - return nil } diff --git a/providers/dns/gandiv5/gandiv5.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/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index d6f077243..451b1b683 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -35,7 +35,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,7 +96,6 @@ func TestDNSProvider(t *testing.T) { config := NewDefaultConfig() config.PersonalAccessToken = "123412341234123412341234" config.BaseURL = server.URL - config.HTTPClient = server.Client() return NewDNSProviderConfig(config) }, @@ -121,11 +119,9 @@ func TestDNSProvider(t *testing.T) { // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn - defer func() { provider.findZoneByFqdn = savedFindZoneByFqdn }() - provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index bfb71c9f6..57de9d615 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" @@ -75,7 +78,6 @@ func (c *Client) getTXTRecord(ctx context.Context, domain, name string) (*Record } txtRecord := &Record{} - err = c.do(req, txtRecord) if err != nil { return nil, fmt.Errorf("unable to get TXT records for domain %s and name %s: %w", domain, name, err) @@ -93,7 +95,6 @@ func (c *Client) addTXTRecord(ctx context.Context, domain, name string, newRecor } message := apiResponse{} - err = c.do(req, &message) if err != nil { return fmt.Errorf("unable to create TXT record for domain %s and name %s: %w", domain, name, err) @@ -115,7 +116,6 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain, name string) error } message := apiResponse{} - err = c.do(req, &message) if err != nil { return fmt.Errorf("unable to delete TXT record for domain %s and name %s: %w", domain, name, err) @@ -130,7 +130,7 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain, name string) error func (c *Client) do(req *http.Request, result any) error { if c.apiKey != "" { - req.Header.Set(authorizationHeader, "Apikey "+c.apiKey) + req.Header.Set(APIKeyHeader, c.apiKey) } if c.pat != "" { @@ -208,7 +208,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) response := apiResponse{} - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/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..30a028d61 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" @@ -12,17 +11,15 @@ import ( "time" "cloud.google.com/go/compute/metadata" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/miekg/dns" + "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - gdns "google.golang.org/api/dns/v1" + "google.golang.org/api/dns/v1" "google.golang.org/api/googleapi" "google.golang.org/api/impersonate" "google.golang.org/api/option" @@ -77,7 +74,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *gdns.Service + client *dns.Service } // NewDNSProvider returns a DNSProvider instance configured for Google Cloud DNS. @@ -93,7 +90,6 @@ func NewDNSProvider() (*DNSProvider, error) { // Use default credentials. project := env.GetOrDefaultString(EnvProject, autodetectProjectID(context.Background())) - return NewDNSProviderCredentials(project) } @@ -108,7 +104,6 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) { config.Project = project var err error - config.HTTPClient, err = newClientFromCredentials(context.Background(), config) if err != nil { return nil, fmt.Errorf("googlecloud: %w", err) @@ -132,12 +127,10 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) { var datJSON struct { ProjectID string `json:"project_id"` } - err := json.Unmarshal(saKey, &datJSON) if err != nil || datJSON.ProjectID == "" { return nil, errors.New("googlecloud: project ID not found in Google Cloud Service Account file") } - project = datJSON.ProjectID } @@ -145,7 +138,6 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) { config.Project = project var err error - config.HTTPClient, err = newClientFromServiceAccountKey(context.Background(), config, saKey) if err != nil { return nil, fmt.Errorf("googlecloud: %w", err) @@ -174,12 +166,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { return nil, errors.New("googlecloud: the configuration of the DNS provider is nil") } - if config.HTTPClient == nil { return nil, errors.New("googlecloud: unable to create Google Cloud DNS service: client is nil") } - svc, err := gdns.NewService(context.Background(), option.WithHTTPClient(clientdebug.Wrap(config.HTTPClient))) + svc, err := dns.NewService(context.Background(), option.WithHTTPClient(config.HTTPClient)) if err != nil { return nil, fmt.Errorf("googlecloud: unable to create Google Cloud DNS service: %w", err) } @@ -189,8 +180,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) zone, err := d.getHostedZone(info.EffectiveFQDN) @@ -206,7 +195,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { for _, rrSet := range existingRrSet { var rrd []string - for _, rr := range rrSet.Rrdatas { data := mustUnquote(rr) rrd = append(rrd, data) @@ -216,18 +204,17 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } } - rrSet.Rrdatas = rrd } // Attempt to delete the existing records before adding the new one. if len(existingRrSet) > 0 { - if err = d.applyChanges(ctx, zone, &gdns.Change{Deletions: existingRrSet}); err != nil { + if err = d.applyChanges(zone, &dns.Change{Deletions: existingRrSet}); err != nil { return fmt.Errorf("googlecloud: %w", err) } } - rec := &gdns.ResourceRecordSet{ + rec := &dns.ResourceRecordSet{ Name: info.EffectiveFQDN, Rrdatas: []string{info.Value}, Ttl: int64(d.config.TTL), @@ -243,18 +230,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } } - change := &gdns.Change{ - Additions: []*gdns.ResourceRecordSet{rec}, + change := &dns.Change{ + Additions: []*dns.ResourceRecordSet{rec}, } - if err = d.applyChanges(ctx, zone, change); err != nil { + if err = d.applyChanges(zone, change); err != nil { return fmt.Errorf("googlecloud: %w", err) } return nil } -func (d *DNSProvider) applyChanges(ctx context.Context, zone string, change *gdns.Change) error { +func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error { if d.config.Debug { data, _ := json.Marshal(change) log.Printf("change (Create): %s", string(data)) @@ -268,7 +255,6 @@ func (d *DNSProvider) applyChanges(ctx context.Context, zone string, change *gdn } data, _ := json.Marshal(change) - return fmt.Errorf("failed to perform changes [zone %s, change %s]: %w", zone, string(data), err) } @@ -279,28 +265,24 @@ func (d *DNSProvider) applyChanges(ctx context.Context, zone string, change *gdn chgID := chg.Id // wait for change to be acknowledged - return wait.Retry(ctx, - func() error { - if d.config.Debug { - data, _ := json.Marshal(change) - log.Printf("change (Get): %s", string(data)) - } + return wait.For("apply change", 30*time.Second, 3*time.Second, func() (bool, error) { + if d.config.Debug { + data, _ := json.Marshal(change) + log.Printf("change (Get): %s", string(data)) + } - chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do() - if err != nil { - data, _ := json.Marshal(change) - return fmt.Errorf("failed to get changes [zone %s, change %s]: %w", zone, string(data), err) - } + chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do() + if err != nil { + data, _ := json.Marshal(change) + return false, fmt.Errorf("failed to get changes [zone %s, change %s]: %w", zone, string(data), err) + } - if chg.Status != changeStatusDone { - return fmt.Errorf("status: %s", chg.Status) - } + if chg.Status == changeStatusDone { + return true, nil + } - return nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(3*time.Second)), - backoff.WithMaxElapsedTime(30*time.Second), - ) + return false, fmt.Errorf("status: %s", chg.Status) + }) } // CleanUp removes the TXT record matching the specified parameters. @@ -321,11 +303,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - _, err = d.client.Changes.Create(d.config.Project, zone, &gdns.Change{Deletions: records}).Do() + _, err = d.client.Changes.Create(d.config.Project, zone, &dns.Change{Deletions: records}).Do() if err != nil { return fmt.Errorf("googlecloud: %w", err) } - return nil } @@ -371,7 +352,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { // (gcloud projects get-iam-policy $project_id) (a role with permission dns.managedZones.list) // // If we force a zone list to succeed, we demand more permissions than needed. -func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*gdns.ManagedZone, error) { +func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZone, error) { // GCE_ZONE_ID override for service accounts to avoid needing zones-list permission if d.config.ZoneID != "" { zone, err := d.client.ManagedZones.Get(d.config.Project, d.config.ZoneID).Do() @@ -379,10 +360,10 @@ func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*gdns.Managed return "", nil, fmt.Errorf("API call ManagedZones.Get for explicit zone ID %q in project %q failed: %w", d.config.ZoneID, d.config.Project, err) } - return zone.DnsName, []*gdns.ManagedZone{zone}, nil + return zone.DnsName, []*dns.ManagedZone{zone}, nil } - authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(domain)) + authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain)) if err != nil { return "", nil, fmt.Errorf("could not find zone: %w", err) } @@ -398,7 +379,7 @@ func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*gdns.Managed return authZone, zones.ManagedZones, nil } -func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*gdns.ResourceRecordSet, error) { +func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) { recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do() if err != nil { return nil, err @@ -417,7 +398,7 @@ func newClientFromCredentials(ctx context.Context, config *Config) (*http.Client return newImpersonateClient(ctx, config.ImpersonateServiceAccount, ts) } - client, err := google.DefaultClient(ctx, gdns.NdevClouddnsReadwriteScope) + client, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("unable to get Google Cloud client: %w", err) } @@ -435,7 +416,7 @@ func newClientFromServiceAccountKey(ctx context.Context, config *Config, saKey [ return newImpersonateClient(ctx, config.ImpersonateServiceAccount, conf.TokenSource(ctx)) } - conf, err := google.JWTConfigFromJSON(saKey, gdns.NdevClouddnsReadwriteScope) + conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("unable to acquire config: %w", err) } @@ -446,7 +427,7 @@ func newClientFromServiceAccountKey(ctx context.Context, config *Config, saKey [ func newImpersonateClient(ctx context.Context, impersonateServiceAccount string, ts oauth2.TokenSource) (*http.Client, error) { impersonatedTS, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ TargetPrincipal: impersonateServiceAccount, - Scopes: []string{gdns.NdevClouddnsReadwriteScope}, + Scopes: []string{dns.NdevClouddnsReadwriteScope}, }, option.WithTokenSource(ts)) if err != nil { return nil, fmt.Errorf("unable to create impersonated credentials: %w", err) @@ -460,7 +441,6 @@ func mustUnquote(raw string) string { if err != nil { return raw } - return clean } diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 28b08a2f9..7fda2f8f6 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -52,7 +52,7 @@ func TestNewDNSProvider(t *testing.T) { envServiceAccountFile: "", // as Travis run on GCE, we have to alter env envGoogleApplicationCredentials: "not-a-secret-file", - envMetadataHost: "http://example.com", // defined here to avoid the client cache. + envMetadataHost: "http://lego.wtf", // defined here to avoid the client cache. }, // the error message varies according to the OS used. expected: "googlecloud: unable to get Google Cloud client: google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: ", @@ -63,7 +63,7 @@ func TestNewDNSProvider(t *testing.T) { EnvProject: "", envServiceAccountFile: "", // as Travis run on GCE, we have to alter env - envMetadataHost: "http://example.com", + envMetadataHost: "http://lego.wtf", }, expected: "googlecloud: project name missing", }, @@ -86,7 +86,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -126,7 +125,6 @@ func TestNewDNSProviderConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() config := NewDefaultConfig() @@ -156,7 +154,7 @@ func TestPresentNoExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "example.com."). + With("dnsName", "lego.wtf."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords @@ -165,7 +163,7 @@ func TestPresentNoExistingRR(t *testing.T) { Rrsets: []*dns.ResourceRecordSet{}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.example.com."). + With("name", "_acme-challenge.lego.wtf."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -191,7 +189,7 @@ func TestPresentNoExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "example.com" + domain := "lego.wtf" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -207,21 +205,21 @@ func TestPresentWithExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "example.com."). + With("dnsName", "lego.wtf."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.example.com.", + Name: "_acme-challenge.lego.wtf.", Rrdatas: []string{`"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.example.com."). + With("name", "_acme-challenge.lego.wtf."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -239,14 +237,12 @@ func TestPresentWithExistingRR(t *testing.T) { } var prevVal string - for _, addition := range chgReq.Additions { for _, value := range addition.Rrdatas { if prevVal == value { http.Error(rw, fmt.Sprintf("The resource %s already exists", value), http.StatusConflict) return } - prevVal = value } } @@ -264,7 +260,7 @@ func TestPresentWithExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "example.com" + domain := "lego.wtf" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -280,27 +276,27 @@ func TestPresentSkipExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "example.com."). + With("dnsName", "lego.wtf."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.example.com.", + Name: "_acme-challenge.lego.wtf.", Rrdatas: []string{`"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.example.com."). + With("name", "_acme-challenge.lego.wtf."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). Build(t) - domain := "example.com" + domain := "lego.wtf" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -356,7 +352,7 @@ func TestLiveCleanUp(t *testing.T) { func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() + config.HTTPClient = &http.Client{Timeout: 10 * time.Second} config.Project = "manhattan" p, err := NewDNSProviderConfig(config) diff --git a/providers/dns/gcore/gcore.go b/providers/dns/gcore/gcore.go index 9b98f28d4..646c5ab1c 100644 --- a/providers/dns/gcore/gcore.go +++ b/providers/dns/gcore/gcore.go @@ -1,16 +1,17 @@ -// 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" ) // Environment variables names. @@ -25,17 +26,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 +56,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 +79,91 @@ 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 + } + + 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..a5eddee7c 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" ) @@ -33,7 +34,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -43,7 +43,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 +78,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) } @@ -91,7 +93,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,10 +106,36 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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..b76da4388 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,10 +45,9 @@ 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{} - err := c.doRequest(ctx, http.MethodGet, endpoint, nil, &zone) if err != nil { return Zone{}, fmt.Errorf("get zone %s: %w", name, err) @@ -60,10 +59,9 @@ 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 - err := c.doRequest(ctx, http.MethodGet, endpoint, nil, &result) if err != nil { return RRSet{}, fmt.Errorf("get txt records %s -> %s: %w", zone, name, err) @@ -75,7 +73,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 +104,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) } @@ -182,7 +180,6 @@ func parseError(resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := APIError{StatusCode: resp.StatusCode} - err := json.Unmarshal(raw, &errAPI) if err != nil { errAPI.Message = string(raw) diff --git a/providers/dns/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.go b/providers/dns/glesys/glesys.go index 729756235..4b0d545ed 100644 --- a/providers/dns/glesys/glesys.go +++ b/providers/dns/glesys/glesys.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/glesys/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -100,8 +99,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -136,7 +133,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // save data necessary for CleanUp d.activeRecords[info.EffectiveFQDN] = recordID - return nil } @@ -147,7 +143,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() - if _, ok := d.activeRecords[info.EffectiveFQDN]; !ok { // if there is no cleanup information then just return return nil diff --git a/providers/dns/glesys/glesys.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/glesys/glesys_test.go b/providers/dns/glesys/glesys_test.go index f2d65e514..d5fdf36da 100644 --- a/providers/dns/glesys/glesys_test.go +++ b/providers/dns/glesys/glesys_test.go @@ -56,7 +56,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -131,7 +130,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -145,7 +143,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/glesys/internal/client.go b/providers/dns/glesys/internal/client.go index ee6ebc058..20bc363ba 100644 --- a/providers/dns/glesys/internal/client.go +++ b/providers/dns/glesys/internal/client.go @@ -102,7 +102,6 @@ func (c *Client) do(req *http.Request) (*apiResponse, error) { } var response apiResponse - err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go index 1603bb57e..38e470509 100644 --- a/providers/dns/godaddy/godaddy.go +++ b/providers/dns/godaddy/godaddy.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/godaddy/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -96,8 +95,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -131,7 +128,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } var newRecords []internal.DNSRecord - for _, record := range existingRecords { if record.Data != "" { newRecords = append(newRecords, record) @@ -178,7 +174,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordsToKeep []internal.DNSRecord - for _, record := range existingRecords { if record.Data != info.Value && record.Data != "" { recordsToKeep = append(recordsToKeep, record) diff --git a/providers/dns/godaddy/godaddy.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/godaddy_test.go b/providers/dns/godaddy/godaddy_test.go index 38b39672e..4cb5f2721 100644 --- a/providers/dns/godaddy/godaddy_test.go +++ b/providers/dns/godaddy/godaddy_test.go @@ -56,7 +56,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -127,7 +126,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -141,7 +139,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/godaddy/internal/client.go b/providers/dns/godaddy/internal/client.go index 9dd337ddc..bf30d437f 100644 --- a/providers/dns/godaddy/internal/client.go +++ b/providers/dns/godaddy/internal/client.go @@ -48,7 +48,6 @@ func (c *Client) GetRecords(ctx context.Context, domainZone, rType, recordName s } var records []DNSRecord - err = c.do(req, &records) if err != nil { return nil, err @@ -142,7 +141,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/godaddy/internal/types.go b/providers/dns/godaddy/internal/types.go index 3bd5c9560..a97a97896 100644 --- a/providers/dns/godaddy/internal/types.go +++ b/providers/dns/godaddy/internal/types.go @@ -1,9 +1,6 @@ package internal -import ( - "fmt" - "strings" -) +import "fmt" // DNSRecord a DNS record. type DNSRecord struct { @@ -26,16 +23,13 @@ type APIError struct { } func (a APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%s: %s", a.Code, a.Message) + msg := fmt.Sprintf("%s: %s", a.Code, a.Message) for _, field := range a.Fields { - msg.WriteString(" ") - msg.WriteString(field.String()) + msg += " " + field.String() } - return msg.String() + return msg } type Field struct { 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..4dcd8e071 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -2,28 +2,28 @@ package hetzner import ( + "context" "errors" + "fmt" "net/http" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal" ) // Environment variables names. const ( - // Deprecated: use EnvAPIToken instead. - EnvAPIKey = legacy.EnvAPIKey - EnvAPIToken = hetznerv1.EnvAPIToken + envNamespace = "HETZNER_" - EnvTTL = hetznerv1.EnvTTL - EnvPropagationTimeout = hetznerv1.EnvPropagationTimeout - EnvPollingInterval = hetznerv1.EnvPollingInterval - EnvHTTPTimeout = hetznerv1.EnvHTTPTimeout + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const minTTL = 60 @@ -32,11 +32,7 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - // Deprecated: use APIToken instead - APIKey string - - APIToken string - + APIKey string PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -57,41 +53,22 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - provider challenge.ProviderTimeout + config *Config + client *internal.Client } // NewDNSProvider returns a DNSProvider instance configured for hetzner. +// Credentials must be passed in the environment variable: HETZNER_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - foundAPIToken := env.GetOrFile(EnvAPIToken) != "" - foundAPIKey := env.GetOrFile(EnvAPIKey) != "" - - switch { - case foundAPIToken: - provider, err := hetznerv1.NewDNSProvider() - if err != nil { - return nil, err - } - - return &DNSProvider{provider: provider}, nil - - case foundAPIKey: - log.Warnf("APIKey (legacy Hetzner DNS API) is deprecated, please use APIToken (Hetzner Cloud API) instead.") - - provider, err := legacy.NewDNSProvider() - if err != nil { - return nil, err - } - - return &DNSProvider{provider: provider}, nil - - default: - provider, err := hetznerv1.NewDNSProvider() - if err != nil { - return nil, err - } - - return &DNSProvider{provider: provider}, nil + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("hetzner: %w", err) } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) } // NewDNSProviderConfig return a DNSProvider instance configured for hetzner. @@ -100,57 +77,98 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hetzner: the configuration of the DNS provider is nil") } - switch { - case config.APIToken != "": - cfg := &hetznerv1.Config{ - APIToken: config.APIToken, - PropagationTimeout: config.PropagationTimeout, - PollingInterval: config.PollingInterval, - TTL: config.TTL, - HTTPClient: config.HTTPClient, - } - - provider, err := hetznerv1.NewDNSProviderConfig(cfg) - if err != nil { - return nil, err - } - - return &DNSProvider{provider: provider}, nil - - case config.APIKey != "": - log.Warnf("%s (legacy Hetzner DNS API) is deprecated, please use %s (Hetzner Cloud API) instead.", EnvAPIKey, EnvAPIToken) - - cfg := &legacy.Config{ - APIKey: config.APIKey, - PropagationTimeout: config.PropagationTimeout, - PollingInterval: config.PollingInterval, - TTL: config.TTL, - HTTPClient: config.HTTPClient, - } - - provider, err := legacy.NewDNSProviderConfig(cfg) - if err != nil { - return nil, err - } - - return &DNSProvider{provider: provider}, nil + if config.APIKey == "" { + return nil, errors.New("hetzner: credentials missing") } - return nil, errors.New("hetzner: credentials missing") + if config.TTL < minTTL { + return nil, fmt.Errorf("hetzner: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := internal.NewClient(config.APIKey) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{config: config, client: client}, nil } // 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.provider.Timeout() + 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 { - return d.provider.Present(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) + } + + zone := dns01.UnFqdn(authZone) + + ctx := context.Background() + + zoneID, err := d.client.GetZoneID(ctx, zone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + record := internal.DNSRecord{ + Type: "TXT", + Name: subDomain, + Value: info.Value, + TTL: d.config.TTL, + ZoneID: zoneID, + } + + if err := d.client.CreateRecord(ctx, record); err != nil { + return fmt.Errorf("hetzner: failed to add TXT record: fqdn=%s, zoneID=%s: %w", info.EffectiveFQDN, zoneID, err) + } + + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - return d.provider.CleanUp(domain, token, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) + } + + zone := dns01.UnFqdn(authZone) + + ctx := context.Background() + + zoneID, err := d.client.GetZoneID(ctx, zone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + record, err := d.client.GetTxtRecord(ctx, subDomain, info.Value, zoneID) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + if err := d.client.DeleteRecord(ctx, record.ID); err != nil { + return fmt.Errorf("hetzner: failed to delete TXT record: id=%s, name=%s: %w", record.ID, record.Name, err) + } + + return nil } diff --git a/providers/dns/hetzner/hetzner.toml b/providers/dns/hetzner/hetzner.toml index 40d4cea72..033ae2d2f 100644 --- a/providers/dns/hetzner/hetzner.toml +++ b/providers/dns/hetzner/hetzner.toml @@ -5,18 +5,18 @@ Code = "hetzner" Since = "v3.7.0" Example = ''' -HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hetzner -d '*.example.com' -d example.com run +HETZNER_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ +lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ''' [Configuration] [Configuration.Credentials] - HETZNER_API_TOKEN = "API token" + HETZNER_API_KEY = "API key" [Configuration.Additional] HETZNER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" HETZNER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://docs.hetzner.cloud/reference/cloud#dns" + API = "https://dns.hetzner.com/api-docs" diff --git a/providers/dns/hetzner/hetzner_test.go b/providers/dns/hetzner/hetzner_test.go index 430f0270b..d028fd06b 100644 --- a/providers/dns/hetzner/hetzner_test.go +++ b/providers/dns/hetzner/hetzner_test.go @@ -3,72 +3,52 @@ package hetzner import ( "testing" - "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var envTest = tester.NewEnvTest(EnvAPIKey, EnvAPIToken) +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvAPIKey). + WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { - desc string - envVars map[string]string - - expectedProvider challenge.ProviderTimeout - expectedError string + desc string + envVars map[string]string + expected string }{ { - desc: "success (v1)", - envVars: map[string]string{ - EnvAPIToken: "123", - }, - expectedProvider: &hetznerv1.DNSProvider{}, - }, - { - desc: "success (legacy)", + desc: "success", envVars: map[string]string{ EnvAPIKey: "123", }, - expectedProvider: &legacy.DNSProvider{}, - }, - { - desc: "success (both)", - envVars: map[string]string{ - EnvAPIKey: "123", - EnvAPIToken: "123", - }, - expectedProvider: &hetznerv1.DNSProvider{}, }, { desc: "missing credentials", envVars: map[string]string{ - EnvAPIKey: "", - EnvAPIToken: "", + EnvAPIKey: "", }, - expectedError: "hetzner: some credentials information are missing: HETZNER_API_TOKEN", + expected: "hetzner: some credentials information are missing: HETZNER_API_KEY", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) p, err := NewDNSProvider() - if test.expectedError == "" { + if test.expected == "" { require.NoError(t, err) - assert.IsType(t, test.expectedProvider, p.provider) require.NotNil(t, p) + require.NotNil(t, p.config) } else { - require.EqualError(t, err, test.expectedError) + require.EqualError(t, err, test.expected) } }) } @@ -78,53 +58,68 @@ func TestNewDNSProviderConfig(t *testing.T) { testCases := []struct { desc string apiKey string - apiToken string ttl int - - expectedProvider challenge.ProviderTimeout - expectedError string + expected string }{ { - desc: "success (v1)", - ttl: minTTL, - apiToken: "123", - expectedProvider: &hetznerv1.DNSProvider{}, + desc: "success", + ttl: minTTL, + apiKey: "123", }, { - desc: "success (legacy)", - ttl: minTTL, - apiKey: "456", - expectedProvider: &legacy.DNSProvider{}, + desc: "missing credentials", + ttl: minTTL, + expected: "hetzner: credentials missing", }, { - desc: "success (both)", - ttl: minTTL, - apiToken: "123", - apiKey: "456", - expectedProvider: &hetznerv1.DNSProvider{}, - }, - { - desc: "missing credentials", - ttl: minTTL, - expectedError: "hetzner: credentials missing", + desc: "invalid TTL", + apiKey: "123", + ttl: 10, + expected: "hetzner: invalid TTL, TTL (10) must be greater than 60", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() - config.APIToken = test.apiToken config.APIKey = test.apiKey config.TTL = test.ttl p, err := NewDNSProviderConfig(config) - if test.expectedError == "" { + if test.expected == "" { require.NoError(t, err) - assert.IsType(t, test.expectedProvider, p.provider) + require.NotNil(t, p) + require.NotNil(t, p.config) } else { - require.EqualError(t, err, test.expectedError) + require.EqualError(t, err, test.expected) } }) } } + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/hetzner/internal/legacy/internal/client.go b/providers/dns/hetzner/internal/client.go similarity index 99% rename from providers/dns/hetzner/internal/legacy/internal/client.go rename to providers/dns/hetzner/internal/client.go index cd187f6e5..381922264 100644 --- a/providers/dns/hetzner/internal/legacy/internal/client.go +++ b/providers/dns/hetzner/internal/client.go @@ -83,7 +83,6 @@ func (c *Client) getRecords(ctx context.Context, zoneID string) (*DNSRecords, er } records := &DNSRecords{} - err = json.Unmarshal(raw, records) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -191,7 +190,6 @@ func (c *Client) getZones(ctx context.Context, name string) (*Zones, error) { } zones := &Zones{} - err = json.Unmarshal(raw, zones) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/hetzner/internal/legacy/internal/client_test.go b/providers/dns/hetzner/internal/client_test.go similarity index 100% rename from providers/dns/hetzner/internal/legacy/internal/client_test.go rename to providers/dns/hetzner/internal/client_test.go diff --git a/providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record-request.json b/providers/dns/hetzner/internal/fixtures/create_txt_record-request.json similarity index 100% rename from providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record-request.json rename to providers/dns/hetzner/internal/fixtures/create_txt_record-request.json diff --git a/providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record.json b/providers/dns/hetzner/internal/fixtures/create_txt_record.json similarity index 100% rename from providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record.json rename to providers/dns/hetzner/internal/fixtures/create_txt_record.json diff --git a/providers/dns/hetzner/internal/legacy/internal/fixtures/get_txt_record.json b/providers/dns/hetzner/internal/fixtures/get_txt_record.json similarity index 100% rename from providers/dns/hetzner/internal/legacy/internal/fixtures/get_txt_record.json rename to providers/dns/hetzner/internal/fixtures/get_txt_record.json diff --git a/providers/dns/hetzner/internal/legacy/internal/fixtures/get_zone_id.json b/providers/dns/hetzner/internal/fixtures/get_zone_id.json similarity index 100% rename from providers/dns/hetzner/internal/legacy/internal/fixtures/get_zone_id.json rename to providers/dns/hetzner/internal/fixtures/get_zone_id.json diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json deleted file mode 100644 index 210f84435..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ttl": 120, - "records": [ - { - "value": "\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"" - } - ] -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json deleted file mode 100644 index 2341c7e6e..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "action": { - "id": 1, - "command": "add_rrset_records", - "status": "running", - "progress": 50, - "started": "2016-01-30T23:55:00+00:00", - "finished": null, - "resources": [ - { - "id": 42, - "type": "zone" - } - ], - "error": null - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json deleted file mode 100644 index 2a4472f67..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "action": { - "id": 1, - "command": "remove_rrset_records", - "status": "error", - "started": "2016-01-30T23:55:00+00:00", - "finished": "2016-01-30T23:55:00+00:00", - "progress": 50, - "resources": [ - { - "id": 42, - "type": "zone" - } - ], - "error": { - "code": "action_failed", - "message": "Action failed" - } - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json deleted file mode 100644 index dcec6c2cd..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "action": { - "id": 1, - "command": "remove_rrset_records", - "status": "running", - "started": "2016-01-30T23:55:00+00:00", - "finished": "2016-01-30T23:55:00+00:00", - "progress": 50, - "resources": [ - { - "id": 42, - "type": "zone" - } - ] - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json deleted file mode 100644 index 6b7267c07..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "action": { - "id": 1, - "command": "remove_rrset_records", - "status": "success", - "started": "2016-01-30T23:55:00+00:00", - "finished": "2016-01-30T23:55:00+00:00", - "progress": 100, - "resources": [ - { - "id": 42, - "type": "zone" - } - ] - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json deleted file mode 100644 index 982273b67..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "records": [ - { - "value": "\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"" - } - ] -} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json deleted file mode 100644 index 1b10dfd5e..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "action": { - "id": 1, - "command": "remove_rrset_records", - "status": "running", - "progress": 50, - "started": "2016-01-30T23:55:00+00:00", - "finished": null, - "resources": [ - { - "id": 42, - "type": "zone" - } - ], - "error": null - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go deleted file mode 100644 index b31c766ce..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ /dev/null @@ -1,209 +0,0 @@ -// Package hetznerv1 implements a DNS provider for solving the DNS-01 challenge using Hetzner. -package hetznerv1 - -import ( - "context" - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/cenkalti/backoff/v5" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/platform/wait" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "golang.org/x/net/idna" -) - -// Environment variables names. -const ( - envNamespace = "HETZNER_" - - EnvAPIToken = envNamespace + "API_TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIToken string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for Hetzner. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIToken) - if err != nil { - return nil, fmt.Errorf("hetzner: %w", err) - } - - config := NewDefaultConfig() - config.APIToken = values[EnvAPIToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Hetzner. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("hetzner: the configuration of the DNS provider is nil") - } - - if config.APIToken == "" { - return nil, errors.New("hetzner: credentials missing") - } - - client, err := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.APIToken), - ), - ) - if err != nil { - return nil, fmt.Errorf("hetzner: %w", err) - } - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - subDomainPunnycoded, err := idna.ToASCII(dns01.UnFqdn(subDomain)) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - zone, err := idna.ToASCII(dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - records := []internal.Record{{Value: strconv.Quote(info.Value)}} - - action, err := d.client.AddRRSetRecords(ctx, zone, "TXT", subDomainPunnycoded, d.config.TTL, records) - if err != nil { - return fmt.Errorf("hetzner: add RRSet records: %w", err) - } - - err = d.waitAction(ctx, action.ID) - if err != nil { - return fmt.Errorf("hetzner: wait (add RRSet 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) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - subDomainPunnycoded, err := idna.ToASCII(dns01.UnFqdn(subDomain)) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - zone, err := idna.ToASCII(dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - records := []internal.Record{{Value: strconv.Quote(info.Value)}} - - action, err := d.client.RemoveRRSetRecords(ctx, zone, "TXT", subDomainPunnycoded, records) - if err != nil { - return fmt.Errorf("hetzner: remove RRSet records: %w", err) - } - - err = d.waitAction(ctx, action.ID) - if err != nil { - return fmt.Errorf("hetzner: wait (remove RRSet 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) waitAction(ctx context.Context, actionID int64) error { - return wait.Retry(ctx, - func() error { - result, err := d.client.GetAction(ctx, actionID) - if err != nil { - return backoff.Permanent(fmt.Errorf("get action %d: %w", actionID, err)) - } - - switch result.Status { - case internal.StatusRunning: - return fmt.Errorf("action %d is %s", actionID, internal.StatusRunning) - - case internal.StatusError: - return backoff.Permanent(fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo)) - - default: - return nil - } - }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), - ) -} diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go deleted file mode 100644 index bf52baa35..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package hetznerv1 - -import ( - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIToken: "secret", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "hetzner: some credentials information are missing: HETZNER_API_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "secret", - }, - { - desc: "missing credentials", - expected: "hetzner: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIToken = "secret" - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", - servermock.ResponseFromFixture("add_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_success.json")). - Build(t) - - err := provider.Present("example.com", "", "foobar") - require.NoError(t, err) -} - -func TestDNSProvider_Present_error(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", - servermock.ResponseFromFixture("add_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_error.json")). - Build(t) - - provider.config.PollingInterval = 20 * time.Millisecond - provider.config.PropagationTimeout = 1 * time.Second - - err := provider.Present("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (add RRSet records): action 1: error: action_failed: Action failed") -} - -func TestDNSProvider_Present_running(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", - servermock.ResponseFromFixture("add_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_running.json")). - Build(t) - - provider.config.PollingInterval = 20 * time.Millisecond - provider.config.PropagationTimeout = 1 * time.Second - - err := provider.Present("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (add RRSet records): action 1 is running") -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", - servermock.ResponseFromFixture("remove_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_success.json")). - Build(t) - - err := provider.CleanUp("example.com", "", "foobar") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp_error(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", - servermock.ResponseFromFixture("remove_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_error.json")). - Build(t) - - provider.config.PollingInterval = 20 * time.Millisecond - provider.config.PropagationTimeout = 1 * time.Second - - err := provider.CleanUp("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (remove RRSet records): action 1: error: action_failed: Action failed") -} - -func TestDNSProvider_CleanUp_running(t *testing.T) { - provider := mockBuilder(). - Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", - servermock.ResponseFromFixture("remove_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). - Route("GET /actions/1", - servermock.ResponseFromFixture("get_action_running.json")). - Build(t) - - provider.config.PollingInterval = 20 * time.Millisecond - provider.config.PropagationTimeout = 1 * time.Second - - err := provider.CleanUp("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (remove RRSet records): action 1 is running") -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client.go b/providers/dns/hetzner/internal/hetznerv1/internal/client.go deleted file mode 100644 index 2f29f642a..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client.go +++ /dev/null @@ -1,183 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "golang.org/x/oauth2" -) - -const defaultBaseURL = "https://api.hetzner.cloud/v1" - -const ( - StatusRunning = "running" - StatusSuccess = "success" - StatusError = "error" -) - -// Client the Hetzner API client. -type Client struct { - BaseURL *url.URL - httpClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(hc *http.Client) (*Client, error) { - baseURL, _ := url.Parse(defaultBaseURL) - - if hc == nil { - hc = &http.Client{Timeout: 10 * time.Second} - } - - return &Client{ - BaseURL: baseURL, - httpClient: hc, - }, nil -} - -// AddRRSetRecords adds records to an RRSet. -// https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset -func (c *Client) AddRRSetRecords(ctx context.Context, zoneIDName, recordType, recordName string, ttl int, records []Record) (*Action, error) { - endpoint := c.BaseURL.JoinPath("zones", zoneIDName, "rrsets", recordName, recordType, "actions", "add_records") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, RRSet{TTL: ttl, Records: records}) - if err != nil { - return nil, err - } - - var result ActionResponse - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Action, nil -} - -// RemoveRRSetRecords removes records from an RRSet. -// https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset -func (c *Client) RemoveRRSetRecords(ctx context.Context, zoneIDName, recordType, recordName string, records []Record) (*Action, error) { - endpoint := c.BaseURL.JoinPath("zones", zoneIDName, "rrsets", recordName, recordType, "actions", "remove_records") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, RRSet{Records: records}) - if err != nil { - return nil, err - } - - var result ActionResponse - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Action, nil -} - -// GetAction gets an action. -// https://docs.hetzner.cloud/reference/cloud#actions-get-an-action -func (c *Client) GetAction(ctx context.Context, id int64) (*Action, error) { - endpoint := c.BaseURL.JoinPath("actions", strconv.FormatInt(id, 10)) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result ActionResponse - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Action, nil -} - -func (c *Client) do(req *http.Request, result any) error { - resp, err := c.httpClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} - -func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client { - if client == nil { - client = &http.Client{Timeout: 5 * time.Second} - } - - client.Transport = &oauth2.Transport{ - Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}), - Base: client.Transport, - } - - return client -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go deleted file mode 100644 index 6fd3d77a7..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go +++ /dev/null @@ -1,154 +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(OAuthStaticAccessToken(server.Client(), "secret")) - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - - return client, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - WithAuthorization("Bearer secret"), - ) -} - -func TestClient_AddRRSetRecords(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", - servermock.ResponseFromFixture("add_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). - Build(t) - - records := []Record{{ - Value: "198.51.100.1", - Comment: "My web server at Hetzner Cloud.", - }} - - result, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 3600, records) - require.NoError(t, err) - - expected := &Action{ - ID: 1, - Command: "add_rrset_records", - Status: "running", - Progress: 50, - Resources: []Resources{{ID: 590000000000000, Type: "zone"}}, - } - - assert.Equal(t, expected, result) -} - -func TestClient_AddRRSetRecords_error_invalid_input(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", - servermock.ResponseFromFixture("error-invalid_input.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - records := []Record{{ - Value: "198.51.100.1", - Comment: "My web server at Hetzner Cloud.", - }} - - _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) - require.EqualError(t, err, "invalid_input: invalid input in field 'broken_field': is too longfield: broken_field: is too long") -} - -func TestClient_AddRRSetRecords_error_resource_limit_exceeded(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", - servermock.ResponseFromFixture("error-resource_limit_exceeded.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - records := []Record{{ - Value: "198.51.100.1", - Comment: "My web server at Hetzner Cloud.", - }} - - _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) - require.EqualError(t, err, "resource_limit_exceeded: project limit exceededlimit: project_limit") -} - -func TestClient_AddRRSetRecords_error_deprecated_api_endpoint(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", - servermock.ResponseFromFixture("error-deprecated_api_endpoint.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - records := []Record{{ - Value: "198.51.100.1", - Comment: "My web server at Hetzner Cloud.", - }} - - _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) - require.EqualError(t, err, "deprecated_api_endpoint: API functionality was removed: https://docs.hetzner.cloud/changelog#2023-07-20-foo-endpoint-is-deprecated") -} - -func TestClient_RemoveRRSetRecords(t *testing.T) { - client := mockBuilder(). - Route("POST /zones/example.com/rrsets/www/TXT/actions/remove_records", - servermock.ResponseFromFixture("remove_rrset_records.json"), - servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). - Build(t) - - records := []Record{{ - Value: "198.51.100.1", - Comment: "My web server at Hetzner Cloud.", - }} - - result, err := client.RemoveRRSetRecords(t.Context(), "example.com", "TXT", "www", records) - require.NoError(t, err) - - expected := &Action{ - ID: 1, - Command: "remove_rrset_records", - Status: "running", - Progress: 50, - Resources: []Resources{{ID: 42, Type: "zone"}}, - } - - assert.Equal(t, expected, result) -} - -func TestClient_GetAction(t *testing.T) { - client := mockBuilder(). - Route("GET /actions/123", servermock.ResponseFromFixture("get_action.json")). - Route("/", servermock.DumpRequest()). - Build(t) - - result, err := client.GetAction(t.Context(), 123) - require.NoError(t, err) - - expected := &Action{ - ID: 590000000000000, - Command: "start_resource", - Status: "running", - Progress: 100, - Resources: []Resources{{ID: 590000000000000, Type: "server"}}, - ErrorInfo: &ErrorInfo{ - Code: "action_failed", - Message: "Action failed", - }, - } - - assert.Equal(t, expected, result) -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json deleted file mode 100644 index cba0f34d3..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ttl": 3600, - "records": [ - { - "value": "198.51.100.1", - "comment": "My web server at Hetzner Cloud." - } - ] -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json deleted file mode 100644 index 7267b02cb..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "action": { - "id": 1, - "command": "add_rrset_records", - "status": "running", - "progress": 50, - "started": "2016-01-30T23:55:00+00:00", - "finished": null, - "resources": [ - { - "id": 590000000000000, - "type": "zone" - } - ], - "error": null - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json deleted file mode 100644 index 4d8fb945d..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "error": { - "code": "deprecated_api_endpoint", - "message": "API functionality was removed", - "details": { - "announcement": "https://docs.hetzner.cloud/changelog#2023-07-20-foo-endpoint-is-deprecated" - } - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json deleted file mode 100644 index e05bf7a3e..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "error": { - "code": "invalid_input", - "message": "invalid input in field 'broken_field': is too long", - "details": { - "fields": [ - { - "name": "broken_field", - "messages": [ - "is too long" - ] - } - ] - } - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json deleted file mode 100644 index 9072d10e3..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "error": { - "code": "resource_limit_exceeded", - "message": "project limit exceeded", - "details": { - "limits": [ - { - "name": "project_limit" - } - ] - } - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json deleted file mode 100644 index 19278fc51..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "action": { - "id": 590000000000000, - "command": "start_resource", - "status": "running", - "started": "2016-01-30T23:55:00+00:00", - "finished": "2016-01-30T23:55:00+00:00", - "progress": 100, - "resources": [ - { - "id": 590000000000000, - "type": "server" - } - ], - "error": { - "code": "action_failed", - "message": "Action failed" - } - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json deleted file mode 100644 index 778e051b4..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "records": [ - { - "value": "198.51.100.1", - "comment": "My web server at Hetzner Cloud." - } - ] -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json deleted file mode 100644 index 1b10dfd5e..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "action": { - "id": 1, - "command": "remove_rrset_records", - "status": "running", - "progress": 50, - "started": "2016-01-30T23:55:00+00:00", - "finished": null, - "resources": [ - { - "id": 42, - "type": "zone" - } - ], - "error": null - } -} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go deleted file mode 100644 index 2b38a8a8c..000000000 --- a/providers/dns/hetzner/internal/hetznerv1/internal/types.go +++ /dev/null @@ -1,98 +0,0 @@ -package internal - -import ( - "fmt" - "strings" -) - -type APIError struct { - ErrorInfo ErrorInfo `json:"error"` -} - -type ErrorInfo struct { - Code string `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Details ErrorDetails `json:"details"` -} - -func (i *ErrorInfo) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%s: %s", i.Code, i.Message) - - if i.Details.Announcement != "" { - _, _ = fmt.Fprintf(msg, ": %s", i.Details.Announcement) - } - - for _, limit := range i.Details.Limits { - _, _ = fmt.Fprintf(msg, "limit: %s", limit.Name) - } - - for _, field := range i.Details.Fields { - _, _ = fmt.Fprintf(msg, "field: %s: %s", field.Name, strings.Join(field.Messages, ", ")) - } - - return msg.String() -} - -type ErrorDetails struct { - Announcement string `json:"announcement,omitempty"` - Limits []LimitError `json:"limits,omitempty"` - Fields []FieldError `json:"fields,omitempty"` -} - -type FieldError struct { - Name string `json:"name,omitempty"` - Messages []string `json:"messages,omitempty"` -} - -type LimitError struct { - Name string `json:"name,omitempty"` -} - -func (a *APIError) Error() string { - return a.ErrorInfo.Error() -} - -type RRSet struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - TTL int `json:"ttl,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Protection *Protection `json:"protection,omitempty"` - Records []Record `json:"records,omitempty"` - ZoneID int `json:"zone,omitempty"` -} - -type Protection struct { - Change bool `json:"change,omitempty"` -} - -type Record struct { - Value string `json:"value,omitempty"` - Comment string `json:"comment,omitempty"` -} - -type ActionResponse struct { - Action *Action `json:"action,omitempty"` -} - -type Action struct { - ID int64 `json:"id,omitempty"` - Command string `json:"command,omitempty"` - - // It can be: `running`, `success`, `error`. - // https://docs.hetzner.cloud/reference/cloud#zone-actions-get-an-action - // https://docs.hetzner.cloud/reference/cloud#zone-actions - Status string `json:"status,omitempty"` - Progress int `json:"progress,omitempty"` - - Resources []Resources `json:"resources,omitempty"` - ErrorInfo *ErrorInfo `json:"error,omitempty"` -} - -type Resources struct { - ID int64 `json:"id,omitempty"` - Type string `json:"type,omitempty"` -} diff --git a/providers/dns/hetzner/internal/legacy/hetzner.go b/providers/dns/hetzner/internal/legacy/hetzner.go deleted file mode 100644 index 393a3d671..000000000 --- a/providers/dns/hetzner/internal/legacy/hetzner.go +++ /dev/null @@ -1,177 +0,0 @@ -// Package legacy implements a DNS provider for solving the DNS-01 challenge using Hetzner DNS. -package legacy - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "HETZNER_" - - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -const minTTL = 60 - -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for hetzner. -// Credentials must be passed in the environment variable: HETZNER_API_KEY. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("hetzner (legacy): %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for hetzner. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("hetzner (legacy): the configuration of the DNS provider is nil") - } - - if config.APIKey == "" { - return nil, errors.New("hetzner (legacy): credentials missing") - } - - if config.TTL < minTTL { - return nil, fmt.Errorf("hetzner (legacy): invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client := internal.NewClient(config.APIKey) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - 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 to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner (legacy): could not find zone for domain %q: %w", domain, err) - } - - zone := dns01.UnFqdn(authZone) - - ctx := context.Background() - - zoneID, err := d.client.GetZoneID(ctx, zone) - if err != nil { - return fmt.Errorf("hetzner (legacy): %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) - if err != nil { - return fmt.Errorf("hetzner (legacy): %w", err) - } - - record := internal.DNSRecord{ - Type: "TXT", - Name: subDomain, - Value: info.Value, - TTL: d.config.TTL, - ZoneID: zoneID, - } - - if err := d.client.CreateRecord(ctx, record); err != nil { - return fmt.Errorf("hetzner (legacy): failed to add TXT record: fqdn=%s, zoneID=%s: %w", info.EffectiveFQDN, zoneID, err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner (legacy): could not find zone for domain %q: %w", domain, err) - } - - zone := dns01.UnFqdn(authZone) - - ctx := context.Background() - - zoneID, err := d.client.GetZoneID(ctx, zone) - if err != nil { - return fmt.Errorf("hetzner (legacy): %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) - if err != nil { - return fmt.Errorf("hetzner (legacy): %w", err) - } - - record, err := d.client.GetTxtRecord(ctx, subDomain, info.Value, zoneID) - if err != nil { - return fmt.Errorf("hetzner (legacy): %w", err) - } - - if err := d.client.DeleteRecord(ctx, record.ID); err != nil { - return fmt.Errorf("hetzner (legacy): failed to delete TXT record: id=%s, name=%s: %w", record.ID, record.Name, err) - } - - return nil -} diff --git a/providers/dns/hetzner/internal/legacy/hetzner_test.go b/providers/dns/hetzner/internal/legacy/hetzner_test.go deleted file mode 100644 index c9258ecf8..000000000 --- a/providers/dns/hetzner/internal/legacy/hetzner_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package legacy - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvAPIKey). - WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIKey: "123", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{ - EnvAPIKey: "", - }, - expected: "hetzner (legacy): some credentials information are missing: HETZNER_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - ttl int - expected string - }{ - { - desc: "success", - ttl: minTTL, - apiKey: "123", - }, - { - desc: "missing credentials", - ttl: minTTL, - expected: "hetzner (legacy): credentials missing", - }, - { - desc: "invalid TTL", - apiKey: "123", - ttl: 10, - expected: "hetzner (legacy): invalid TTL, TTL (10) must be greater than 60", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - config.TTL = test.ttl - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/hetzner/internal/legacy/internal/types.go b/providers/dns/hetzner/internal/types.go similarity index 91% rename from providers/dns/hetzner/internal/legacy/internal/types.go rename to providers/dns/hetzner/internal/types.go index 3b332cc8f..d0e284511 100644 --- a/providers/dns/hetzner/internal/legacy/internal/types.go +++ b/providers/dns/hetzner/internal/types.go @@ -25,12 +25,12 @@ type Zone struct { // Zones a set of DNS zones. type Zones struct { Zones []Zone `json:"zones"` - Meta Meta `json:"meta"` + Meta Meta `json:"meta,omitempty"` } // Meta response metadata. type Meta struct { - Pagination Pagination `json:"pagination"` + Pagination Pagination `json:"pagination,omitempty"` } // Pagination information about pagination. diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 1e022b630..87fc73d34 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -2,9 +2,11 @@ package hostingde import ( + "context" "errors" "fmt" "net/http" + "sync" "time" "github.com/go-acme/lego/v4/challenge" @@ -29,7 +31,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 +55,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,36 +83,140 @@ 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 + return &DNSProvider{ + config: config, + client: hostingde.NewClient(config.APIKey), + 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) + } 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..d7681f953 100644 --- a/providers/dns/hostingde/hostingde_test.go +++ b/providers/dns/hostingde/hostingde_test.go @@ -49,7 +49,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -59,7 +58,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 +101,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) } @@ -115,7 +116,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -129,7 +129,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go deleted file mode 100644 index 13d9ed0f8..000000000 --- a/providers/dns/hostinger/hostinger.go +++ /dev/null @@ -1,211 +0,0 @@ -// Package hostinger implements a DNS provider for solving the DNS-01 challenge using Hostinger. -package hostinger - -import ( - "context" - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/hostinger/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" -) - -// Environment variables names. -const ( - envNamespace = "HOSTINGER_" - - EnvAPIToken = envNamespace + "API_TOKEN" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIToken string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for Hostinger. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIToken) - if err != nil { - return nil, fmt.Errorf("hostinger: %w", err) - } - - config := NewDefaultConfig() - config.APIToken = values[EnvAPIToken] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Hostinger. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("hostinger: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIToken) - if err != nil { - return nil, fmt.Errorf("hostinger: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - 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("hostinger: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("hostinger: %w", err) - } - - ctx := context.Background() - - request := internal.ZoneRequest{ - Overwrite: false, - Zone: []internal.RecordSet{{ - Name: subDomain, - Type: "TXT", - TTL: d.config.TTL, - Records: []internal.Record{ - {Content: info.Value}, - }, - }}, - } - - err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) - if err != nil { - return fmt.Errorf("hostinger: update DNS records (add): %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hostinger: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("hostinger: %w", err) - } - - ctx := context.Background() - - recordSet, err := d.findRecordSet(ctx, authZone, subDomain) - if err != nil { - return fmt.Errorf("hostinger: %w", err) - } - - var newRecords []internal.Record - - for _, record := range recordSet.Records { - if record.Content == info.Value || record.Content == strconv.Quote(info.Value) { - continue - } - - newRecords = append(newRecords, record) - } - - recordSet.Records = newRecords - - if len(recordSet.Records) > 0 { - request := internal.ZoneRequest{ - Overwrite: true, - Zone: []internal.RecordSet{recordSet}, - } - - err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) - if err != nil { - return fmt.Errorf("hostinger: update DNS records (delete): %w", err) - } - - return nil - } - - filters := []internal.Filter{{ - Name: subDomain, - Type: "TXT", - }} - - err = d.client.DeleteDNSRecords(ctx, dns01.UnFqdn(authZone), filters) - if err != nil { - return fmt.Errorf("hostinger: delete DNS 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) findRecordSet(ctx context.Context, authZone, subDomain string) (internal.RecordSet, error) { - recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return internal.RecordSet{}, fmt.Errorf("get DNS records: %w", err) - } - - for _, recordSet := range recordSets { - if recordSet.Name != subDomain || recordSet.Type != "TXT" { - continue - } - - return recordSet, nil - } - - return internal.RecordSet{}, fmt.Errorf("no record found for domain %q and subdomain %q", authZone, subDomain) -} diff --git a/providers/dns/hostinger/hostinger.toml b/providers/dns/hostinger/hostinger.toml deleted file mode 100644 index a6f152e73..000000000 --- a/providers/dns/hostinger/hostinger.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Hostinger" -Description = '''''' -URL = "https://www.hostinger.com/" -Code = "hostinger" -Since = "v4.27.0" - -Example = ''' -HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --dns hostinger -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - HOSTINGER_API_TOKEN = "API Token" - [Configuration.Additional] - HOSTINGER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - HOSTINGER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - HOSTINGER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - HOSTINGER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://developers.hostinger.com/#tag/dns-zone" diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go deleted file mode 100644 index 90ecba529..000000000 --- a/providers/dns/hostinger/hostinger_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package hostinger - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIToken: "secret", - }, - }, - { - desc: "missing API token", - envVars: map[string]string{ - EnvAPIToken: "", - }, - expected: "hostinger: some credentials information are missing: HOSTINGER_API_TOKEN", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiToken string - expected string - }{ - { - desc: "success", - apiToken: "secret", - }, - { - desc: "missing API token", - expected: "hostinger: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIToken = test.apiToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIToken = "secret" - 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("PUT /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("update_dns_records.json"), - servermock.CheckRequestJSONBodyFromInternal("update_dns_records-request.json")). - Build(t) - - err := provider.Present("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp_update(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("get_dns_records_acme.json")). - Route("PUT /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("update_dns_records.json"), - servermock.CheckRequestJSONBodyFromInternal("update_dns_records_base-request.json")). - Build(t) - - err := provider.CleanUp("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp_delete(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("get_dns_records_empty.json")). - Route("DELETE /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("delete_dns_records.json"), - servermock.CheckRequestJSONBody(`{"filters":[{"name":"_acme-challenge","type":"TXT"}]}`)). - Build(t) - - err := provider.CleanUp("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/hostinger/internal/client.go b/providers/dns/hostinger/internal/client.go deleted file mode 100644 index 9da712d61..000000000 --- a/providers/dns/hostinger/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" -) - -const defaultBaseURL = "https://developers.hostinger.com" - -const authorizationHeader = "Authorization" - -// Client the Hostinger API client. -type Client struct { - token string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(token string) (*Client, error) { - if token == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - token: token, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -// GetDNSRecords retrieves DNS zone records for a specific domain. -// https://developers.hostinger.com/#tag/dns-zone/get/api/dns/v1/zones/{domain} -func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]RecordSet, error) { - endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result []RecordSet - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -// UpdateDNSRecords updates DNS records for the selected domain. -// https://developers.hostinger.com/#tag/dns-zone/put/api/dns/v1/zones/{domain} -func (c *Client) UpdateDNSRecords(ctx context.Context, domain string, zone ZoneRequest) error { - endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) - - req, err := newJSONRequest(ctx, http.MethodPut, endpoint, zone) - if err != nil { - return err - } - - return c.do(req, nil) -} - -// DeleteDNSRecords deletes DNS records for the selected domain. -// https://developers.hostinger.com/#tag/dns-zone/delete/api/dns/v1/zones/{domain} -func (c *Client) DeleteDNSRecords(ctx context.Context, domain string, filters []Filter) error { - endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, Filters{Filters: filters}) - if err != nil { - return err - } - - return c.do(req, nil) -} - -func (c *Client) do(req *http.Request, result any) error { - req.Header.Set(authorizationHeader, "Bearer "+c.token) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/hostinger/internal/client_test.go b/providers/dns/hostinger/internal/client_test.go deleted file mode 100644 index 69cab5587..000000000 --- a/providers/dns/hostinger/internal/client_test.go +++ /dev/null @@ -1,154 +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( - func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret") - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader().WithJSONHeaders(). - With("Authorization", "Bearer secret"), - ) -} - -func TestClient_GetDNSRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("get_dns_records.json")). - Build(t) - - records, err := client.GetDNSRecords(t.Context(), "example.com") - require.NoError(t, err) - - expected := []RecordSet{ - { - Name: "_acme-challenge", - Records: []Record{{ - Content: "aaa", - }}, - TTL: 14400, - Type: "TXT", - }, - { - Name: "_acme-challenge", - Records: []Record{{ - Content: "example.com.", - }}, - TTL: 14400, - Type: "A", - }, - } - - assert.Equal(t, expected, records) -} - -func TestClient_GetDNSRecords_error(t *testing.T) { - client := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("error_401.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.GetDNSRecords(t.Context(), "example.com") - - require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: Unauthenticated") -} - -func TestClient_UpdateDNSRecords(t *testing.T) { - client := mockBuilder(). - Route("PUT /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("update_dns_records.json"), - servermock.CheckRequestJSONBodyFromFixture("update_dns_records-request.json")). - Build(t) - - zone := ZoneRequest{ - Overwrite: false, - Zone: []RecordSet{ - { - Name: "_acme-challenge", - Records: []Record{ - {Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, - }, - TTL: 120, - Type: "TXT", - }, - }, - } - - err := client.UpdateDNSRecords(t.Context(), "example.com", zone) - require.NoError(t, err) -} - -func TestClient_UpdateDNSRecords_error(t *testing.T) { - client := mockBuilder(). - Route("PUT /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("error_422.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - zone := ZoneRequest{ - Zone: []RecordSet{{ - Name: "_acme-challenge", - Records: []Record{{ - Content: "aaa", - }}, - TTL: 14400, - Type: "TXT", - }}, - } - - err := client.UpdateDNSRecords(t.Context(), "example.com", zone) - - require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: The name field is required. (and 1 more error): field_1: The field_1 field is required., The field_1 must be a number.") -} - -func TestClient_DeleteDNSRecords(t *testing.T) { - client := mockBuilder(). - Route("DELETE /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("delete_dns_records.json"), - servermock.CheckRequestJSONBody(`{"filters":[{"name":"_acme-challenge","type":"TXT"}]}`)). - Build(t) - - filters := []Filter{{ - Name: "_acme-challenge", - Type: "TXT", - }} - - err := client.DeleteDNSRecords(t.Context(), "example.com", filters) - require.NoError(t, err) -} - -func TestClient_DeleteDNSRecords_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /api/dns/v1/zones/example.com", - servermock.ResponseFromFixture("error_401.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - filters := []Filter{{ - Name: "_acme-challenge", - Type: "TXT", - }} - - err := client.DeleteDNSRecords(t.Context(), "example.com", filters) - - require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: Unauthenticated") -} diff --git a/providers/dns/hostinger/internal/fixtures/delete_dns_records.json b/providers/dns/hostinger/internal/fixtures/delete_dns_records.json deleted file mode 100644 index 11d2582b4..000000000 --- a/providers/dns/hostinger/internal/fixtures/delete_dns_records.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "message": "Request accepted" -} diff --git a/providers/dns/hostinger/internal/fixtures/error_401.json b/providers/dns/hostinger/internal/fixtures/error_401.json deleted file mode 100644 index 1b7381ff6..000000000 --- a/providers/dns/hostinger/internal/fixtures/error_401.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "message": "Unauthenticated", - "correlation_id": "26a91bd9-f8c8-4a83-9df9-83e23d696fe3" -} diff --git a/providers/dns/hostinger/internal/fixtures/error_422.json b/providers/dns/hostinger/internal/fixtures/error_422.json deleted file mode 100644 index 6ec286823..000000000 --- a/providers/dns/hostinger/internal/fixtures/error_422.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "message": "The name field is required. (and 1 more error)", - "errors": { - "field_1": [ - "The field_1 field is required.", - "The field_1 must be a number." - ] - }, - "correlation_id": "26a91bd9-f8c8-4a83-9df9-83e23d696fe3" -} diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records.json b/providers/dns/hostinger/internal/fixtures/get_dns_records.json deleted file mode 100644 index e51edd4dc..000000000 --- a/providers/dns/hostinger/internal/fixtures/get_dns_records.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "name": "_acme-challenge", - "records": [ - { - "content": "aaa", - "is_disabled": false - } - ], - "ttl": 14400, - "type": "TXT" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com.", - "is_disabled": false - } - ], - "ttl": 14400, - "type": "A" - } -] diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json b/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json deleted file mode 100644 index 99a574514..000000000 --- a/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "name": "_acme-challenge", - "records": [ - { - "content": "aaa", - "is_disabled": false - }, - { - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ], - "ttl": 14400, - "type": "TXT" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com.", - "is_disabled": false - } - ], - "ttl": 14400, - "type": "A" - } -] diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json b/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json deleted file mode 100644 index 9989a3fc4..000000000 --- a/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "name": "_acme-challenge", - "records": [ - { - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ], - "ttl": 14400, - "type": "TXT" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com.", - "is_disabled": false - } - ], - "ttl": 14400, - "type": "A" - } -] diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json deleted file mode 100644 index 6f287b3fc..000000000 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "overwrite": false, - "zone": [ - { - "name": "_acme-challenge", - "records": [ - { - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ], - "ttl": 120, - "type": "TXT" - } - ] -} diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records.json b/providers/dns/hostinger/internal/fixtures/update_dns_records.json deleted file mode 100644 index 11d2582b4..000000000 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "message": "Request accepted" -} diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json deleted file mode 100644 index c42ddc6d7..000000000 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "overwrite": true, - "zone": [ - { - "name": "_acme-challenge", - "records": [ - { - "content": "aaa" - } - ], - "ttl": 14400, - "type": "TXT" - } - ] -} diff --git a/providers/dns/hostinger/internal/types.go b/providers/dns/hostinger/internal/types.go deleted file mode 100644 index c1a02ff8c..000000000 --- a/providers/dns/hostinger/internal/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package internal - -import ( - "fmt" - "strings" -) - -type APIError struct { - Message string `json:"message,omitempty"` - Errors map[string][]string `json:"errors,omitempty"` - CorrelationID string `json:"correlation_id,omitempty"` -} - -func (a *APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%s: %s", a.CorrelationID, a.Message) - - for field, values := range a.Errors { - _, _ = fmt.Fprintf(msg, ": %s: %s", field, strings.Join(values, ", ")) - } - - return msg.String() -} - -type ZoneRequest struct { - Overwrite bool `json:"overwrite"` - Zone []RecordSet `json:"zone,omitempty"` -} - -type RecordSet struct { - Name string `json:"name,omitempty"` - Records []Record `json:"records,omitempty"` - TTL int `json:"ttl,omitempty"` - Type string `json:"type,omitempty"` -} - -type Record struct { - Content string `json:"content,omitempty"` - IsDisabled bool `json:"is_disabled,omitempty"` -} - -type Filters struct { - Filters []Filter `json:"filters"` -} - -type Filter struct { - Name string `json:"name"` - Type string `json:"type"` -} 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..22d3be7bd 100644 --- a/providers/dns/hosttech/hosttech.go +++ b/providers/dns/hosttech/hosttech.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hosttech/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -85,11 +84,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hosttech: missing credentials") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.APIKey), - ), - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.APIKey)) return &DNSProvider{ config: config, @@ -164,7 +159,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("hosttech: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -174,9 +168,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/hosttech_test.go b/providers/dns/hosttech/hosttech_test.go index 042b73353..6f0d0bd3e 100644 --- a/providers/dns/hosttech/hosttech_test.go +++ b/providers/dns/hosttech/hosttech_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hosttech/internal/client.go b/providers/dns/hosttech/internal/client.go index 557d54298..399b18d0e 100644 --- a/providers/dns/hosttech/internal/client.go +++ b/providers/dns/hosttech/internal/client.go @@ -58,7 +58,6 @@ func (c *Client) GetZones(ctx context.Context, query string, limit, offset int) } result := apiResponse[[]Zone]{} - err = c.do(req, &result) if err != nil { return nil, err @@ -78,7 +77,6 @@ func (c *Client) GetZone(ctx context.Context, zoneID string) (*Zone, error) { } result := apiResponse[*Zone]{} - err = c.do(req, &result) if err != nil { return nil, err @@ -106,7 +104,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID, recordType string) ([]R } result := apiResponse[[]Record]{} - err = c.do(req, &result) if err != nil { return nil, err @@ -126,7 +123,6 @@ func (c *Client) AddRecord(ctx context.Context, zoneID string, record Record) (* } result := apiResponse[*Record]{} - err = c.do(req, &result) if err != nil { return nil, err @@ -206,7 +202,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{StatusCode: resp.StatusCode} - err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/hosttech/internal/types.go b/providers/dns/hosttech/internal/types.go index a4b5b564d..bf86964f7 100644 --- a/providers/dns/hosttech/internal/types.go +++ b/providers/dns/hosttech/internal/types.go @@ -2,7 +2,6 @@ package internal import ( "fmt" - "strings" ) type apiResponse[T any] struct { @@ -16,15 +15,11 @@ type APIError struct { } func (a APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%d: %s", a.StatusCode, a.Message) - + msg := fmt.Sprintf("%d: %s", a.StatusCode, a.Message) for k, v := range a.Errors { - _, _ = fmt.Fprintf(msg, " %s: %v", k, v) + msg += fmt.Sprintf(" %s: %v", k, v) } - - return msg.String() + return msg } type Zone struct { diff --git a/providers/dns/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index 4a88f1092..56bd92712 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -2,9 +2,12 @@ package httpnet import ( + "context" "errors" "fmt" "net/http" + "net/url" + "sync" "time" "github.com/go-acme/lego/v4/challenge" @@ -26,12 +29,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 +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 http.net. @@ -72,36 +84,143 @@ 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) + + 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) + } 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..a9bc527ad 100644 --- a/providers/dns/httpnet/httpnet_test.go +++ b/providers/dns/httpnet/httpnet_test.go @@ -49,7 +49,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -59,7 +58,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 +101,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) } @@ -115,7 +116,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -129,7 +129,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/httpreq/httpreq.go b/providers/dns/httpreq/httpreq.go index 591e9b5e1..8f8311e0a 100644 --- a/providers/dns/httpreq/httpreq.go +++ b/providers/dns/httpreq/httpreq.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -89,7 +88,6 @@ func NewDNSProvider() (*DNSProvider, error) { config.Username = env.GetOrFile(EnvUsername) config.Password = env.GetOrFile(EnvPassword) config.Endpoint = endpoint - return NewDNSProviderConfig(config) } @@ -103,8 +101,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("httpreq: the endpoint is missing") } - config.HTTPClient = clientdebug.Wrap(config.HTTPClient) - return &DNSProvider{config: config}, nil } @@ -129,7 +125,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } - return nil } @@ -143,7 +138,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } - return nil } @@ -162,7 +156,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } - return nil } @@ -176,13 +169,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } - return nil } func (d *DNSProvider) doPost(ctx context.Context, uri string, msg any) error { reqBody := new(bytes.Buffer) - err := json.NewEncoder(reqBody).Encode(msg) if err != nil { return fmt.Errorf("failed to create request JSON body: %w", err) diff --git a/providers/dns/httpreq/httpreq.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/httpreq/httpreq_test.go b/providers/dns/httpreq/httpreq_test.go index 108d6a565..038b21b1a 100644 --- a/providers/dns/httpreq/httpreq_test.go +++ b/providers/dns/httpreq/httpreq_test.go @@ -43,7 +43,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -228,7 +227,6 @@ func mockBuilder(mode string) *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Mode = mode @@ -240,7 +238,6 @@ func mockBuilderWithPathPrefix(mode, prefix string) *servermock.Builder[*DNSProv return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL + prefix) config.Mode = mode @@ -252,7 +249,6 @@ func mockBuilderWithBasicAuth(username, password string) *servermock.Builder[*DN return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Username = username config.Password = password @@ -267,6 +263,5 @@ func mustParse(rawURL string) *url.URL { if err != nil { panic(err) } - return uri } diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index e47f3e2b5..32f4d3446 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -2,7 +2,6 @@ package huaweicloud import ( - "context" "errors" "fmt" "strconv" @@ -10,7 +9,6 @@ import ( "sync" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -150,27 +148,19 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { d.recordIDs[token] = recordSetID d.recordIDsMu.Unlock() - err = wait.Retry(context.Background(), - func() error { - rs, errShow := d.client.ShowRecordSet(&hwmodel.ShowRecordSetRequest{ - ZoneId: zoneID, - RecordsetId: recordSetID, - }) - if errShow != nil { - return fmt.Errorf("show record set: %w", errShow) - } + err = wait.For("record set sync on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { + rs, errShow := d.client.ShowRecordSet(&hwmodel.ShowRecordSetRequest{ + ZoneId: zoneID, + RecordsetId: recordSetID, + }) + if errShow != nil { + return false, fmt.Errorf("show record set: %w", errShow) + } - if !strings.HasSuffix(ptr.Deref(rs.Status), "PENDING_") { - return nil - } - - return fmt.Errorf("status: %s", ptr.Deref(rs.Status)) - }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), - ) + return !strings.HasSuffix(ptr.Deref(rs.Status), "PENDING_"), nil + }) if err != nil { - return fmt.Errorf("huaweicloud: record set sync on %s: %w", domain, err) + return fmt.Errorf("huaweicloud: %w", err) } return nil @@ -184,7 +174,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("huaweicloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -209,10 +198,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/huaweicloud/huaweicloud_test.go b/providers/dns/huaweicloud/huaweicloud_test.go index 25e295da7..6787650ca 100644 --- a/providers/dns/huaweicloud/huaweicloud_test.go +++ b/providers/dns/huaweicloud/huaweicloud_test.go @@ -62,7 +62,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -141,7 +140,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -155,7 +153,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hurricane/hurricane.go b/providers/dns/hurricane/hurricane.go index b23528bb0..7ce646bc9 100644 --- a/providers/dns/hurricane/hurricane.go +++ b/providers/dns/hurricane/hurricane.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hurricane/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -58,7 +57,6 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Hurricane Electric. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() - values, err := env.Get(EnvTokens) if err != nil { return nil, fmt.Errorf("hurricane: %w", err) @@ -85,12 +83,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Credentials) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/hurricane/hurricane_test.go b/providers/dns/hurricane/hurricane_test.go index 2bbd638fa..f8a1f185c 100644 --- a/providers/dns/hurricane/hurricane_test.go +++ b/providers/dns/hurricane/hurricane_test.go @@ -55,7 +55,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,7 +120,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +133,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hyperone/hyperone.go b/providers/dns/hyperone/hyperone.go index 3cdad8e68..890f9f627 100644 --- a/providers/dns/hyperone/hyperone.go +++ b/providers/dns/hyperone/hyperone.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hyperone/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -77,7 +76,6 @@ func NewDNSProvider() (*DNSProvider, error) { func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.PassportLocation == "" { var err error - config.PassportLocation, err = GetDefaultPassportLocation() if err != nil { return nil, fmt.Errorf("hyperone: %w", err) @@ -98,8 +96,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } @@ -167,7 +163,6 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { if err != nil { return fmt.Errorf("hyperone: %w", err) } - if len(records) == 1 { if records[0].Content != info.Value { return fmt.Errorf("hyperone: record with content %s not found: fqdn=%s", info.Value, info.EffectiveFQDN) diff --git a/providers/dns/hyperone/hyperone.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/hyperone/hyperone_test.go b/providers/dns/hyperone/hyperone_test.go index 675a1fe19..1222d1c74 100644 --- a/providers/dns/hyperone/hyperone_test.go +++ b/providers/dns/hyperone/hyperone_test.go @@ -49,7 +49,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,7 +124,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,7 +137,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hyperone/internal/passport.go b/providers/dns/hyperone/internal/passport.go index d1503d893..b63236c3b 100644 --- a/providers/dns/hyperone/internal/passport.go +++ b/providers/dns/hyperone/internal/passport.go @@ -25,7 +25,6 @@ func LoadPassportFile(location string) (*Passport, error) { defer func() { _ = file.Close() }() var passport Passport - err = json.NewDecoder(file).Decode(&passport) if err != nil { return nil, fmt.Errorf("failed to parse passport file: %w", err) diff --git a/providers/dns/hyperone/internal/token_test.go b/providers/dns/hyperone/internal/token_test.go index 34b4cc573..315d0896f 100644 --- a/providers/dns/hyperone/internal/token_test.go +++ b/providers/dns/hyperone/internal/token_test.go @@ -38,7 +38,6 @@ func TestPayload_buildToken(t *testing.T) { require.NoError(t, err) var headerStruct Header - err = json.Unmarshal(headerString, &headerStruct) require.NoError(t, err) @@ -46,7 +45,6 @@ func TestPayload_buildToken(t *testing.T) { require.NoError(t, err) var payloadStruct Payload - err = json.Unmarshal(payloadString, &payloadStruct) require.NoError(t, err) diff --git a/providers/dns/ibmcloud/ibmcloud.toml b/providers/dns/ibmcloud/ibmcloud.toml index 01088f09b..090c010c9 100644 --- a/providers/dns/ibmcloud/ibmcloud.toml +++ b/providers/dns/ibmcloud/ibmcloud.toml @@ -7,12 +7,12 @@ 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] [Configuration.Credentials] - SOFTLAYER_USERNAME = "Username (IBM Cloud is {accountID}_{emailAddress})" + SOFTLAYER_USERNAME = "Username (IBM Cloud is _)" SOFTLAYER_API_KEY = "Classic Infrastructure API key" [Configuration.Additional] SOFTLAYER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" diff --git a/providers/dns/ibmcloud/ibmcloud_test.go b/providers/dns/ibmcloud/ibmcloud_test.go index 6ca7cd81b..a000e3e59 100644 --- a/providers/dns/ibmcloud/ibmcloud_test.go +++ b/providers/dns/ibmcloud/ibmcloud_test.go @@ -55,7 +55,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -128,7 +127,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,7 +140,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/iij/iij.go b/providers/dns/iij/iij.go index 1d098bde2..6bc7db21a 100644 --- a/providers/dns/iij/iij.go +++ b/providers/dns/iij/iij.go @@ -98,7 +98,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("iij: %w", err) } - return nil } @@ -111,7 +110,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("iij: %w", err) } - return nil } diff --git a/providers/dns/iij/iij.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/iij/iij_test.go b/providers/dns/iij/iij_test.go index bd8140532..2c7ec4217 100644 --- a/providers/dns/iij/iij_test.go +++ b/providers/dns/iij/iij_test.go @@ -71,7 +71,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -239,7 +238,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -253,7 +251,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/iijdpf/iijdpf_test.go b/providers/dns/iijdpf/iijdpf_test.go index fbcf3e1f5..a4fa8b8f6 100644 --- a/providers/dns/iijdpf/iijdpf_test.go +++ b/providers/dns/iijdpf/iijdpf_test.go @@ -43,7 +43,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -116,7 +115,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -130,7 +128,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/iijdpf/wrapper.go b/providers/dns/iijdpf/wrapper.go index 0ab26cdcd..12b09a30c 100644 --- a/providers/dns/iijdpf/wrapper.go +++ b/providers/dns/iijdpf/wrapper.go @@ -51,7 +51,6 @@ func (d *DNSProvider) deleteTxtRecord(ctx context.Context, zoneID, fqdn, rdata s // empty target rrset return nil } - return err } @@ -67,13 +66,11 @@ func (d *DNSProvider) deleteTxtRecord(ctx context.Context, zoneID, fqdn, rdata s // delete rdata rdataSlice := dpfzones.RecordRDATASlice{} - for _, v := range r.RData { if v.Value != rdata { rdataSlice = append(rdataSlice, v) } } - r.RData = rdataSlice _, _, err = dpfapiutils.SyncUpdate(ctx, d.client, r, nil) diff --git a/providers/dns/infoblox/infoblox.go b/providers/dns/infoblox/infoblox.go index 054f13679..37e119e85 100644 --- a/providers/dns/infoblox/infoblox.go +++ b/providers/dns/infoblox/infoblox.go @@ -198,7 +198,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordRefsMu.Lock() recordRef, ok := d.recordRefs[token] d.recordRefsMu.Unlock() - if !ok { return fmt.Errorf("infoblox: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/infoblox/infoblox.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/infoblox/infoblox_test.go b/providers/dns/infoblox/infoblox_test.go index 68158cb0d..45434e0e3 100644 --- a/providers/dns/infoblox/infoblox_test.go +++ b/providers/dns/infoblox/infoblox_test.go @@ -68,7 +68,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -150,7 +149,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -164,7 +162,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/infomaniak/infomaniak.go b/providers/dns/infomaniak/infomaniak.go index 9b8b53590..79c6f577e 100644 --- a/providers/dns/infomaniak/infomaniak.go +++ b/providers/dns/infomaniak/infomaniak.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/infomaniak/internal" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Infomaniak API reference: https://api.infomaniak.com/doc @@ -97,11 +96,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("infomaniak: missing access token") } - client, err := internal.New( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.AccessToken), - ), - config.APIEndpoint) + client, err := internal.New(internal.OAuthStaticAccessToken(config.HTTPClient, config.AccessToken), config.APIEndpoint) if err != nil { return nil, fmt.Errorf("infomaniak: %w", err) } 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/infomaniak/infomaniak_test.go b/providers/dns/infomaniak/infomaniak_test.go index 980f3b959..bc8fb7b58 100644 --- a/providers/dns/infomaniak/infomaniak_test.go +++ b/providers/dns/infomaniak/infomaniak_test.go @@ -39,7 +39,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -102,7 +101,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -116,7 +114,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/infomaniak/internal/client.go b/providers/dns/infomaniak/internal/client.go index 40b56c707..886a8966f 100644 --- a/providers/dns/infomaniak/internal/client.go +++ b/providers/dns/infomaniak/internal/client.go @@ -50,7 +50,6 @@ func (c *Client) CreateDNSRecord(ctx context.Context, domain *DNSDomain, record } result := APIResponse[string]{} - err = c.do(req, &result) if err != nil { return "", err @@ -113,7 +112,6 @@ func (c *Client) getDomainByName(ctx context.Context, name string) (*DNSDomain, } result := APIResponse[[]DNSDomain]{} - err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/internal/active24/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..32ecc2186 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" @@ -55,7 +55,6 @@ func (c *Client) GetServices(ctx context.Context) ([]Service, error) { } var result OldAPIResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -83,7 +82,6 @@ func (c *Client) GetRecords(ctx context.Context, service string, filter RecordFi } var result APIResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -182,7 +180,6 @@ 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) @@ -201,7 +198,6 @@ func (c *Client) sign(req *http.Request, now time.Time) error { canonicalRequest := fmt.Sprintf("%s %s %d", req.Method, req.URL.Path, now.Unix()) mac := hmac.New(sha1.New, []byte(c.secret)) - _, err := mac.Write([]byte(canonicalRequest)) if err != nil { return err diff --git a/providers/dns/internal/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/.gitattributes b/providers/dns/internal/clientdebug/.gitattributes deleted file mode 100644 index 0ce5804f7..000000000 --- a/providers/dns/internal/clientdebug/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/testdata/** text eol=lf diff --git a/providers/dns/internal/clientdebug/client.go b/providers/dns/internal/clientdebug/client.go deleted file mode 100644 index 342577b93..000000000 --- a/providers/dns/internal/clientdebug/client.go +++ /dev/null @@ -1,134 +0,0 @@ -package clientdebug - -import ( - "fmt" - "io" - "net/http" - "net/http/httputil" - "os" - "regexp" - "strconv" - "strings" - - "github.com/go-acme/lego/v4/platform/config/env" -) - -const replacement = "***" - -type Option func(*DumpTransport) - -func WithEnvKeys(keys ...string) Option { - return func(d *DumpTransport) { - for _, key := range keys { - v := strings.TrimSpace(env.GetOrFile(key)) - if v == "" { - continue - } - - d.replacements = append(d.replacements, v, replacement) - } - } -} - -func WithValues(values ...string) Option { - return func(d *DumpTransport) { - for _, value := range values { - d.replacements = append(d.replacements, value, replacement) - } - } -} - -func WithHeaders(keys ...string) Option { - return func(d *DumpTransport) { - d.regexps = append(d.regexps, - regexp.MustCompile(fmt.Sprintf(`(?im)^(%s):.+$`, strings.Join(keys, "|")))) - } -} - -type DumpTransport struct { - rt http.RoundTripper - - replacements []string - replacer *strings.Replacer - - regexps []*regexp.Regexp - - writer io.Writer -} - -func NewDumpTransport(rt http.RoundTripper, opts ...Option) *DumpTransport { - if rt == nil { - rt = http.DefaultTransport - } - - d := &DumpTransport{ - rt: rt, - writer: os.Stdout, - } - - for _, opt := range opts { - opt(d) - } - - d.regexps = append(d.regexps, - regexp.MustCompile(`(?im)^(Authorization):.+$`), - regexp.MustCompile(`(?im)^(Token|X-Token):.+$`), - regexp.MustCompile(`(?im)^(Auth-Token|X-Auth-Token):.+$`), - regexp.MustCompile(`(?im)^(Api-Key|X-Api-Key|X-Api-Secret):.+$`), - ) - - if len(d.replacements) > 0 { - d.replacer = strings.NewReplacer(d.replacements...) - } - - return d -} - -func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) { - data, _ := httputil.DumpRequestOut(h, true) - - _, _ = fmt.Fprintln(d.writer, "[HTTP Request]") - _, _ = fmt.Fprintln(d.writer, d.redact(data)) - - resp, err := d.rt.RoundTrip(h) - if err != nil { - return nil, err - } - - data, _ = httputil.DumpResponse(resp, true) - - _, _ = fmt.Fprintln(d.writer, "[HTTP Response]") - _, _ = fmt.Fprintln(d.writer, d.redact(data)) - - return resp, err -} - -func (d *DumpTransport) redact(content []byte) string { - data := string(content) - - for _, r := range d.regexps { - data = r.ReplaceAllString(data, "$1: "+replacement) - } - - if d.replacer == nil { - return data - } - - return d.replacer.Replace(data) -} - -// Wrap wraps an HTTP client Transport with the [DumpTransport]. -func Wrap(client *http.Client, opts ...Option) *http.Client { - val, found := os.LookupEnv("LEGO_DEBUG_DNS_API_HTTP_CLIENT") - if !found { - return client - } - - if ok, _ := strconv.ParseBool(val); !ok { - return client - } - - client.Transport = NewDumpTransport(client.Transport, opts...) - - return client -} diff --git a/providers/dns/internal/clientdebug/client_test.go b/providers/dns/internal/clientdebug/client_test.go deleted file mode 100644 index 3a0c4021a..000000000 --- a/providers/dns/internal/clientdebug/client_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package clientdebug - -import ( - "bytes" - "io" - "net/http" - "net/http/httptest" - "net/url" - "path/filepath" - "strings" - "testing" - "text/template" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestWrap_redact_env_vars(t *testing.T) { - t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") - - t.Setenv("MY_VAR_01", "env-aaaa-aaaa") - t.Setenv("MY_VAR_02", "query-aaaa-aaaa") - t.Setenv("MY_VAR_03", "path-aaaa-aaaa") - t.Setenv("MY_VAR_04", "request-body-aaaa-aaaa") - t.Setenv("MY_VAR_05", "request-header-aaaa-aaaa") - t.Setenv("MY_VAR_06", "response-body-aaaa-aaaa") - - buf := bytes.NewBufferString("") - - server, client, req := setupTest(t, buf, - WithEnvKeys("MY_VAR_01", "MY_VAR_02", "MY_VAR_03", "MY_VAR_04", "MY_VAR_05", "MY_VAR_06"), - ) - - now := time.Now() - - resp, err := client.Transport.RoundTrip(req) - require.NoError(t, err) - - assert.Equal(t, http.StatusOK, resp.StatusCode) - - assertDump(t, now, server, buf, "env_vars.txt") -} - -func TestWrap_redact_headers(t *testing.T) { - t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") - - buf := bytes.NewBufferString("") - - server, client, req := setupTest(t, buf, - WithHeaders("Secret-Request-Header", "Super-Secret-Request-Header", "Secret-Response-Header"), - ) - - now := time.Now() - - resp, err := client.Transport.RoundTrip(req) - require.NoError(t, err) - - assert.Equal(t, http.StatusOK, resp.StatusCode) - - assertDump(t, now, server, buf, "headers.txt") -} - -func TestWrap_redact_values(t *testing.T) { - t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") - - buf := bytes.NewBufferString("") - - server, client, req := setupTest(t, buf, - WithValues("query-aaaa-aaaa", "path-aaaa-aaaa", "request-body-aaaa-aaaa"), - ) - - now := time.Now() - - resp, err := client.Transport.RoundTrip(req) - require.NoError(t, err) - - assert.Equal(t, http.StatusOK, resp.StatusCode) - - assertDump(t, now, server, buf, "values.txt") -} - -func fakeRequest(t *testing.T, baseURL string) *http.Request { - t.Helper() - - endpoint, err := url.Parse(baseURL) - require.NoError(t, err) - - query := endpoint.Query() - query.Set("foo", "query-aaaa-aaaa") - endpoint.RawQuery = query.Encode() - - endpoint = endpoint.JoinPath("path-aaaa-aaaa") - - body := `{ - "foo": "request-body-aaaa-aaaa" -} -` - - req := httptest.NewRequest(http.MethodGet, endpoint.String(), bytes.NewBufferString(body)) - - req.Header.Set("X-Authorization", "not-redacted") - - req.Header.Set("Secret-Request-Header", "request-header-aaaa-aaaa") - req.Header.Set("Super-Secret-Request-Header", "env-aaaa-aaaa") - - req.Header.Set("Authorization", "header-aaaa-0000") - req.Header.Set("Token", "header-aaaa-0001") - req.Header.Set("X-Token", "header-aaaa-0002") - req.Header.Set("Auth-Token", "header-aaaa-0003") - req.Header.Set("X-Auth-Token", "header-aaaa-0004") - req.Header.Set("Api-Key", "header-aaaa-0006") - req.Header.Set("X-Api-Key", "header-aaaa-0007") - req.Header.Set("X-Api-Secret", "header-aaaa-0008") - - req.SetBasicAuth("user", "secret") - - return req -} - -func fakeResponse() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Secret-Response-Header", "response-header-aaaa-aaaa") - _, _ = w.Write([]byte(`{ - "bar": "response-body-aaaa-aaaa" -}`, - )) - } -} - -func withWriter(w io.Writer) Option { - return func(d *DumpTransport) { - if w != nil { - d.writer = w - } - } -} - -func setupTest(t *testing.T, buf io.Writer, opts ...Option) (*httptest.Server, *http.Client, *http.Request) { - t.Helper() - - server := httptest.NewServer(fakeResponse()) - - opts = append(opts, withWriter(buf)) - - client := Wrap(server.Client(), opts...) - - req := fakeRequest(t, server.URL) - - return server, client, req -} - -func assertDump(t *testing.T, now time.Time, server *httptest.Server, actual *bytes.Buffer, filename string) { - t.Helper() - - tmpl, err := template.New(filename).ParseFiles(filepath.Join("testdata", filename)) - require.NoError(t, err) - - expected := bytes.NewBufferString("") - - location, err := time.LoadLocation("GMT") - require.NoError(t, err) - - baseURL, err := url.Parse(server.URL) - require.NoError(t, err) - - err = tmpl.Execute(expected, map[string]string{ - "Host": baseURL.Host, - "Date": now.In(location).Format(time.RFC1123), - }) - require.NoError(t, err) - - assert.Equal(t, expected.String(), strings.ReplaceAll(actual.String(), "\r", "")) -} diff --git a/providers/dns/internal/clientdebug/testdata/env_vars.txt b/providers/dns/internal/clientdebug/testdata/env_vars.txt deleted file mode 100644 index a2697850e..000000000 --- a/providers/dns/internal/clientdebug/testdata/env_vars.txt +++ /dev/null @@ -1,32 +0,0 @@ -[HTTP Request] -GET /***?foo=*** HTTP/1.1 -Host: {{ .Host }} -User-Agent: Go-http-client/1.1 -Content-Length: 37 -Api-Key: *** -Auth-Token: *** -Authorization: *** -Secret-Request-Header: *** -Super-Secret-Request-Header: *** -Token: *** -X-Api-Key: *** -X-Api-Secret: *** -X-Auth-Token: *** -X-Authorization: not-redacted -X-Token: *** -Accept-Encoding: gzip - -{ - "foo": "***" -} - -[HTTP Response] -HTTP/1.1 200 OK -Content-Length: 37 -Content-Type: text/plain; charset=utf-8 -Date: {{ .Date }} -Secret-Response-Header: response-header-aaaa-aaaa - -{ - "bar": "***" -} diff --git a/providers/dns/internal/clientdebug/testdata/headers.txt b/providers/dns/internal/clientdebug/testdata/headers.txt deleted file mode 100644 index fe803fb22..000000000 --- a/providers/dns/internal/clientdebug/testdata/headers.txt +++ /dev/null @@ -1,32 +0,0 @@ -[HTTP Request] -GET /path-aaaa-aaaa?foo=query-aaaa-aaaa HTTP/1.1 -Host: {{ .Host }} -User-Agent: Go-http-client/1.1 -Content-Length: 37 -Api-Key: *** -Auth-Token: *** -Authorization: *** -Secret-Request-Header: *** -Super-Secret-Request-Header: *** -Token: *** -X-Api-Key: *** -X-Api-Secret: *** -X-Auth-Token: *** -X-Authorization: not-redacted -X-Token: *** -Accept-Encoding: gzip - -{ - "foo": "request-body-aaaa-aaaa" -} - -[HTTP Response] -HTTP/1.1 200 OK -Content-Length: 37 -Content-Type: text/plain; charset=utf-8 -Date: {{ .Date }} -Secret-Response-Header: *** - -{ - "bar": "response-body-aaaa-aaaa" -} diff --git a/providers/dns/internal/clientdebug/testdata/values.txt b/providers/dns/internal/clientdebug/testdata/values.txt deleted file mode 100644 index b40f51f14..000000000 --- a/providers/dns/internal/clientdebug/testdata/values.txt +++ /dev/null @@ -1,32 +0,0 @@ -[HTTP Request] -GET /***?foo=*** HTTP/1.1 -Host: {{ .Host }} -User-Agent: Go-http-client/1.1 -Content-Length: 37 -Api-Key: *** -Auth-Token: *** -Authorization: *** -Secret-Request-Header: request-header-aaaa-aaaa -Super-Secret-Request-Header: env-aaaa-aaaa -Token: *** -X-Api-Key: *** -X-Api-Secret: *** -X-Auth-Token: *** -X-Authorization: not-redacted -X-Token: *** -Accept-Encoding: gzip - -{ - "foo": "***" -} - -[HTTP Response] -HTTP/1.1 200 OK -Content-Length: 37 -Content-Type: text/plain; charset=utf-8 -Date: {{ .Date }} -Secret-Response-Header: response-header-aaaa-aaaa - -{ - "bar": "response-body-aaaa-aaaa" -} diff --git a/providers/dns/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 83% rename from providers/dns/internal/hostingde/internal/client.go rename to providers/dns/internal/hostingde/client.go index 133c3479c..869d93d3e 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" @@ -10,11 +10,14 @@ import ( "net/url" "time" - "github.com/cenkalti/backoff/v5" + "github.com/cenkalti/backoff/v4" "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, @@ -37,25 +40,35 @@ func NewClient(apiKey string) *Client { // GetZone gets a zone. func (c *Client) GetZone(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneConfig, error) { - operation := func() (*ZoneConfig, error) { + var zoneConfig *ZoneConfig + + operation := func() error { response, err := c.ListZoneConfigs(ctx, req) if err != nil { - return nil, backoff.Permanent(err) + return backoff.Permanent(err) } if response.Data[0].Status != "active" { - return nil, fmt.Errorf("unexpected status: %q", response.Data[0].Status) + return fmt.Errorf("unexpected status: %q", response.Data[0].Status) } - return &response.Data[0], nil + zoneConfig = &response.Data[0] + + return nil } bo := backoff.NewExponentialBackOff() bo.InitialInterval = 3 * time.Second bo.MaxInterval = 10 * bo.InitialInterval + bo.MaxElapsedTime = 100 * bo.InitialInterval // retry in case the zone was edited recently and is not yet active - return backoff.Retry(ctx, operation, backoff.WithBackOff(bo), backoff.WithMaxElapsedTime(100*bo.InitialInterval)) + err := backoff.Retry(operation, bo) + if err != nil { + return nil, err + } + + return zoneConfig, nil } // ListZoneConfigs lists zone configuration. 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 98% rename from providers/dns/internal/hostingde/internal/types.go rename to providers/dns/internal/hostingde/types.go index 330eab27d..4f3347190 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" @@ -88,8 +88,7 @@ type Zone struct { // https://www.hosting.de/api/?json#updating-zones type ZoneUpdateRequest struct { BaseRequest - ZoneConfig `json:"zoneConfig"` - + ZoneConfig `json:"zoneConfig"` RecordsToAdd []DNSRecord `json:"recordsToAdd"` RecordsToDelete []DNSRecord `json:"recordsToDelete"` } @@ -98,7 +97,6 @@ type ZoneUpdateRequest struct { // https://www.hosting.de/api/?json#list-zoneconfigs type ZoneConfigsFindRequest struct { BaseRequest - Filter Filter `json:"filter"` Limit int `json:"limit"` Page int `json:"page"` diff --git a/providers/dns/internal/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..06292453a 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}, } } @@ -78,17 +82,14 @@ func (c *Client) DoActions(ctx context.Context, actions ...ActionParameter) (*DN if err != nil { return nil, err } - return resp, nil } multi := c.toMultiParameters(actions) - err := c.do(ctx, multi, resp) if err != nil { return nil, err } - return resp, nil } @@ -159,7 +160,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := APIError{} - err := xml.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/internal/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..1e1e4a215 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, @@ -48,13 +52,12 @@ func (c *Client) GetDomainByName(ctx context.Context, domainName string) (*Domai } domain := &Domain{} - statusCode, err := c.do(req, domain) if err != nil { if statusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1 { // 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 @@ -71,7 +74,6 @@ func (c *Client) AddRecord(ctx context.Context, domainID int, body Record) (*Rec } record := &Record{} - _, err = c.do(req, record) if err != nil { return nil, err @@ -88,7 +90,6 @@ func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error } var records []Record - _, err = c.do(req, &records) if err != nil { return nil, err @@ -107,7 +108,6 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error } _, err = c.do(req, nil) - return err } @@ -170,7 +170,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} - err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/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..f380c05b0 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.32.0" + ourUserAgent = "goacme-lego/4.25.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. 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/internal/client.go b/providers/dns/internetbs/internal/client.go index cf9e90dc5..4de57dc9a 100644 --- a/providers/dns/internetbs/internal/client.go +++ b/providers/dns/internetbs/internal/client.go @@ -48,7 +48,6 @@ func NewClient(apiKey, password string) *Client { // AddRecord The command is intended to add a new DNS record to a specific zone (domain). func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error { var r APIResponse - err := c.doRequest(ctx, "Add", query, &r) if err != nil { return err @@ -64,7 +63,6 @@ func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error { // RemoveRecord The command is intended to remove a DNS record from a specific zone. func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error { var r APIResponse - err := c.doRequest(ctx, "Remove", query, &r) if err != nil { return err @@ -80,7 +78,6 @@ func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error { // ListRecords The command is intended to retrieve the list of DNS records for a specific domain. func (c *Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Record, error) { var l ListResponse - err := c.doRequest(ctx, "List", query, &l) if err != nil { return nil, err diff --git a/providers/dns/internetbs/internetbs.go b/providers/dns/internetbs/internetbs.go index e8cb868d2..9d6c17676 100644 --- a/providers/dns/internetbs/internetbs.go +++ b/providers/dns/internetbs/internetbs.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internetbs/internal" ) @@ -89,8 +88,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/internetbs/internetbs_test.go b/providers/dns/internetbs/internetbs_test.go index be436d6e7..ea328d506 100644 --- a/providers/dns/internetbs/internetbs_test.go +++ b/providers/dns/internetbs/internetbs_test.go @@ -49,7 +49,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,7 +121,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -136,7 +134,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 0e79d71e0..9945d904c 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -177,19 +177,17 @@ 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 { continue } recordID = record.ID - 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/inwx/inwx_test.go b/providers/dns/inwx/inwx_test.go index 47b12e228..39ce7d70e 100644 --- a/providers/dns/inwx/inwx_test.go +++ b/providers/dns/inwx/inwx_test.go @@ -62,7 +62,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,7 +124,6 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() - envTest.Apply(map[string]string{ EnvSandbox: "true", EnvTTL: "3600", // In sandbox mode, the minimum allowed TTL is 3600 diff --git a/providers/dns/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..b51e003f7 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. @@ -51,7 +52,6 @@ func (c *Client) ListZones(ctx context.Context) ([]Zone, error) { } var zones []Zone - err = c.do(req, &zones) if err != nil { return nil, fmt.Errorf("failed to call API: %w", err) @@ -96,7 +96,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, filter *RecordsF } var zone CustomerZone - err = c.do(req, &zone) if err != nil { return nil, fmt.Errorf("failed to call API: %w", err) @@ -181,7 +180,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errClient := &ClientError{StatusCode: resp.StatusCode} - err := json.Unmarshal(raw, &errClient.errors) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/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 91% rename from providers/dns/internal/ionos/internal/types.go rename to providers/dns/ionos/internal/types.go index 35bfe0966..3b7acbec2 100644 --- a/providers/dns/internal/ionos/internal/types.go +++ b/providers/dns/ionos/internal/types.go @@ -3,7 +3,6 @@ package internal import ( "fmt" "strconv" - "strings" ) // ClientError a detailed error. @@ -14,23 +13,21 @@ type ClientError struct { } func (f ClientError) Error() string { - var msg strings.Builder - - msg.WriteString(strconv.Itoa(f.StatusCode) + ": ") + msg := strconv.Itoa(f.StatusCode) + ": " if f.message != "" { - msg.WriteString(f.message + ": ") + msg += f.message + ": " } for i, e := range f.errors { if i != 0 { - msg.WriteString(", ") + msg += ", " } - msg.WriteString(e.Error()) + msg += e.Error() } - return msg.String() + return msg } func (f ClientError) Unwrap() error { diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index 892370f5d..394def027 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -2,15 +2,18 @@ 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/ionos/internal" ) // Environment variables names. @@ -30,12 +33,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 +55,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 +79,126 @@ 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 + } + + 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..5aef6ad14 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 { @@ -35,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -45,7 +46,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 +91,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) } @@ -103,7 +106,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -117,7 +119,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/ipv64/internal/client.go index 0dfd94374..1cb9c532f 100644 --- a/providers/dns/ipv64/internal/client.go +++ b/providers/dns/ipv64/internal/client.go @@ -131,7 +131,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} - err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/ipv64/internal/types.go b/providers/dns/ipv64/internal/types.go index 6ef31a3cc..e9e357ecc 100644 --- a/providers/dns/ipv64/internal/types.go +++ b/providers/dns/ipv64/internal/types.go @@ -11,7 +11,6 @@ type APIResponse struct { type APIError struct { APIResponse - AddRecordMessage string `json:"add_record"` DelRecordMessage string `json:"del_record"` AddDomainMessage string `json:"add_domain"` @@ -42,7 +41,6 @@ func (a APIError) Error() string { type Domains struct { APIResponse - APICall string `json:"add_domain"` Subdomains map[string]Subdomain `json:"subdomains"` } diff --git a/providers/dns/ipv64/ipv64.go b/providers/dns/ipv64/ipv64.go index 078fe5ca1..6e8d1c5bb 100644 --- a/providers/dns/ipv64/ipv64.go +++ b/providers/dns/ipv64/ipv64.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/ipv64/internal" "github.com/miekg/dns" ) @@ -86,8 +85,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/ipv64/ipv64.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/ipv64/ipv64_test.go b/providers/dns/ipv64/ipv64_test.go index 6dc7d1cfc..b3fe142e9 100644 --- a/providers/dns/ipv64/ipv64_test.go +++ b/providers/dns/ipv64/ipv64_test.go @@ -114,7 +114,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -172,7 +171,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -186,7 +184,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/iwantmyname/internal/client.go new file mode 100644 index 000000000..c3418c854 --- /dev/null +++ b/providers/dns/iwantmyname/internal/client.go @@ -0,0 +1,66 @@ +package internal + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + querystring "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://iwantmyname.com/basicauth/ddns" + +// Client iwantmyname client. +type Client struct { + username string + password string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(username, password string) *Client { + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + username: username, + password: password, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +// SendRequest send a request (create/add/delete) to the API. +func (c *Client) SendRequest(ctx context.Context, record Record) error { + values, err := querystring.Values(record) + if err != nil { + return err + } + + endpoint := c.baseURL + endpoint.RawQuery = values.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), http.NoBody) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + req.SetBasicAuth(c.username, c.password) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + return nil +} diff --git a/providers/dns/iwantmyname/internal/client_test.go b/providers/dns/iwantmyname/internal/client_test.go new file mode 100644 index 000000000..c25eb56ef --- /dev/null +++ b/providers/dns/iwantmyname/internal/client_test.go @@ -0,0 +1,46 @@ +package internal + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func setupClient(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client, nil +} + +func TestClient_Do(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"), + ). + Route("POST /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + fmt.Println(req) + }), + servermock.CheckQueryParameter().Strict(). + With("hostname", "example.com"). + With("ttl", "120"). + With("type", "TXT"). + With("value", "data")). + Build(t) + + record := Record{ + Hostname: "example.com", + Type: "TXT", + Value: "data", + TTL: 120, + } + + err := client.SendRequest(t.Context(), record) + require.NoError(t, err) +} diff --git a/providers/dns/iwantmyname/internal/types.go b/providers/dns/iwantmyname/internal/types.go new file mode 100644 index 000000000..b259235f5 --- /dev/null +++ b/providers/dns/iwantmyname/internal/types.go @@ -0,0 +1,9 @@ +package internal + +// Record represents a record. +type Record struct { + Hostname string `url:"hostname,omitempty"` + Type string `url:"type,omitempty"` + Value string `url:"value,omitempty"` + TTL int `url:"ttl,omitempty"` +} diff --git a/providers/dns/iwantmyname/iwantmyname.go b/providers/dns/iwantmyname/iwantmyname.go index f53287e69..2b53377ed 100644 --- a/providers/dns/iwantmyname/iwantmyname.go +++ b/providers/dns/iwantmyname/iwantmyname.go @@ -2,13 +2,16 @@ package iwantmyname import ( + "context" "errors" "fmt" "net/http" "time" "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/iwantmyname/internal" ) // Environment variables names. @@ -38,12 +41,20 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { - return &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 iwantmyname. @@ -63,7 +74,24 @@ func NewDNSProvider() (*DNSProvider, error) { // NewDNSProviderConfig return a DNSProvider instance configured for iwantmyname. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - return nil, errors.New("iwantmyname: the iwantmyname API has shut down https://github.com/go-acme/lego/issues/2563") + if config == nil { + return nil, errors.New("iwantmyname: the configuration of the DNS provider is nil") + } + + if config.Username == "" || config.Password == "" { + return nil, errors.New("iwantmyname: credentials missing") + } + + client := internal.NewClient(config.Username, config.Password) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -74,10 +102,38 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + record := internal.Record{ + Hostname: dns01.UnFqdn(info.EffectiveFQDN), + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + } + + err := d.client.SendRequest(context.Background(), record) + if err != nil { + return fmt.Errorf("iwantmyname: %w", err) + } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + record := internal.Record{ + Hostname: dns01.UnFqdn(info.EffectiveFQDN), + Type: "TXT", + Value: "delete", + TTL: d.config.TTL, + } + + err := d.client.SendRequest(context.Background(), record) + if err != nil { + return fmt.Errorf("iwantmyname: %w", err) + } + return nil } diff --git a/providers/dns/iwantmyname/iwantmyname.toml b/providers/dns/iwantmyname/iwantmyname.toml index a82c2b749..00a45b714 100644 --- a/providers/dns/iwantmyname/iwantmyname.toml +++ b/providers/dns/iwantmyname/iwantmyname.toml @@ -1,9 +1,5 @@ -Name = "iwantmyname (Deprecated)" -Description = ''' -The iwantmyname API has shut down. - -https://github.com/go-acme/lego/issues/2563 -''' +Name = "iwantmyname" +Description = '''''' URL = "https://iwantmyname.com" Code = "iwantmyname" Since = "v4.7.0" @@ -11,7 +7,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/com35/com35_test.go b/providers/dns/iwantmyname/iwantmyname_test.go similarity index 77% rename from providers/dns/com35/com35_test.go rename to providers/dns/iwantmyname/iwantmyname_test.go index 78fd8f829..7ae4545b2 100644 --- a/providers/dns/com35/com35_test.go +++ b/providers/dns/iwantmyname/iwantmyname_test.go @@ -1,4 +1,4 @@ -package com35 +package iwantmyname import ( "testing" @@ -9,7 +9,8 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvUsername, EnvPassword).WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvUsername, EnvPassword). + WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -24,33 +25,30 @@ func TestNewDNSProvider(t *testing.T) { EnvPassword: "secret", }, }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_USERNAME,IWANTMYNAME_PASSWORD", + }, { desc: "missing username", envVars: map[string]string{ - EnvUsername: "", EnvPassword: "secret", }, - expected: "35com: some credentials information are missing: COM35_USERNAME", + expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_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", + expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_PASSWORD", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -60,7 +58,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) } @@ -80,19 +79,19 @@ func TestNewDNSProviderConfig(t *testing.T) { username: "user", password: "secret", }, + { + desc: "missing credentials", + expected: "iwantmyname: credentials missing", + }, { desc: "missing username", password: "secret", - expected: "35com: credentials missing", + expected: "iwantmyname: credentials missing", }, { desc: "missing password", username: "user", - expected: "35com: credentials missing", - }, - { - desc: "missing credentials", - expected: "35com: credentials missing", + expected: "iwantmyname: credentials missing", }, } @@ -107,7 +106,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) } @@ -121,7 +121,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +134,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/dmapi/client.go b/providers/dns/joker/internal/dmapi/client.go index 576410723..6496abe2e 100644 --- a/providers/dns/joker/internal/dmapi/client.go +++ b/providers/dns/joker/internal/dmapi/client.go @@ -176,15 +176,12 @@ func RemoveTxtEntryFromZone(zone, relative string) (string, bool) { prefix := fmt.Sprintf("%s TXT 0 ", relative) modified := false - var zoneEntries []string - for line := range strings.Lines(zone) { if strings.HasPrefix(line, prefix) { modified = true continue } - zoneEntries = append(zoneEntries, line) } diff --git a/providers/dns/joker/internal/dmapi/identity.go b/providers/dns/joker/internal/dmapi/identity.go index 63c0b2ea1..351d987e9 100644 --- a/providers/dns/joker/internal/dmapi/identity.go +++ b/providers/dns/joker/internal/dmapi/identity.go @@ -24,7 +24,6 @@ type Token struct { // login performs a log in to Joker's DMAPI. func (c *Client) login(ctx context.Context) (*Response, error) { var values url.Values - switch { case c.username != "" && c.password != "": values = url.Values{ @@ -107,6 +106,5 @@ func formatResponseError(response *Response, err error) error { if response != nil { return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) } - return fmt.Errorf("joker: DMAPI error: %w", err) } diff --git a/providers/dns/joker/joker.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/joker/joker_test.go b/providers/dns/joker/joker_test.go index bc21ccbbc..20e3fc7a5 100644 --- a/providers/dns/joker/joker_test.go +++ b/providers/dns/joker/joker_test.go @@ -53,7 +53,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -113,7 +112,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -127,7 +125,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/joker/provider_dmapi.go b/providers/dns/joker/provider_dmapi.go index 11f850136..5c623467a 100644 --- a/providers/dns/joker/provider_dmapi.go +++ b/providers/dns/joker/provider_dmapi.go @@ -10,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/joker/internal/dmapi" ) @@ -28,7 +27,6 @@ func newDmapiProvider() (*dmapiProvider, error) { values, err := env.Get(EnvAPIKey) if err != nil { var errU error - values, errU = env.Get(EnvUsername, EnvPassword) if errU != nil { //nolint:errorlint // false-positive @@ -68,8 +66,6 @@ func newDmapiProviderConfig(config *Config) (*dmapiProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &dmapiProvider{config: config, client: client}, nil } @@ -162,7 +158,6 @@ func (d *dmapiProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return formatResponseError(response, err) } - return nil } @@ -171,6 +166,5 @@ func formatResponseError(response *dmapi.Response, err error) error { if response != nil { return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) } - return fmt.Errorf("joker: DMAPI error: %w", err) } diff --git a/providers/dns/joker/provider_dmapi_test.go b/providers/dns/joker/provider_dmapi_test.go index 06f283872..4704f2b80 100644 --- a/providers/dns/joker/provider_dmapi_test.go +++ b/providers/dns/joker/provider_dmapi_test.go @@ -58,7 +58,6 @@ func Test_newDmapiProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/joker/provider_svc.go b/providers/dns/joker/provider_svc.go index f4d8fcf3f..991772fe7 100644 --- a/providers/dns/joker/provider_svc.go +++ b/providers/dns/joker/provider_svc.go @@ -9,7 +9,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/joker/internal/svc" ) @@ -48,8 +47,6 @@ func newSvcProviderConfig(config *Config) (*svcProvider, error) { client := svc.NewClient(config.Username, config.Password) - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &svcProvider{config: config, client: client}, nil } diff --git a/providers/dns/joker/provider_svc_test.go b/providers/dns/joker/provider_svc_test.go index dc981b6b4..ad6c74c87 100644 --- a/providers/dns/joker/provider_svc_test.go +++ b/providers/dns/joker/provider_svc_test.go @@ -49,7 +49,6 @@ func Test_newSvcProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/keyhelp/internal/client.go b/providers/dns/keyhelp/internal/client.go deleted file mode 100644 index a5a80db5c..000000000 --- a/providers/dns/keyhelp/internal/client.go +++ /dev/null @@ -1,175 +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" -) - -// APIKeyHeader API key header. -const APIKeyHeader = "X-Api-Key" - -// Client the KeyHelp API client. -type Client struct { - apiKey string - - baseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(baseURL, apiKey string) (*Client, error) { - if baseURL == "" { - return nil, errors.New("missing base URL") - } - - if apiKey == "" { - return nil, errors.New("credentials missing") - } - - base, err := url.Parse(baseURL) - if err != nil { - return nil, fmt.Errorf("parse base URL: %w", err) - } - - return &Client{ - apiKey: apiKey, - baseURL: base.JoinPath("api", "v2"), - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) do(req *http.Request, result any) error { - req.Header.Set(APIKeyHeader, c.apiKey) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { - endpoint := c.baseURL.JoinPath("domains") - - query := endpoint.Query() - query.Set("sort", "domain_utf8") - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result []Domain - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) ListDomainRecords(ctx context.Context, domainID int) (*DomainRecords, error) { - endpoint := c.baseURL.JoinPath("dns", strconv.Itoa(domainID)) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - var result DomainRecords - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return &result, nil -} - -func (c *Client) UpdateDomainRecords(ctx context.Context, domainID int, records DomainRecords) (*DomainID, error) { - endpoint := c.baseURL.JoinPath("dns", strconv.Itoa(domainID)) - - req, err := newJSONRequest(ctx, http.MethodPut, endpoint, records) - if err != nil { - return nil, err - } - - var result DomainID - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return &result, nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/keyhelp/internal/client_test.go b/providers/dns/keyhelp/internal/client_test.go deleted file mode 100644 index 80b21495b..000000000 --- a/providers/dns/keyhelp/internal/client_test.go +++ /dev/null @@ -1,169 +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, "secret") - if err != nil { - return nil, err - } - - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - With(APIKeyHeader, "secret"). - WithJSONHeaders(), - ) -} - -func TestClient_ListDomains(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v2/domains", - servermock.ResponseFromFixture("get_domains.json"), - servermock.CheckQueryParameter(). - With("sort", "domain_utf8"). - Strict()). - Build(t) - - domains, err := client.ListDomains(t.Context()) - require.NoError(t, err) - - expected := []Domain{{ - ID: 8, - UserID: 4, - ParentDomainID: 0, - Status: 1, - Domain: "example.com", - DomainUTF8: "example.com", - IsEmailDomain: true, - }} - - assert.Equal(t, expected, domains) -} - -func TestClient_ListDomains_error(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v2/domains", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.ListDomains(t.Context()) - - require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") -} - -func TestClient_ListDomainRecords(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v2/dns/123", - servermock.ResponseFromFixture("get_domain_records.json")). - Build(t) - - domainRecords, err := client.ListDomainRecords(t.Context(), 123) - require.NoError(t, err) - - expected := &DomainRecords{ - DkimRecord: `default._domainkey IN TXT ( "v=DKIM1; k=rsa; s=email; " "...DKIM KEY..." )`, - Records: &Records{ - Soa: &SOARecord{ - TTL: 86400, - PrimaryNs: "ns.example.com.", - RName: "root.example.com.", - Refresh: 14400, - Retry: 1800, - Expire: 604800, - Minimum: 3600, - }, - Other: []Record{{ - Host: "@", - TTL: 86400, - Type: "A", - Value: "192.168.178.1", - }}, - }, - } - - assert.Equal(t, expected, domainRecords) -} - -func TestClient_ListDomainRecords_error(t *testing.T) { - client := mockBuilder(). - Route("GET /api/v2/dns/8", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - _, err := client.ListDomainRecords(t.Context(), 8) - - require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") -} - -func TestClient_UpdateDomainRecords(t *testing.T) { - client := mockBuilder(). - Route("PUT /api/v2/dns/8", - servermock.ResponseFromFixture("update_domain_records.json"), - servermock.CheckRequestJSONBodyFromFixture("update_domain_records-request.json")). - Build(t) - - records := DomainRecords{ - DkimRecord: `default._domainkey IN TXT ( "v=DKIM1; k=rsa; s=email; " "...DKIM KEY..." )`, - Records: &Records{ - Soa: &SOARecord{ - TTL: 86400, - PrimaryNs: "ns.example.com.", - RName: "root.example.com.", - Refresh: 14400, - Retry: 1800, - Expire: 604800, - Minimum: 3600, - }, - Other: []Record{ - { - Host: "@", - TTL: 86400, - Type: "A", - Value: "192.168.178.1", - }, - { - Host: "_acme-challenge", - TTL: 120, - Type: "TXT", - Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - }, - }, - }, - } - - domainID, err := client.UpdateDomainRecords(t.Context(), 8, records) - require.NoError(t, err) - - expected := &DomainID{ID: 8} - - assert.Equal(t, expected, domainID) -} - -func TestClient_UpdateDomainRecords_error(t *testing.T) { - client := mockBuilder(). - Route("PUT /api/v2/dns/123", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusUnauthorized)). - Build(t) - - records := DomainRecords{} - - _, err := client.UpdateDomainRecords(t.Context(), 123, records) - - require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") -} diff --git a/providers/dns/keyhelp/internal/fixtures/error.json b/providers/dns/keyhelp/internal/fixtures/error.json deleted file mode 100644 index 4fdf5e8f5..000000000 --- a/providers/dns/keyhelp/internal/fixtures/error.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "code": "401 Unauthorized", - "message": "API key is missing or invalid." -} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domain_records.json b/providers/dns/keyhelp/internal/fixtures/get_domain_records.json deleted file mode 100644 index 50483bb8e..000000000 --- a/providers/dns/keyhelp/internal/fixtures/get_domain_records.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "is_custom_dns": false, - "is_dns_disabled": false, - "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", - "records": { - "soa": { - "ttl": 86400, - "primary_ns": "ns.example.com.", - "rname": "root.example.com.", - "refresh": 14400, - "retry": 1800, - "expire": 604800, - "minimum": 3600 - }, - "other": [ - { - "host": "@", - "ttl": 86400, - "type": "A", - "value": "192.168.178.1" - } - ] - } -} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json b/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json deleted file mode 100644 index cd49fd6d0..000000000 --- a/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "is_custom_dns": false, - "is_dns_disabled": false, - "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", - "records": { - "soa": { - "ttl": 86400, - "primary_ns": "ns.example.com.", - "rname": "root.example.com.", - "refresh": 14400, - "retry": 1800, - "expire": 604800, - "minimum": 3600 - }, - "other": [ - { - "host": "@", - "ttl": 86400, - "type": "A", - "value": "192.168.178.1" - }, - { - "host": "_acme-challenge", - "ttl": 120, - "type": "TXT", - "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ] - } -} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domains.json b/providers/dns/keyhelp/internal/fixtures/get_domains.json deleted file mode 100644 index 28ae0887d..000000000 --- a/providers/dns/keyhelp/internal/fixtures/get_domains.json +++ /dev/null @@ -1,41 +0,0 @@ -[ - { - "id": 8, - "id_user": 4, - "id_parent_domain": 0, - "status": 1, - "domain": "example.com", - "domain_utf8": "example.com", - "created_at": "2019-08-15T11:29:13+02:00", - "php_version": "", - "traffic": 32434624, - "is_disabled": false, - "delete_on": "2025-09-02T19:31:14+0000", - "dkim_selector": "default", - "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", - "is_custom_dns": false, - "is_dns_disabled": false, - "is_subdomain": false, - "is_system_domain": false, - "is_email_domain": true, - "is_email_sending_only": false, - "target": { - "target": "https://www.keyhelp.de", - "is_forwarding": true, - "forwarding_type": 301 - }, - "security": { - "id_certificate": 0, - "lets_encrypt": true, - "is_prefer_https": true, - "is_hsts": true, - "hsts_max_age": 10368000, - "hsts_include": true, - "hsts_preload": true - }, - "apache": { - "http_directives": "# My custom HTTP directives", - "https_directives": "# My custom HTTPS directives" - } - } -] diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json deleted file mode 100644 index 6f83ead11..000000000 --- a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", - "records": { - "soa": { - "ttl": 86400, - "primary_ns": "ns.example.com.", - "rname": "root.example.com.", - "refresh": 14400, - "retry": 1800, - "expire": 604800, - "minimum": 3600 - }, - "other": [ - { - "host": "@", - "ttl": 86400, - "type": "A", - "value": "192.168.178.1" - }, - { - "host": "_acme-challenge", - "ttl": 120, - "type": "TXT", - "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ] - } -} diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json deleted file mode 100644 index 3ebb2ee7a..000000000 --- a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", - "records": { - "soa": { - "ttl": 86400, - "primary_ns": "ns.example.com.", - "rname": "root.example.com.", - "refresh": 14400, - "retry": 1800, - "expire": 604800, - "minimum": 3600 - }, - "other": [ - { - "host": "@", - "ttl": 86400, - "type": "A", - "value": "192.168.178.1" - } - ] - } -} diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records.json deleted file mode 100644 index a335b5ba5..000000000 --- a/providers/dns/keyhelp/internal/fixtures/update_domain_records.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": 8 -} diff --git a/providers/dns/keyhelp/internal/types.go b/providers/dns/keyhelp/internal/types.go deleted file mode 100644 index 8716fa0c8..000000000 --- a/providers/dns/keyhelp/internal/types.go +++ /dev/null @@ -1,63 +0,0 @@ -package internal - -import ( - "fmt" -) - -type APIError struct { - Code string `json:"code,omitempty"` - Message string `json:"message,omitempty"` -} - -func (a *APIError) Error() string { - return fmt.Sprintf("%s: %s", a.Code, a.Message) -} - -type Domain struct { - ID int `json:"id,omitempty"` - UserID int `json:"id_user,omitempty"` - ParentDomainID int `json:"id_parent_domain,omitempty"` - Status int `json:"status,omitempty"` - Domain string `json:"domain,omitempty"` - DomainUTF8 string `json:"domain_utf8,omitempty"` - IsDisabled bool `json:"is_disabled,omitempty"` - IsCustomDNS bool `json:"is_custom_dns,omitempty"` - IsDNSDisabled bool `json:"is_dns_disabled,omitempty"` - IsSubdomain bool `json:"is_subdomain,omitempty"` - IsSystemDomain bool `json:"is_system_domain,omitempty"` - IsEmailDomain bool `json:"is_email_domain,omitempty"` - IsEmailSendingOnly bool `json:"is_email_sending_only,omitempty"` -} - -type DomainID struct { - ID int `json:"id,omitempty"` -} - -type DomainRecords struct { - IsCustomDNS bool `json:"is_custom_dns,omitempty"` - IsDNSDisabled bool `json:"is_dns_disabled,omitempty"` - DkimRecord string `json:"dkim_record,omitempty"` - Records *Records `json:"records,omitempty"` -} - -type Records struct { - Soa *SOARecord `json:"soa,omitempty"` - Other []Record `json:"other,omitempty"` -} - -type SOARecord struct { - TTL int `json:"ttl,omitempty"` - PrimaryNs string `json:"primary_ns,omitempty"` - RName string `json:"rname,omitempty"` - Refresh int `json:"refresh,omitempty"` - Retry int `json:"retry,omitempty"` - Expire int `json:"expire,omitempty"` - Minimum int `json:"minimum,omitempty"` -} - -type Record struct { - Host string `json:"host"` - TTL int `json:"ttl"` - Type string `json:"type"` - Value string `json:"value"` -} diff --git a/providers/dns/keyhelp/keyhelp.go b/providers/dns/keyhelp/keyhelp.go deleted file mode 100644 index 67ceaaa63..000000000 --- a/providers/dns/keyhelp/keyhelp.go +++ /dev/null @@ -1,225 +0,0 @@ -// Package keyhelp implements a DNS provider for solving the DNS-01 challenge using KeyHelp. -package keyhelp - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/keyhelp/internal" -) - -// Environment variables names. -const ( - envNamespace = "KEYHELP_" - - EnvBaseURL = envNamespace + "BASE_URL" - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - BaseURL string - APIKey string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - domainIDs map[string]int - domainIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for KeyHelp. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvBaseURL, EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("keyhelp: %w", err) - } - - config := NewDefaultConfig() - config.BaseURL = values[EnvBaseURL] - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for KeyHelp. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("keyhelp: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.BaseURL, config.APIKey) - if err != nil { - return nil, fmt.Errorf("keyhelp: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - domainIDs: make(map[string]int), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("keyhelp: could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - domainInfo, err := d.findDomain(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("keyhelp: %w", err) - } - - domainRecords, err := d.client.ListDomainRecords(ctx, domainInfo.ID) - if err != nil { - return fmt.Errorf("keyhelp: list domain records: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("keyhelp: %w", err) - } - - records := domainRecords.Records.Other - records = append(records, internal.Record{ - Host: subDomain, - TTL: d.config.TTL, - Type: "TXT", - Value: info.Value, - }) - - req := internal.DomainRecords{ - DkimRecord: domainRecords.DkimRecord, - Records: &internal.Records{ - Soa: domainRecords.Records.Soa, - Other: records, - }, - } - - _, err = d.client.UpdateDomainRecords(ctx, domainInfo.ID, req) - if err != nil { - return fmt.Errorf("keyhelp: update domain records (add): %w", err) - } - - d.domainIDsMu.Lock() - d.domainIDs[token] = domainInfo.ID - d.domainIDsMu.Unlock() - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - // get the domain's unique ID from when we created it - d.domainIDsMu.Lock() - domainID, ok := d.domainIDs[token] - d.domainIDsMu.Unlock() - - if !ok { - return fmt.Errorf("keyhelp: unknown record ID for '%s'", info.EffectiveFQDN) - } - - domainRecords, err := d.client.ListDomainRecords(ctx, domainID) - if err != nil { - return fmt.Errorf("keyhelp: list domain records: %w", err) - } - - var records []internal.Record - - for _, record := range domainRecords.Records.Other { - if record.Type == "TXT" && record.Value == info.Value { - continue - } - - records = append(records, record) - } - - req := internal.DomainRecords{ - DkimRecord: domainRecords.DkimRecord, - Records: &internal.Records{ - Soa: domainRecords.Records.Soa, - Other: records, - }, - } - - _, err = d.client.UpdateDomainRecords(ctx, domainID, req) - if err != nil { - return fmt.Errorf("keyhelp: update domain records (delete): %w", err) - } - - // Delete domain ID from map - d.domainIDsMu.Lock() - delete(d.domainIDs, token) - d.domainIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) findDomain(ctx context.Context, zone string) (internal.Domain, error) { - domains, err := d.client.ListDomains(ctx) - if err != nil { - return internal.Domain{}, fmt.Errorf("list domains: %w", err) - } - - for _, domain := range domains { - if domain.DomainUTF8 == zone || domain.Domain == zone { - return domain, nil - } - } - - return internal.Domain{}, fmt.Errorf("domain not found: %s", zone) -} diff --git a/providers/dns/keyhelp/keyhelp.toml b/providers/dns/keyhelp/keyhelp.toml deleted file mode 100644 index e622794ca..000000000 --- a/providers/dns/keyhelp/keyhelp.toml +++ /dev/null @@ -1,24 +0,0 @@ -Name = "KeyHelp" -Description = '''''' -URL = "https://www.keyweb.de/en/keyhelp/keyhelp/" -Code = "keyhelp" -Since = "v4.26.0" - -Example = ''' -KEYHELP_BASE_URL="https://keyhelp.example.com" \ -KEYHELP_API_KEY="xxx" \ -lego --dns keyhelp -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - KEYHELP_BASE_URL= "Server URL" - KEYHELP_API_KEY = "API key" - [Configuration.Additional] - KEYHELP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - KEYHELP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - KEYHELP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - KEYHELP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://app.swaggerhub.com/apis-docs/keyhelp/api/" diff --git a/providers/dns/keyhelp/keyhelp_test.go b/providers/dns/keyhelp/keyhelp_test.go deleted file mode 100644 index 8d8ac821d..000000000 --- a/providers/dns/keyhelp/keyhelp_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package keyhelp - -import ( - "net/http/httptest" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/go-acme/lego/v4/providers/dns/keyhelp/internal" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvBaseURL, EnvAPIKey). - WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvBaseURL: "https://keyhelp.example.com", - EnvAPIKey: "secret", - }, - }, - { - desc: "missing base URL", - envVars: map[string]string{ - EnvAPIKey: "secret", - }, - expected: "keyhelp: some credentials information are missing: KEYHELP_BASE_URL", - }, - { - desc: "missing API key", - envVars: map[string]string{ - EnvBaseURL: "https://keyhelp.example.com", - }, - expected: "keyhelp: some credentials information are missing: KEYHELP_API_KEY", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "keyhelp: some credentials information are missing: KEYHELP_BASE_URL,KEYHELP_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - baseURL string - apiKey string - expected string - }{ - { - desc: "success", - baseURL: "https://keyhelp.example.com", - apiKey: "secret", - }, - { - desc: "missing base URL", - apiKey: "secret", - expected: "keyhelp: missing base URL", - }, - { - desc: "missing API key", - baseURL: "https://keyhelp.example.com", - expected: "keyhelp: credentials missing", - }, - { - desc: "missing credentials", - expected: "keyhelp: missing base URL", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.BaseURL = test.baseURL - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.HTTPClient = server.Client() - config.APIKey = "secret" - config.BaseURL = server.URL - - return NewDNSProviderConfig(config) - }, - servermock.CheckHeader(). - With(internal.APIKeyHeader, "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/v2/domains", - servermock.ResponseFromInternal("get_domains.json"), - servermock.CheckQueryParameter(). - With("sort", "domain_utf8"). - Strict()). - Route("GET /api/v2/dns/8", - servermock.ResponseFromInternal("get_domain_records.json")). - Route("PUT /api/v2/dns/8", - servermock.ResponseFromInternal("update_domain_records.json"), - servermock.CheckRequestJSONBodyFromInternal("update_domain_records-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) - - assert.Equal(t, 8, provider.domainIDs["abc"]) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/v2/dns/8", - servermock.ResponseFromInternal("get_domain_records2.json")). - Route("PUT /api/v2/dns/8", - servermock.ResponseFromInternal("update_domain_records.json"), - servermock.CheckRequestJSONBodyFromInternal("update_domain_records-request2.json")). - Build(t) - - provider.domainIDs["abc"] = 8 - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/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..3d4af1d3b 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) } @@ -66,7 +60,6 @@ func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, e } var response Response[[]Record] - err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -79,7 +72,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) } @@ -101,7 +94,6 @@ func (c *Client) CreateRecord(ctx context.Context, domainName string, record Rec } var response Response[*Record] - err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -114,7 +106,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) } @@ -136,7 +128,6 @@ func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*R } var response Response[*Record] - err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -149,7 +140,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 +159,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 { @@ -203,7 +187,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/liara/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..a0437b0eb 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/liara/internal" "github.com/hashicorp/go-retryablehttp" ) @@ -23,7 +22,6 @@ const ( envNamespace = "LIARA_" EnvAPIKey = envNamespace + "API_KEY" - EnvTeamID = envNamespace + "TEAM_ID" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -40,9 +38,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 +76,6 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values[EnvAPIKey] - config.TeamID = env.GetOrFile(EnvTeamID) return NewDNSProviderConfig(config) } @@ -104,20 +99,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } retryClient := retryablehttp.NewClient() - retryClient.RetryMax = 5 if config.HTTPClient != nil { retryClient.HTTPClient = config.HTTPClient } - retryClient.Logger = log.Logger - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey), - ), - config.TeamID, - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey)) return &DNSProvider{ config: config, @@ -152,7 +140,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Contents: []internal.Content{{Text: info.Value}}, TTL: d.config.TTL, } - newRecord, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) if err != nil { return fmt.Errorf("liara: failed to create TXT record, fqdn=%s: %w", info.EffectiveFQDN, err) @@ -178,7 +165,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("liara: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/liara/liara.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/liara/liara_test.go b/providers/dns/liara/liara_test.go index b1f3f77c9..4256be55e 100644 --- a/providers/dns/liara/liara_test.go +++ b/providers/dns/liara/liara_test.go @@ -38,7 +38,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -114,7 +113,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -128,7 +126,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/lightsail/lightsail.go b/providers/dns/lightsail/lightsail.go index 95b07c503..ddaf7baca 100644 --- a/providers/dns/lightsail/lightsail.go +++ b/providers/dns/lightsail/lightsail.go @@ -99,7 +99,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) - return time.Duration(delay) * time.Millisecond, nil }) }) diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index dc86bf079..718e57460 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -34,7 +34,6 @@ func TestLiveTTL(t *testing.T) { require.NoError(t, err) svc := lightsail.NewFromConfig(cfg) - require.NoError(t, err) defer func() { diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index a6b46045e..010e794a9 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -34,7 +34,6 @@ var envTest = tester.NewEnvTest( func TestCredentialsFromEnv(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() _ = os.Setenv(envAwsAccessKeyID, "123") @@ -61,7 +60,6 @@ func TestDNSProvider_Present(t *testing.T) { func(server *httptest.Server) (*DNSProvider, error) { return &DNSProvider{ client: lightsail.NewFromConfig(aws.Config{ - HTTPClient: server.Client(), Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), Region: "mock-region", BaseEndpoint: aws.String(server.URL), @@ -77,5 +75,5 @@ func TestDNSProvider_Present(t *testing.T) { keyAuth := "123456d==" err := provider.Present(domain, "", keyAuth) - require.NoError(t, err) + require.NoError(t, err, "Expected Present to return no error") } diff --git a/providers/dns/limacity/internal/client.go b/providers/dns/limacity/internal/client.go index ae6ab87eb..07622e121 100644 --- a/providers/dns/limacity/internal/client.go +++ b/providers/dns/limacity/internal/client.go @@ -41,7 +41,6 @@ func (c *Client) GetDomains(ctx context.Context) ([]Domain, error) { } var results DomainsResponse - err = c.do(req, &results) if err != nil { return nil, err @@ -59,7 +58,6 @@ func (c *Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) } var results RecordsResponse - err = c.do(req, &results) if err != nil { return nil, err @@ -77,7 +75,6 @@ func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) err } var results APIResponse - err = c.do(req, &results) if err != nil { return err @@ -95,7 +92,6 @@ func (c *Client) UpdateRecord(ctx context.Context, domainID, recordID int, recor } var results APIResponse - err = c.do(req, &results) if err != nil { return err @@ -114,7 +110,6 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error } var results APIResponse - err = c.do(req, &results) if err != nil { return err @@ -182,7 +177,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/limacity/internal/types.go b/providers/dns/limacity/internal/types.go index 7411632ea..5fdbacef9 100644 --- a/providers/dns/limacity/internal/types.go +++ b/providers/dns/limacity/internal/types.go @@ -10,7 +10,7 @@ type RecordsResponse struct { } type NameserverRecordPayload struct { - Data Record `json:"nameserver_record"` + Data Record `json:"nameserver_record,omitempty"` } type DomainsResponse struct { diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index 3291faf66..58755aabe 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/limacity/internal" ) @@ -90,12 +89,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.APIKey) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -117,11 +110,9 @@ func (d *DNSProvider) Sequential() time.Duration { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.GetDomains(ctx) + domains, err := d.client.GetDomains(context.Background()) if err != nil { return fmt.Errorf("limacity: get domains: %w", err) } @@ -143,7 +134,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Type: "TXT", } - err = d.client.AddRecord(ctx, dom.ID, record) + err = d.client.AddRecord(context.Background(), dom.ID, record) if err != nil { return fmt.Errorf("limacity: add record: %w", err) } @@ -157,26 +148,22 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) // gets the domain's unique ID d.domainIDsMu.Lock() domainID, ok := d.domainIDs[token] d.domainIDsMu.Unlock() - if !ok { return fmt.Errorf("limacity: unknown domain ID for '%s' '%s'", info.EffectiveFQDN, token) } - records, err := d.client.GetRecords(ctx, domainID) + records, err := d.client.GetRecords(context.Background(), domainID) if err != nil { return fmt.Errorf("limacity: get records: %w", err) } var recordID int - for _, record := range records { if record.Type == "TXT" && record.Content == strconv.Quote(info.Value) { recordID = record.ID @@ -188,15 +175,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return errors.New("limacity: TXT record not found") } - err = d.client.DeleteRecord(ctx, domainID, recordID) + err = d.client.DeleteRecord(context.Background(), domainID, recordID) if err != nil { 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/limacity/limacity_test.go b/providers/dns/limacity/limacity_test.go index 3301fcb2e..2834a5f1f 100644 --- a/providers/dns/limacity/limacity_test.go +++ b/providers/dns/limacity/limacity_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index b03dee4f5..25e1b07d1 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/linode/linodego" "golang.org/x/oauth2" @@ -103,7 +102,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { }, } - client := linodego.NewClient(clientdebug.Wrap(oauth2Client)) + client := linodego.NewClient(oauth2Client) client.SetUserAgent(useragent.Get()) return &DNSProvider{config: config, client: &client}, nil @@ -131,11 +130,9 @@ func (d *DNSProvider) Timeout() (time.Duration, time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZoneInfo(ctx, info.EffectiveFQDN) + zone, err := d.getHostedZoneInfo(info.EffectiveFQDN) if err != nil { return err } @@ -147,26 +144,22 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Type: linodego.RecordTypeTXT, } - _, err = d.client.CreateDomainRecord(ctx, zone.domainID, createOpts) - + _, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts) return err } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZoneInfo(ctx, info.EffectiveFQDN) + zone, err := d.getHostedZoneInfo(info.EffectiveFQDN) if err != nil { return err } // Get all TXT records for the specified domain. listOpts := linodego.NewListOptions(0, `{"type":"TXT"}`) - - resources, err := d.client.ListDomainRecords(ctx, zone.domainID, listOpts) + resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts) if err != nil { return err } @@ -175,7 +168,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { for _, resource := range resources { if (resource.Name == dns01.UnFqdn(info.EffectiveFQDN) || resource.Name == zone.resourceName) && resource.Target == info.Value { - if err := d.client.DeleteDomainRecord(ctx, zone.domainID, resource.ID); err != nil { + if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil { return err } } @@ -184,7 +177,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) getHostedZoneInfo(ctx context.Context, fqdn string) (*hostedZoneInfo, error) { +func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { // Lookup the zone that handles the specified FQDN. authZone, err := dns01.FindZoneByFqdn(fqdn) if err != nil { @@ -198,8 +191,7 @@ func (d *DNSProvider) getHostedZoneInfo(ctx context.Context, fqdn string) (*host } listOpts := linodego.NewListOptions(0, string(filter)) - - domains, err := d.client.ListDomains(ctx, listOpts) + domains, err := d.client.ListDomains(context.Background(), listOpts) if err != nil { return nil, err } 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/linode/linode_test.go b/providers/dns/linode/linode_test.go index 1c4903aca..08549ab7e 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -39,7 +39,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,7 +94,6 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() - os.Setenv(EnvToken, "testing") domain := "example.com" @@ -180,7 +178,6 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() - os.Setenv(EnvToken, "testing") domain := "example.com" diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go index 6e93e2a12..2d0a46142 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 } @@ -160,7 +159,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } params := &network.DNSRecordParams{ID: recordID} - _, err := d.client.NetworkDNS.Delete(params) if err != nil { return fmt.Errorf("liquidweb: could not remove TXT record: %w", err) @@ -181,7 +179,6 @@ func (d *DNSProvider) findZone(domain string) (string, error) { // filter the zones on the account to only ones that match var zs []network.DNSZone - for _, item := range zones.Items { if strings.HasSuffix(domain, item.Name) { zs = append(zs, item) diff --git a/providers/dns/liquidweb/liquidweb.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..b0788c7f5 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", }, @@ -66,7 +66,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -148,13 +147,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 +163,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 +180,7 @@ func TestDNSProvider(t *testing.T) { }{ { desc: "expected successful", - domain: "tacoman.example", + domain: "tacoman.com", token: "123", keyAuth: "456", present: true, @@ -189,7 +188,7 @@ func TestDNSProvider(t *testing.T) { }, { desc: "other successful", - domain: "banana.example", + domain: "banana.com", token: "123", keyAuth: "456", present: true, @@ -197,16 +196,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 +213,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, @@ -249,7 +248,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/liquidweb/servermock_test.go b/providers/dns/liquidweb/servermock_test.go index 4886e17f1..9cb434761 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)). @@ -62,7 +62,6 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, makeEncodingError(body), http.StatusBadRequest) return } - payload.Params.ID = types.FlexInt(rand.Intn(10000000)) payload.Params.ZoneID = types.FlexInt(mockAPIServerZones[payload.Params.Name]) @@ -70,7 +69,6 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, "dns record already exists", http.StatusTeapot) return } - recs[int(payload.Params.ID)] = payload.Params resp, err := json.Marshal(payload.Params) @@ -78,7 +76,6 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, "", http.StatusInternalServerError) return } - http.Error(rw, string(resp), http.StatusOK) } } @@ -112,7 +109,6 @@ func mockAPIDelete(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, payload.Params.ID, payload.Params.ID), http.StatusOK) return } - delete(recs, payload.Params.ID) http.Error(rw, fmt.Sprintf("{\"deleted\":%d}", payload.Params.ID), http.StatusOK) } @@ -145,7 +141,6 @@ func mockAPIListZones() http.HandlerFunc { case payload.Params.PageNum > len(mockZones): payload.Params.PageNum = len(mockZones) } - resp := mockZones[payload.Params.PageNum] resp.ItemTotal = types.FlexInt(len(mockAPIServerZones)) resp.PageNum = types.FlexInt(payload.Params.PageNum) @@ -172,38 +167,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 +206,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,43 +245,41 @@ 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", }, }, }, } mockAPIServerZones := make(map[string]int) - for _, page := range mockZones { for _, zone := range page.Items { mockAPIServerZones[zone.Name] = int(zone.ID) } } - return mockZones, mockAPIServerZones } diff --git a/providers/dns/loopia/internal/client_test.go b/providers/dns/loopia/internal/client_test.go index fed7d94f1..63962b06e 100644 --- a/providers/dns/loopia/internal/client_test.go +++ b/providers/dns/loopia/internal/client_test.go @@ -15,7 +15,6 @@ func mockBuilder(password string) *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient("apiuser", password) - client.HTTPClient = server.Client() client.BaseURL = server.URL + "/" return client, nil diff --git a/providers/dns/loopia/internal/types.go b/providers/dns/loopia/internal/types.go index c3425c8b1..c286c01fd 100644 --- a/providers/dns/loopia/internal/types.go +++ b/providers/dns/loopia/internal/types.go @@ -66,7 +66,6 @@ type response interface { type responseString struct { responseFault - Value string `xml:"params>param>value>string"` } @@ -89,7 +88,6 @@ func (e RPCError) Error() string { type recordObjectsResponse struct { responseFault - XMLName xml.Name `xml:"methodResponse"` Params []RecordObj `xml:"params>param>value>array>data>value>struct"` } @@ -104,7 +102,6 @@ type RecordObj struct { func (r *RecordObj) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { var name string - for { t, err := d.Token() if err != nil { @@ -147,7 +144,6 @@ func (r *RecordObj) decodeValueString(name string, d *xml.Decoder, start xml.Sta } s = strings.TrimSpace(s) - switch name { case "type": r.Type = s diff --git a/providers/dns/loopia/loopia.go b/providers/dns/loopia/loopia.go index be3416ddf..8389ae5f6 100644 --- a/providers/dns/loopia/loopia.go +++ b/providers/dns/loopia/loopia.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/loopia/internal" ) @@ -114,8 +113,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.BaseURL != "" { client.BaseURL = config.BaseURL } diff --git a/providers/dns/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/loopia/loopia_test.go b/providers/dns/loopia/loopia_test.go index b3163fc77..e397c9639 100644 --- a/providers/dns/loopia/loopia_test.go +++ b/providers/dns/loopia/loopia_test.go @@ -103,7 +103,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -193,7 +192,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -207,7 +205,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/luadns/internal/client.go b/providers/dns/luadns/internal/client.go index 5ce9cca86..8e46418f2 100644 --- a/providers/dns/luadns/internal/client.go +++ b/providers/dns/luadns/internal/client.go @@ -49,7 +49,6 @@ func (c *Client) ListZones(ctx context.Context) ([]DNSZone, error) { } var zones []DNSZone - err = c.do(req, &zones) if err != nil { return nil, fmt.Errorf("could not list zones: %w", err) @@ -69,7 +68,6 @@ func (c *Client) CreateRecord(ctx context.Context, zone DNSZone, newRecord DNSRe } var record *DNSRecord - err = c.do(req, &record) if err != nil { return nil, fmt.Errorf("could not create record %#v: %w", record, err) @@ -155,7 +153,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errResp errorResponse - err := json.Unmarshal(raw, &errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/luadns/luadns.go b/providers/dns/luadns/luadns.go index 68b9c66b8..026a0da70 100644 --- a/providers/dns/luadns/luadns.go +++ b/providers/dns/luadns/luadns.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/luadns/internal" ) @@ -101,12 +100,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - 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/luadns/luadns_test.go b/providers/dns/luadns/luadns_test.go index a1aa36872..ea4d06ae1 100644 --- a/providers/dns/luadns/luadns_test.go +++ b/providers/dns/luadns/luadns_test.go @@ -58,7 +58,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -200,7 +199,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -214,7 +212,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mailinabox/mailinabox.go b/providers/dns/mailinabox/mailinabox.go index cf6202a92..3ea8a9f29 100644 --- a/providers/dns/mailinabox/mailinabox.go +++ b/providers/dns/mailinabox/mailinabox.go @@ -5,13 +5,11 @@ import ( "context" "errors" "fmt" - "net/http" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/mailinabox" ) @@ -25,7 +23,6 @@ const ( EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -37,7 +34,6 @@ type Config struct { BaseURL string PropagationTimeout time.Duration PollingInterval time.Duration - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -45,9 +41,6 @@ func NewDefaultConfig() *Config { return &Config{ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } @@ -88,13 +81,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("mailinabox: missing base URL") } - if config.HTTPClient == nil { - config.HTTPClient = &http.Client{Timeout: 30 * time.Second} - } - - config.HTTPClient = clientdebug.Wrap(config.HTTPClient) - - client, err := mailinabox.New(config.BaseURL, config.Email, config.Password, mailinabox.WithHTTPClient(config.HTTPClient)) + client, err := mailinabox.New(config.BaseURL, config.Email, config.Password) if err != nil { return nil, fmt.Errorf("mailinabox: %w", err) } diff --git a/providers/dns/mailinabox/mailinabox.toml b/providers/dns/mailinabox/mailinabox.toml index 74d8aabbc..4b30dd9e2 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] @@ -19,7 +19,6 @@ lego --dns mailinabox -d '*.example.com' -d example.com run [Configuration.Additional] MAILINABOX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" MAILINABOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" - MAILINABOX_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://mailinabox.email/api-docs.html" diff --git a/providers/dns/mailinabox/mailinabox_test.go b/providers/dns/mailinabox/mailinabox_test.go index 11143a11f..1b95c220d 100644 --- a/providers/dns/mailinabox/mailinabox_test.go +++ b/providers/dns/mailinabox/mailinabox_test.go @@ -59,7 +59,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -137,7 +136,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -151,7 +149,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/manageengine/internal/client.go b/providers/dns/manageengine/internal/client.go index b5a7dbae7..b360840f0 100644 --- a/providers/dns/manageengine/internal/client.go +++ b/providers/dns/manageengine/internal/client.go @@ -24,12 +24,12 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(hc *http.Client) *Client { +func NewClient(ctx context.Context, clientID, clientSecret string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ baseURL: baseURL, - httpClient: hc, + httpClient: createOAuthClient(ctx, clientID, clientSecret), } } @@ -109,7 +109,6 @@ func (c *Client) UpdateZoneRecord(ctx context.Context, record ZoneRecord) error if record.SpfTxtDomainID == 0 { return errors.New("SpfTxtDomainID is empty") } - if record.ZoneID == 0 { return errors.New("ZoneID is empty") } @@ -189,7 +188,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/manageengine/internal/client_test.go b/providers/dns/manageengine/internal/client_test.go index 25d1730f6..0c18a245f 100644 --- a/providers/dns/manageengine/internal/client_test.go +++ b/providers/dns/manageengine/internal/client_test.go @@ -1,6 +1,7 @@ package internal import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -14,8 +15,9 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(server.Client()) + client := NewClient(context.Background(), "abc", "secret") + client.httpClient = server.Client() client.baseURL, _ = url.Parse(server.URL) return client, nil diff --git a/providers/dns/manageengine/internal/identity.go b/providers/dns/manageengine/internal/identity.go index ec28121e4..66a659188 100644 --- a/providers/dns/manageengine/internal/identity.go +++ b/providers/dns/manageengine/internal/identity.go @@ -9,7 +9,7 @@ import ( const defaultAuthURL = "https://clouddns.manageengine.com/oauth2/token/" -func CreateOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { +func createOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { config := &clientcredentials.Config{ TokenURL: defaultAuthURL, ClientID: clientID, diff --git a/providers/dns/manageengine/manageengine.go b/providers/dns/manageengine/manageengine.go index 76b6644c0..f26ae33b5 100644 --- a/providers/dns/manageengine/manageengine.go +++ b/providers/dns/manageengine/manageengine.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/manageengine/internal" ) @@ -76,13 +75,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("manageengine: credentials missing") } + client := internal.NewClient(context.Background(), config.ClientID, config.ClientSecret) + return &DNSProvider{ config: config, - client: internal.NewClient( - clientdebug.Wrap( - internal.CreateOAuthClient(context.Background(), config.ClientID, config.ClientSecret), - ), - ), + client: client, }, nil } @@ -195,7 +192,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Update the zone record. var values []string - for _, value := range record.Values { if value != info.Value { values = append(values, value) diff --git a/providers/dns/manageengine/manageengine.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/manageengine/manageengine_test.go b/providers/dns/manageengine/manageengine_test.go index 215de68dd..624459be9 100644 --- a/providers/dns/manageengine/manageengine_test.go +++ b/providers/dns/manageengine/manageengine_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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..9b8c41def 100644 --- a/providers/dns/metaname/metaname.go +++ b/providers/dns/metaname/metaname.go @@ -79,7 +79,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.AccountReference == "" { return nil, errors.New("metaname: missing account reference") } - if config.APIKey == "" { return nil, errors.New("metaname: missing api key") } @@ -153,10 +152,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/metaname/metaname_test.go b/providers/dns/metaname/metaname_test.go index 855fc493d..174af4014 100644 --- a/providers/dns/metaname/metaname_test.go +++ b/providers/dns/metaname/metaname_test.go @@ -51,7 +51,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/metaregistrar/internal/client.go b/providers/dns/metaregistrar/internal/client.go index df99d81ba..711a1e94c 100644 --- a/providers/dns/metaregistrar/internal/client.go +++ b/providers/dns/metaregistrar/internal/client.go @@ -121,7 +121,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/metaregistrar/metaregistrar.go b/providers/dns/metaregistrar/metaregistrar.go index 7a601ef21..28526fcb4 100644 --- a/providers/dns/metaregistrar/metaregistrar.go +++ b/providers/dns/metaregistrar/metaregistrar.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/metaregistrar/internal" ) @@ -83,8 +82,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/metaregistrar/metaregistrar_test.go b/providers/dns/metaregistrar/metaregistrar_test.go index aa9bbbb58..ffd7965d9 100644 --- a/providers/dns/metaregistrar/metaregistrar_test.go +++ b/providers/dns/metaregistrar/metaregistrar_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mijnhost/internal/client.go b/providers/dns/mijnhost/internal/client.go index a51233211..1a67a73b1 100644 --- a/providers/dns/mijnhost/internal/client.go +++ b/providers/dns/mijnhost/internal/client.go @@ -47,7 +47,6 @@ func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { } var results Response[DomainData] - err = c.do(req, &results) if err != nil { return nil, err @@ -67,7 +66,6 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var results Response[RecordData] - err = c.do(req, &results) if err != nil { return nil, err @@ -153,7 +151,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go index adb3e9ce3..21aca3c9e 100644 --- a/providers/dns/mijnhost/mijnhost.go +++ b/providers/dns/mijnhost/mijnhost.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mijnhost/internal" ) @@ -87,12 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.APIKey) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -113,11 +106,9 @@ func (d *DNSProvider) Sequential() time.Duration { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.ListDomains(ctx) + domains, err := d.client.ListDomains(context.Background()) if err != nil { return fmt.Errorf("mijnhost: list domains: %w", err) } @@ -127,7 +118,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("mijnhost: find domain: %w", err) } - records, err := d.client.GetRecords(ctx, dom.Domain) + records, err := d.client.GetRecords(context.Background(), dom.Domain) if err != nil { return fmt.Errorf("mijnhost: get records: %w", err) } @@ -152,7 +143,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { cleanedRecords = append(cleanedRecords, record) - err = d.client.UpdateRecords(ctx, dom.Domain, cleanedRecords) + err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords) if err != nil { return fmt.Errorf("mijnhost: update records: %w", err) } @@ -162,11 +153,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.ListDomains(ctx) + domains, err := d.client.ListDomains(context.Background()) if err != nil { return fmt.Errorf("mijnhost: list domains: %w", err) } @@ -176,7 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("mijnhost: find domain: %w", err) } - records, err := d.client.GetRecords(ctx, dom.Domain) + records, err := d.client.GetRecords(context.Background(), dom.Domain) if err != nil { return fmt.Errorf("mijnhost: get records: %w", err) } @@ -185,7 +174,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return record.Type == txtType && record.Value == info.Value }) - err = d.client.UpdateRecords(ctx, dom.Domain, cleanedRecords) + err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords) if err != nil { return fmt.Errorf("mijnhost: update records: %w", err) } 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/mijnhost/mijnhost_test.go b/providers/dns/mijnhost/mijnhost_test.go index c87ae0a40..a48f84ca8 100644 --- a/providers/dns/mijnhost/mijnhost_test.go +++ b/providers/dns/mijnhost/mijnhost_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,7 +94,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,7 +107,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mittwald/internal/client.go b/providers/dns/mittwald/internal/client.go index 2b1564dc1..69222903d 100644 --- a/providers/dns/mittwald/internal/client.go +++ b/providers/dns/mittwald/internal/client.go @@ -47,7 +47,6 @@ func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { } var result []Domain - err = c.do(req, &result) if err != nil { return nil, err @@ -67,7 +66,6 @@ func (c *Client) GetDNSZone(ctx context.Context, zoneID string) (*DNSZone, error } result := &DNSZone{} - err = c.do(req, result) if err != nil { return nil, err @@ -87,7 +85,6 @@ func (c *Client) ListDNSZones(ctx context.Context, projectID string) ([]DNSZone, } var result []DNSZone - err = c.do(req, &result) if err != nil { return nil, err @@ -107,7 +104,6 @@ func (c *Client) CreateDNSZone(ctx context.Context, zone CreateDNSZoneRequest) ( } result := &DNSZone{} - err = c.do(req, result) if err != nil { return nil, err @@ -201,7 +197,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mittwald/internal/types.go b/providers/dns/mittwald/internal/types.go index 86cdf065c..df10ab293 100644 --- a/providers/dns/mittwald/internal/types.go +++ b/providers/dns/mittwald/internal/types.go @@ -1,9 +1,6 @@ package internal -import ( - "fmt" - "strings" -) +import "fmt" // https://api.mittwald.de/v2/docs/#/Domain/domain-list-domains @@ -39,7 +36,7 @@ type NewDNSZone struct { // https://api.mittwald.de/v2/docs/#/Domain/dns-update-record-set type TXTRecord struct { - Settings Settings `json:"settings"` + Settings Settings `json:"settings,omitempty"` Entries []string `json:"entries,omitempty"` } @@ -61,25 +58,23 @@ type APIError struct { } func (a APIError) Error() string { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%s: %s", a.Type, a.Message) + msg := 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)]", + msg += fmt.Sprintf(" [%s: %s (%s, %s)]", validationError.Type, validationError.Message, validationError.Path, validationError.Context.Format) } } - return msg.String() + return msg } type ValidationError struct { Message string `json:"message,omitempty"` Path string `json:"path,omitempty"` Type string `json:"type,omitempty"` - Context ValidationErrorContext `json:"context"` + Context ValidationErrorContext `json:"context,omitempty"` } type ValidationErrorContext struct { diff --git a/providers/dns/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index dcd882482..2c3c5a8f3 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mittwald/internal" ) @@ -93,17 +92,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("mittwald: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } - client := internal.NewClient(config.Token) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, - client: client, + client: internal.NewClient(config.Token), zoneIDs: map[string]string{}, }, nil } @@ -158,7 +149,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.zoneIDsMu.Lock() zoneID, ok := d.zoneIDs[token] d.zoneIDsMu.Unlock() - if !ok { return fmt.Errorf("mittwald: unknown zone ID for '%s'", info.EffectiveFQDN) } @@ -170,10 +160,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/mittwald/mittwald_test.go b/providers/dns/mittwald/mittwald_test.go index 6a6599536..d8cbdb263 100644 --- a/providers/dns/mittwald/mittwald_test.go +++ b/providers/dns/mittwald/mittwald_test.go @@ -38,7 +38,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -105,7 +104,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,7 +117,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/myaddr/myaddr.go b/providers/dns/myaddr/myaddr.go index fb7ea66a0..df280f2f4 100644 --- a/providers/dns/myaddr/myaddr.go +++ b/providers/dns/myaddr/myaddr.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/myaddr/internal" ) @@ -92,8 +91,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/myaddr/myaddr_test.go b/providers/dns/myaddr/myaddr_test.go index 8e555ecfd..d95a0cf5c 100644 --- a/providers/dns/myaddr/myaddr_test.go +++ b/providers/dns/myaddr/myaddr_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mydnsjp/mydnsjp.go b/providers/dns/mydnsjp/mydnsjp.go index 8a790c88e..d0565e8bd 100644 --- a/providers/dns/mydnsjp/mydnsjp.go +++ b/providers/dns/mydnsjp/mydnsjp.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mydnsjp/internal" ) @@ -80,17 +79,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("mydnsjp: some credentials information are missing") } - client := internal.NewClient(config.MasterID, config.Password) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, - client: client, + client: internal.NewClient(config.MasterID, config.Password), }, nil } @@ -109,7 +100,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("mydnsjp: %w", err) } - return nil } @@ -122,6 +112,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("mydnsjp: %w", err) } - return nil } diff --git a/providers/dns/mydnsjp/mydnsjp.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/mydnsjp/mydnsjp_test.go b/providers/dns/mydnsjp/mydnsjp_test.go index c82bd2264..96eb95865 100644 --- a/providers/dns/mydnsjp/mydnsjp_test.go +++ b/providers/dns/mydnsjp/mydnsjp_test.go @@ -56,7 +56,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,7 +124,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,7 +137,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mythicbeasts/internal/client.go b/providers/dns/mythicbeasts/internal/client.go index 82c51dbf3..87464553c 100644 --- a/providers/dns/mythicbeasts/internal/client.go +++ b/providers/dns/mythicbeasts/internal/client.go @@ -99,7 +99,6 @@ func (c *Client) createTXTRecord(ctx context.Context, zone, leaf, recordType, va } resp := &createTXTResponse{} - err = c.do(req, resp) if err != nil { return nil, err diff --git a/providers/dns/mythicbeasts/internal/identity.go b/providers/dns/mythicbeasts/internal/identity.go index 15e35ba69..417f1c759 100644 --- a/providers/dns/mythicbeasts/internal/identity.go +++ b/providers/dns/mythicbeasts/internal/identity.go @@ -44,7 +44,6 @@ func (c *Client) obtainToken(ctx context.Context) (*Token, error) { } tok := Token{} - err = json.Unmarshal(raw, &tok) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -84,7 +83,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &authResponseError{} - err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mythicbeasts/mythicbeasts.go b/providers/dns/mythicbeasts/mythicbeasts.go index e8f5081f7..ae8f72d33 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.go +++ b/providers/dns/mythicbeasts/mythicbeasts.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mythicbeasts/internal" ) @@ -88,7 +87,6 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("mythicbeasts: %w", err) } - config.UserName = values[EnvUserName] config.Password = values[EnvPassword] @@ -119,8 +117,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/mythicbeasts/mythicbeasts_test.go b/providers/dns/mythicbeasts/mythicbeasts_test.go index c684725b7..5a8a9d4bb 100644 --- a/providers/dns/mythicbeasts/mythicbeasts_test.go +++ b/providers/dns/mythicbeasts/mythicbeasts_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -109,7 +108,6 @@ func TestNewDNSProviderConfig(t *testing.T) { t.Run(test.desc, func(t *testing.T) { config, err := NewDefaultConfig() require.NoError(t, err) - config.UserName = test.username config.Password = test.password @@ -132,7 +130,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -146,7 +143,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/namecheap/internal/client.go b/providers/dns/namecheap/internal/client.go index 6fb737b95..0fb32b1be 100644 --- a/providers/dns/namecheap/internal/client.go +++ b/providers/dns/namecheap/internal/client.go @@ -54,7 +54,6 @@ func (c *Client) GetHosts(ctx context.Context, sld, tld string) ([]Record, error } var ghr getHostsResponse - err = c.do(request, &ghr) if err != nil { return nil, err @@ -89,7 +88,6 @@ func (c *Client) SetHosts(ctx context.Context, sld, tld string, hosts []Record) } var shr setHostsResponse - err = c.do(req, &shr) if err != nil { return err @@ -98,7 +96,6 @@ func (c *Client) SetHosts(ctx context.Context, sld, tld string, hosts []Record) if len(shr.Errors) > 0 { return shr.Errors[0] } - if shr.Result.IsSuccess != "true" { return errors.New("setHosts failed") } diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index 54640f8e0..48b9492c4 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/namecheap/internal" "golang.org/x/net/publicsuffix" ) @@ -76,8 +75,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), }, } } @@ -119,7 +117,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("namecheap: %w", err) } - config.ClientIP = clientIP } @@ -130,8 +127,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -176,7 +171,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namecheap: %w", err) } - return nil } @@ -196,11 +190,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } // Find the challenge TXT record and remove it if found. - var ( - found bool - newRecords []internal.Record - ) - + var found bool + var newRecords []internal.Record for _, h := range records { if h.Name == pr.key && h.Type == "TXT" { found = true @@ -217,7 +208,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namecheap: %w", err) } - return nil } diff --git a/providers/dns/namecheap/namecheap.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/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index e55a4a6bc..922e48ebb 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -1,8 +1,10 @@ package namecheap import ( + "net/http" "net/http/httptest" "testing" + "time" "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" @@ -152,7 +154,6 @@ func Test_newPseudoRecord_domainSplit(t *testing.T) { for _, test := range tests { t.Run(test.domain, func(t *testing.T) { valid := true - ch, err := newPseudoRecord(test.domain, "") if err != nil { valid = false @@ -178,11 +179,11 @@ func Test_newPseudoRecord_domainSplit(t *testing.T) { func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.BaseURL = server.URL config.APIUser = envTestUser config.APIKey = envTestKey config.ClientIP = envTestClientIP + config.HTTPClient = &http.Client{Timeout: 60 * time.Second} return NewDNSProviderConfig(config) }) diff --git a/providers/dns/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..789599552 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -10,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/namedotcom/go/v4/namecom" ) @@ -98,12 +97,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := namecom.New(config.Username, config.APIToken) - - if config.HTTPClient != nil { - client.Client = config.HTTPClient - } - - client.Client = clientdebug.Wrap(client.Client) + client.Client = config.HTTPClient if config.Server != "" { client.Server = config.Server @@ -116,10 +110,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 +121,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 +142,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,11 +150,11 @@ 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, } - _, err := d.client.DeleteRecord(request) if err != nil { return fmt.Errorf("namedotcom: %w", err) @@ -189,7 +178,6 @@ func (d *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) { } var records []*namecom.Record - for request.Page > 0 { response, err := d.client.ListRecords(request) if err != nil { diff --git a/providers/dns/namedotcom/namedotcom.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/namedotcom/namedotcom_test.go b/providers/dns/namedotcom/namedotcom_test.go index da9878bdc..c7d4deaa1 100644 --- a/providers/dns/namedotcom/namedotcom_test.go +++ b/providers/dns/namedotcom/namedotcom_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -132,7 +131,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -146,7 +144,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/namesilo/namesilo.go b/providers/dns/namesilo/namesilo.go index 0297b4e1c..f76c8549e 100644 --- a/providers/dns/namesilo/namesilo.go +++ b/providers/dns/namesilo/namesilo.go @@ -2,7 +2,6 @@ package namesilo import ( - "context" "errors" "fmt" "time" @@ -10,7 +9,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/namesilo" ) @@ -81,15 +79,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("namesilo: TTL should be in [%d, %d]", defaultTTL, maxTTL) } - if config.APIKey == "" { - return nil, errors.New("namesilo: credentials missing") + transport, err := namesilo.NewTokenTransport(config.APIKey) + if err != nil { + return nil, fmt.Errorf("namesilo: %w", err) } - client := namesilo.NewClient(config.APIKey) - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{client: client, config: config}, nil + return &DNSProvider{client: namesilo.NewClient(transport.Client()), config: config}, nil } // Present creates a TXT record to fulfill the dns-01 challenge. @@ -108,7 +103,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("namesilo: %w", err) } - _, err = d.client.DnsAddRecord(context.Background(), &namesilo.DnsAddRecordParams{ + _, err = d.client.DnsAddRecord(&namesilo.DnsAddRecordParams{ Domain: zoneName, Type: "TXT", Host: subdomain, @@ -118,14 +113,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namesilo: failed to add record %w", err) } - return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -135,7 +127,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { zoneName := dns01.UnFqdn(zone) - resp, err := d.client.DnsListRecords(ctx, &namesilo.DnsListRecordsParams{Domain: zoneName}) + resp, err := d.client.DnsListRecords(&namesilo.DnsListRecordsParams{Domain: zoneName}) if err != nil { return fmt.Errorf("namesilo: %w", err) } @@ -147,7 +139,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { for _, r := range resp.Reply.ResourceRecord { if r.Type == "TXT" && r.Value == info.Value && (r.Host == subdomain || r.Host == dns01.UnFqdn(info.EffectiveFQDN)) { - _, err := d.client.DnsDeleteRecord(ctx, &namesilo.DnsDeleteRecordParams{Domain: zoneName, ID: r.RecordID}) + _, err := d.client.DnsDeleteRecord(&namesilo.DnsDeleteRecordParams{Domain: zoneName, ID: r.RecordID}) if err != nil { return fmt.Errorf("namesilo: %w", err) } 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/namesilo/namesilo_test.go b/providers/dns/namesilo/namesilo_test.go index 09eacd035..4b01d7388 100644 --- a/providers/dns/namesilo/namesilo_test.go +++ b/providers/dns/namesilo/namesilo_test.go @@ -45,7 +45,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -78,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { { desc: "missing API key", ttl: defaultTTL, - expected: "namesilo: credentials missing", + expected: "namesilo: credentials missing: API key", }, { desc: "unavailable TTL", @@ -113,7 +112,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/nearlyfreespeech/internal/client.go index 5d7e79fbe..fcfe4e9b7 100644 --- a/providers/dns/nearlyfreespeech/internal/client.go +++ b/providers/dns/nearlyfreespeech/internal/client.go @@ -97,7 +97,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} - err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) @@ -119,6 +118,7 @@ func (c Signer) Sign(uri, body, login, apiKey string) string { // Header is "login;timestamp;salt;hash". // hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash") // and body-hash is SHA1(body). + bodyHash := sha1.Sum([]byte(body)) timestamp := strconv.FormatInt(c.clock().Unix(), 10) diff --git a/providers/dns/nearlyfreespeech/internal/client_test.go b/providers/dns/nearlyfreespeech/internal/client_test.go index 26e4552be..1445286c3 100644 --- a/providers/dns/nearlyfreespeech/internal/client_test.go +++ b/providers/dns/nearlyfreespeech/internal/client_test.go @@ -163,7 +163,6 @@ func TestSigner_Sign(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { t.Parallel() - signer := NewSigner() signer.saltShaker = func() []byte { return []byte(test.salt) } signer.clock = func() time.Time { return time.Unix(test.now, 0) } diff --git a/providers/dns/nearlyfreespeech/nearlyfreespeech.go b/providers/dns/nearlyfreespeech/nearlyfreespeech.go index af5e5363c..464ac35d0 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech.go +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech/internal" ) @@ -93,8 +92,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/nearlyfreespeech/nearlyfreespeech_test.go b/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go index b67b350e9..adc7efe1e 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go @@ -54,7 +54,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -127,7 +126,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -141,7 +139,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/netcup/internal/client.go index 1287a8d7a..553733175 100644 --- a/providers/dns/netcup/internal/client.go +++ b/providers/dns/netcup/internal/client.go @@ -80,7 +80,6 @@ func (c *Client) GetDNSRecords(ctx context.Context, hostname string) ([]DNSRecor } var responseData InfoDNSRecordsResponse - err := c.doRequest(ctx, payload, &responseData) if err != nil { return nil, fmt.Errorf("error when sending the request: %w", err) @@ -140,7 +139,6 @@ func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) { return index, nil } } - return -1, errors.New("no DNS Record found") } @@ -175,7 +173,6 @@ func unmarshalResponseMsg(req *http.Request, resp *http.Response) (*ResponseMsg, } var respMsg ResponseMsg - err = json.Unmarshal(raw, &respMsg) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/netcup/internal/client_live_test.go b/providers/dns/netcup/internal/client_live_test.go index 68621882e..3cf6c8c0b 100644 --- a/providers/dns/netcup/internal/client_live_test.go +++ b/providers/dns/netcup/internal/client_live_test.go @@ -38,7 +38,7 @@ func TestClient_GetDNSRecords_Live(t *testing.T) { info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NoError(t, err) + require.NoError(t, err, "error finding DNSZone") zone = dns01.UnFqdn(zone) @@ -103,7 +103,7 @@ func TestClient_UpdateDNSRecord_Live(t *testing.T) { // Tear down err = client.UpdateDNSRecord(ctx, envTest.GetDomain(), []DNSRecord{records[recordIdx]}) - require.NoError(t, err) + require.NoError(t, err, "Did not remove record! Please do so yourself.") err = client.Logout(ctx) require.NoError(t, err) diff --git a/providers/dns/netcup/internal/session.go b/providers/dns/netcup/internal/session.go index b53751edf..6627d74e1 100644 --- a/providers/dns/netcup/internal/session.go +++ b/providers/dns/netcup/internal/session.go @@ -24,7 +24,6 @@ func (c *Client) login(ctx context.Context) (string, error) { } var responseData LoginResponse - err := c.doRequest(ctx, payload, &responseData) if err != nil { return "", fmt.Errorf("loging error: %w", err) diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go index 13b329e07..f0544bbcd 100644 --- a/providers/dns/netcup/netcup.go +++ b/providers/dns/netcup/netcup.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/netcup/internal" ) @@ -93,11 +92,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("netcup: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + client.HTTPClient = config.HTTPClient return &DNSProvider{client: client, config: config}, nil } 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/netcup/netcup_test.go b/providers/dns/netcup/netcup_test.go index fedc56ba9..f9cc43ab9 100644 --- a/providers/dns/netcup/netcup_test.go +++ b/providers/dns/netcup/netcup_test.go @@ -72,7 +72,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -159,14 +158,13 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() - p, err := NewDNSProvider() require.NoError(t, err) info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NoError(t, err) + require.NoError(t, err, "error finding DNSZone") zone = dns01.UnFqdn(zone) @@ -183,7 +181,7 @@ func TestLivePresentAndCleanup(t *testing.T) { require.NoError(t, err) err = p.CleanUp(test, "987d", "123d==") - require.NoError(t, err) + require.NoError(t, err, "Did not clean up! Please remove record yourself.") }) } } diff --git a/providers/dns/netlify/internal/client.go b/providers/dns/netlify/internal/client.go index 3b6b681fe..a8e3b35c3 100644 --- a/providers/dns/netlify/internal/client.go +++ b/providers/dns/netlify/internal/client.go @@ -59,7 +59,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]DNSRecord, er } var records []DNSRecord - err = json.Unmarshal(raw, &records) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -94,7 +93,6 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record DNSReco } var recordResp DNSRecord - err = json.Unmarshal(raw, &recordResp) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/netlify/netlify.go b/providers/dns/netlify/netlify.go index 5b2980d24..1d4c78f4f 100644 --- a/providers/dns/netlify/netlify.go +++ b/providers/dns/netlify/netlify.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/netlify/internal" ) @@ -85,11 +84,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("netlify: incomplete credentials, missing token") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), - ), - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token)) return &DNSProvider{ config: config, @@ -149,7 +144,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("netlify: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/netlify/netlify.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/netlify/netlify_test.go b/providers/dns/netlify/netlify_test.go index 1e84517be..f351802da 100644 --- a/providers/dns/netlify/netlify_test.go +++ b/providers/dns/netlify/netlify_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,7 +93,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,7 +106,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nicmanager/internal/client.go b/providers/dns/nicmanager/internal/client.go index 16bfe497b..eb84e29ec 100644 --- a/providers/dns/nicmanager/internal/client.go +++ b/providers/dns/nicmanager/internal/client.go @@ -83,7 +83,6 @@ func (c *Client) GetZone(ctx context.Context, name string) (*Zone, error) { } var zone Zone - err = c.do(req, http.StatusOK, &zone) if err != nil { return nil, err diff --git a/providers/dns/nicmanager/nicmanager.go b/providers/dns/nicmanager/nicmanager.go index 9b27df64e..2a5742373 100644 --- a/providers/dns/nicmanager/nicmanager.go +++ b/providers/dns/nicmanager/nicmanager.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nicmanager/internal" ) @@ -129,8 +128,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } @@ -191,11 +188,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { name := dns01.UnFqdn(info.EffectiveFQDN) - var ( - existingRecord internal.Record - existingRecordFound bool - ) - + var existingRecord internal.Record + var existingRecordFound bool for _, record := range zone.Records { if strings.EqualFold(record.Type, "TXT") && strings.EqualFold(record.Name, name) && record.Content == info.Value { existingRecord = record diff --git a/providers/dns/nicmanager/nicmanager.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/nicmanager/nicmanager_test.go b/providers/dns/nicmanager/nicmanager_test.go index 114cdb7ca..bc2f50cc3 100644 --- a/providers/dns/nicmanager/nicmanager_test.go +++ b/providers/dns/nicmanager/nicmanager_test.go @@ -66,7 +66,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -160,7 +159,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -174,7 +172,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nicru/internal/client.go b/providers/dns/nicru/internal/client.go index 5d851fc76..37acd68f1 100644 --- a/providers/dns/nicru/internal/client.go +++ b/providers/dns/nicru/internal/client.go @@ -30,7 +30,6 @@ func (tr Trimmer) Token() (xml.Token, error) { if cd, ok := t.(xml.CharData); ok { t = xml.CharData(bytes.TrimSpace(cd)) } - return t, err } diff --git a/providers/dns/nicru/nicru.go b/providers/dns/nicru/nicru.go index cf4255bdb..9320f94c2 100644 --- a/providers/dns/nicru/nicru.go +++ b/providers/dns/nicru/nicru.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nicru/internal" ) @@ -91,7 +90,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("nicru: %w", err) } - client, err := internal.NewClient(clientdebug.Wrap(oauthClient)) + client, err := internal.NewClient(oauthClient) if err != nil { return nil, fmt.Errorf("nicru: unable to build API client: %w", err) } 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/nicru/nicru_test.go b/providers/dns/nicru/nicru_test.go index 7e71f9d2c..12db3a4ff 100644 --- a/providers/dns/nicru/nicru_test.go +++ b/providers/dns/nicru/nicru_test.go @@ -75,7 +75,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -172,7 +171,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -186,7 +184,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nifcloud/internal/client.go b/providers/dns/nifcloud/internal/client.go index 0f3851883..4469a1f78 100644 --- a/providers/dns/nifcloud/internal/client.go +++ b/providers/dns/nifcloud/internal/client.go @@ -59,7 +59,6 @@ func (c *Client) ChangeResourceRecordSets(ctx context.Context, hostedZoneID stri } output := &ChangeResourceRecordSetsResponse{} - err = c.do(req, output) if err != nil { return nil, err @@ -78,7 +77,6 @@ func (c *Client) GetChange(ctx context.Context, statusID string) (*GetChangeResp } output := &GetChangeResponse{} - err = c.do(req, output) if err != nil { return nil, err @@ -131,7 +129,6 @@ func (c *Client) sign(req *http.Request) error { } mac := hmac.New(sha1.New, []byte(c.secretKey)) - _, err := mac.Write([]byte(req.Header.Get("Date"))) if err != nil { return err @@ -151,7 +148,6 @@ func newXMLRequest(ctx context.Context, method string, endpoint *url.URL, payloa if payload != nil { body.WriteString(xml.Header) - err := xml.NewEncoder(body).Encode(payload) if err != nil { return nil, fmt.Errorf("failed to create request XML body: %w", err) @@ -174,7 +170,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &ErrorResponse{} - err := xml.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index ced7eff09..e73333c52 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -9,12 +9,10 @@ import ( "net/url" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nifcloud/internal" ) @@ -95,8 +93,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.BaseURL != "" { baseURL, err := url.Parse(config.BaseURL) if err != nil { @@ -111,29 +107,23 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.changeRecord(ctx, "CREATE", info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.changeRecord("CREATE", info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("nifcloud: %w", err) } - return err } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.changeRecord(ctx, "DELETE", info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.changeRecord("DELETE", info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("nifcloud: %w", err) } - return err } @@ -143,7 +133,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) changeRecord(ctx context.Context, action, fqdn, value string, ttl int) error { +func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { authZone, err := dns01.FindZoneByFqdn(fqdn) if err != nil { return fmt.Errorf("could not find zone: %w", err) @@ -180,6 +170,8 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action, fqdn, value stri }, } + ctx := context.Background() + resp, err := d.client.ChangeResourceRecordSets(ctx, dns01.UnFqdn(authZone), reqParams) if err != nil { return fmt.Errorf("failed to change record set: %w", err) @@ -187,20 +179,11 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action, fqdn, value stri statusID := resp.ChangeInfo.ID - return wait.Retry(ctx, - func() error { - resp, err := d.client.GetChange(ctx, statusID) - if err != nil { - return fmt.Errorf("get change: %w", err) - } - - if resp.ChangeInfo.Status != "INSYNC" { - return fmt.Errorf("change status: %s", resp.ChangeInfo.Status) - } - - return nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(4*time.Second)), - backoff.WithMaxElapsedTime(120*time.Second), - ) + return wait.For("nifcloud", 120*time.Second, 4*time.Second, func() (bool, error) { + resp, err := d.client.GetChange(ctx, statusID) + if err != nil { + return false, fmt.Errorf("failed to query change status: %w", err) + } + return resp.ChangeInfo.Status == "INSYNC", nil + }) } 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/nifcloud/nifcloud_test.go b/providers/dns/nifcloud/nifcloud_test.go index 0eff98a71..9b635edfc 100644 --- a/providers/dns/nifcloud/nifcloud_test.go +++ b/providers/dns/nifcloud/nifcloud_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,7 +129,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,7 +142,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/njalla/internal/client.go b/providers/dns/njalla/internal/client.go index d2893253f..f64db3c80 100644 --- a/providers/dns/njalla/internal/client.go +++ b/providers/dns/njalla/internal/client.go @@ -46,7 +46,6 @@ func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) } var result APIResponse[*Record] - err = c.do(req, &result) if err != nil { return nil, err @@ -93,7 +92,6 @@ func (c *Client) ListRecords(ctx context.Context, domain string) ([]Record, erro } var result APIResponse[Records] - err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/njalla/njalla.go b/providers/dns/njalla/njalla.go index 2f9aef8ea..b08ce69de 100644 --- a/providers/dns/njalla/njalla.go +++ b/providers/dns/njalla/njalla.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/njalla/internal" "github.com/miekg/dns" ) @@ -91,8 +90,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -148,7 +145,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("njalla: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/njalla/njalla.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/njalla/njalla_test.go b/providers/dns/njalla/njalla_test.go index 61f106d75..f1489257b 100644 --- a/providers/dns/njalla/njalla_test.go +++ b/providers/dns/njalla/njalla_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,7 +95,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,7 +108,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nodion/nodion.go b/providers/dns/nodion/nodion.go index 4bc887568..1fdc8b87d 100644 --- a/providers/dns/nodion/nodion.go +++ b/providers/dns/nodion/nodion.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/nodion" ) @@ -94,8 +93,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -172,7 +169,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.zoneIDsMu.Lock() zoneID, ok := d.zoneIDs[token] d.zoneIDsMu.Unlock() - if !ok { return fmt.Errorf("nodion: unknown zone ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -208,9 +204,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/nodion/nodion_test.go b/providers/dns/nodion/nodion_test.go index 0ec5c1627..fbf4b89eb 100644 --- a/providers/dns/nodion/nodion_test.go +++ b/providers/dns/nodion/nodion_test.go @@ -34,7 +34,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,7 +91,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,7 +104,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ns1/ns1.go b/providers/dns/ns1/ns1.go index 6a7846e85..c3bf168cb 100644 --- a/providers/dns/ns1/ns1.go +++ b/providers/dns/ns1/ns1.go @@ -11,7 +11,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) @@ -81,12 +80,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("ns1: credentials missing") } - if config.HTTPClient == nil { - // Because the rest.NewClient uses the http.DefaultClient. - config.HTTPClient = &http.Client{Timeout: 10 * time.Second} - } - - client := rest.NewClient(clientdebug.Wrap(config.HTTPClient), rest.SetAPIKey(config.APIKey)) + client := rest.NewClient(config.HTTPClient, rest.SetAPIKey(config.APIKey)) return &DNSProvider{client: client, config: config}, nil } @@ -147,12 +141,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } name := dns01.UnFqdn(info.EffectiveFQDN) - _, err = d.client.Records.Delete(zone.Zone, name, "TXT") if err != nil { return fmt.Errorf("ns1: failed to delete record [zone: %q, domain: %q]: %w", zone.Zone, name, err) } - return nil } diff --git a/providers/dns/ns1/ns1.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/ns1/ns1_test.go b/providers/dns/ns1/ns1_test.go index 82fa70c52..6df6b4afb 100644 --- a/providers/dns/ns1/ns1_test.go +++ b/providers/dns/ns1/ns1_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,7 +96,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -111,7 +109,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/octenium/fixtures/add_dns_record.json b/providers/dns/octenium/fixtures/add_dns_record.json deleted file mode 100644 index 25edcdf11..000000000 --- a/providers/dns/octenium/fixtures/add_dns_record.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "record": { - "type": "TXT", - "name": "_acme-challenge.example.com.", - "ttl": 120, - "value": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI", - "raw": { - "txtdata": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI" - } - } - } -} diff --git a/providers/dns/octenium/fixtures/delete_dns_record.json b/providers/dns/octenium/fixtures/delete_dns_record.json deleted file mode 100644 index 2aa9415cc..000000000 --- a/providers/dns/octenium/fixtures/delete_dns_record.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "deleted": { - "count": 1, - "lines": [ - 123 - ] - } - } -} diff --git a/providers/dns/octenium/fixtures/list_dns_records.json b/providers/dns/octenium/fixtures/list_dns_records.json deleted file mode 100644 index 405afff11..000000000 --- a/providers/dns/octenium/fixtures/list_dns_records.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "records": [ - { - "line": 31, - "type": "TXT", - "name": "_dmarc.example.com.", - "ttl": 300, - "value": "xxx", - "raw": { - "txtdata": "xxx" - } - }, - { - "line": 123, - "type": "TXT", - "name": "_acme-challenge.example.com.", - "ttl": 300, - "value": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI", - "raw": { - "txtdata": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI" - } - } - ] - } -} diff --git a/providers/dns/octenium/fixtures/list_domains.json b/providers/dns/octenium/fixtures/list_domains.json deleted file mode 100644 index a62febcda..000000000 --- a/providers/dns/octenium/fixtures/list_domains.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "domains": { - "2976": { - "domain-name": "example.com", - "registration-date": "21\/08\/2025", - "expiration-date": "-", - "status": "active" - } - } - } -} diff --git a/providers/dns/octenium/internal/client.go b/providers/dns/octenium/internal/client.go deleted file mode 100644 index 474770aeb..000000000 --- a/providers/dns/octenium/internal/client.go +++ /dev/null @@ -1,204 +0,0 @@ -package internal - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - querystring "github.com/google/go-querystring/query" -) - -const defaultBaseURL = "https://api.panel.octenium.com/" - -const statusSuccess = "success" - -// Client the Octenium API client. -type Client struct { - apiKey string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(apiKey string) (*Client, error) { - if apiKey == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiKey: apiKey, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -// ListDomains retrieves a list of domains. -// https://octenium.com/api#tag/Domains/operation/listdomains -func (c *Client) ListDomains(ctx context.Context, domain string) (map[string]Domain, error) { - endpoint := c.BaseURL.JoinPath("domains") - - data := endpoint.Query() - data.Set("domain-name", domain) - endpoint.RawQuery = data.Encode() - - req, err := newRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - result := &DomainsResponse{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result.Domains, nil -} - -// ListDNSRecords retrieves a list of DNS records. -// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-list -func (c *Client) ListDNSRecords(ctx context.Context, orderID, recordType string) ([]Record, error) { - endpoint := c.BaseURL.JoinPath("domains", "dns-records", "list") - - data := make(url.Values) - data.Set("order-id", orderID) - data.Set("types[]", recordType) - - req, err := newRequest(ctx, http.MethodPost, endpoint, data) - if err != nil { - return nil, err - } - - result := &ListRecordsResponse{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result.Records, nil -} - -// AddDNSRecord adds a DNS record. -// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-add -func (c *Client) AddDNSRecord(ctx context.Context, orderID string, record Record) (*Record, error) { - endpoint := c.BaseURL.JoinPath("domains", "dns-records", "add") - - data, err := querystring.Values(record) - if err != nil { - return nil, err - } - - data.Set("order-id", orderID) - - req, err := newRequest(ctx, http.MethodPost, endpoint, data) - if err != nil { - return nil, err - } - - result := &AddRecordResponse{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result.Record, nil -} - -// DeleteDNSRecord deletes a DNS record. -// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-delete -func (c *Client) DeleteDNSRecord(ctx context.Context, orderID string, recordID int) (*DeletedRecordInfo, error) { - endpoint := c.BaseURL.JoinPath("domains", "dns-records", "delete") - - data := make(url.Values) - data.Set("order-id", orderID) - data.Set("line", strconv.Itoa(recordID)) - - req, err := newRequest(ctx, http.MethodPost, endpoint, data) - if err != nil { - return nil, err - } - - result := &DeleteRecordResponse{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result.Deleted, nil -} - -func (c *Client) do(req *http.Request, result any) error { - req.Header.Set("X-Api-Key", c.apiKey) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - raw, _ := io.ReadAll(resp.Body) - - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - var response APIResponse - - err = json.Unmarshal(raw, &response) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - if response.Status != statusSuccess { - return fmt.Errorf("unexpected status: %s: %s", response.Status, response.Error) - } - - err = json.Unmarshal(response.Response, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, response.Response, err) - } - - return nil -} - -func newRequest(ctx context.Context, method string, endpoint *url.URL, payload url.Values) (*http.Request, error) { - var body io.Reader = http.NoBody - - if method == http.MethodPost && payload != nil { - body = strings.NewReader(payload.Encode()) - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if method == http.MethodPost && payload != nil { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - } - - return req, nil -} diff --git a/providers/dns/octenium/internal/client_test.go b/providers/dns/octenium/internal/client_test.go deleted file mode 100644 index ff1b21961..000000000 --- a/providers/dns/octenium/internal/client_test.go +++ /dev/null @@ -1,224 +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(). - WithAccept("application/json"). - With("X-Api-Key", "secret"), - ) -} - -func TestClient_ListDomains(t *testing.T) { - client := mockBuilder(). - Route("GET /domains", - servermock.ResponseFromFixture("list_domains.json"), - servermock.CheckQueryParameter().Strict(). - With("domain-name", "example.com")). - Build(t) - - domains, err := client.ListDomains(t.Context(), "example.com") - require.NoError(t, err) - - expected := map[string]Domain{ - "2976": {DomainName: "example.com", RegistrationDate: "12/09/2021", ExpirationDate: "12/09/2024", Status: "active"}, - "2977": {DomainName: "example.org", RegistrationDate: "01/10/2021", ExpirationDate: "01/10/2024", Status: "active"}, - "2978": {DomainName: "example.net", RegistrationDate: "21/08/2025", ExpirationDate: "-", Status: "active"}, - } - - assert.Equal(t, expected, domains) -} - -func TestClient_ListDomains_error(t *testing.T) { - client := mockBuilder(). - Route("GET /domains", - servermock.Noop().WithStatusCode(http.StatusBadRequest)). - Build(t) - - _, err := client.ListDomains(t.Context(), "example.com") - require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") -} - -func TestClient_ListDomains_api_error(t *testing.T) { - client := mockBuilder(). - Route("GET /domains", - servermock.ResponseFromFixture("error.json")). - Build(t) - - _, err := client.ListDomains(t.Context(), "example.com") - require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") -} - -func TestClient_ListDNSRecords(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/list", - servermock.ResponseFromFixture("list_dns_records.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "abc"). - With("types[]", "TXT")). - Build(t) - - records, err := client.ListDNSRecords(t.Context(), "abc", "TXT") - require.NoError(t, err) - - expected := []Record{ - {ID: 15, Type: "A", Name: "example.com.", TTL: 14400, Value: "203.0.113.10"}, - {ID: 22, Type: "MX", Name: "example.com.", TTL: 14400, Value: "10 mail.example.com."}, - {ID: 31, Type: "TXT", Name: "_dmarc.example.com.", TTL: 300, Value: "v=DMARC1; p=none; rua=mailto:dmarc@example.com"}, - } - - assert.Equal(t, expected, records) -} - -func TestClient_ListDNSRecords_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/list", - servermock.Noop().WithStatusCode(http.StatusBadRequest)). - Build(t) - - _, err := client.ListDNSRecords(t.Context(), "abc", "TXT") - require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") -} - -func TestClient_ListDNSRecords_api_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/list", - servermock.ResponseFromFixture("error.json")). - Build(t) - - _, err := client.ListDNSRecords(t.Context(), "abc", "TXT") - require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") -} - -func TestClient_AddDNSRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/add", - servermock.ResponseFromFixture("add_dns_record.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "abc"). - With("name", "example.com."). - With("ttl", "120"). - With("type", "TXT"). - With("value", "txtTXTtxt")). - Build(t) - - record := Record{ - Type: "TXT", - Name: "example.com.", - TTL: 120, - Value: "txtTXTtxt", - } - - result, err := client.AddDNSRecord(t.Context(), "abc", record) - require.NoError(t, err) - - expected := &Record{ - Type: "A", - Name: "example.com.", - TTL: 14400, - Value: "203.0.113.10", - } - - assert.Equal(t, expected, result) -} - -func TestClient_AddDNSRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/add", - servermock.Noop().WithStatusCode(http.StatusBadRequest)). - Build(t) - - record := Record{ - Type: "TXT", - Name: "example.com.", - TTL: 120, - Value: "txtTXTtxt", - } - - _, err := client.AddDNSRecord(t.Context(), "abc", record) - require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") -} - -func TestClient_AddDNSRecord_api_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/add", - servermock.ResponseFromFixture("error.json")). - Build(t) - - record := Record{ - Type: "TXT", - Name: "example.com.", - TTL: 120, - Value: "txtTXTtxt", - } - - _, err := client.AddDNSRecord(t.Context(), "abc", record) - require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") -} - -func TestClient_DeleteDNSRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/delete", - servermock.ResponseFromFixture("delete_dns_record.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "abc"). - With("line", "123")). - Build(t) - - result, err := client.DeleteDNSRecord(t.Context(), "abc", 123) - require.NoError(t, err) - - expected := &DeletedRecordInfo{ - Count: 1, - Lines: []int{15}, - } - - assert.Equal(t, expected, result) -} - -func TestClient_DeleteDNSRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/delete", - servermock.Noop().WithStatusCode(http.StatusBadRequest)). - Build(t) - - _, err := client.DeleteDNSRecord(t.Context(), "abc", 123) - require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") -} - -func TestClient_DeleteDNSRecord_api_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/dns-records/delete", - servermock.ResponseFromFixture("error.json")). - Build(t) - - _, err := client.DeleteDNSRecord(t.Context(), "abc", 123) - require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") -} diff --git a/providers/dns/octenium/internal/fixtures/add_dns_record.json b/providers/dns/octenium/internal/fixtures/add_dns_record.json deleted file mode 100644 index 6c73ea1f9..000000000 --- a/providers/dns/octenium/internal/fixtures/add_dns_record.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "record": { - "type": "A", - "name": "example.com.", - "ttl": 14400, - "value": "203.0.113.10", - "raw": { - "address": "203.0.113.10" - } - } - } -} diff --git a/providers/dns/octenium/internal/fixtures/delete_dns_record.json b/providers/dns/octenium/internal/fixtures/delete_dns_record.json deleted file mode 100644 index 0d4692ffd..000000000 --- a/providers/dns/octenium/internal/fixtures/delete_dns_record.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "deleted": { - "count": 1, - "lines": [ - 15 - ] - } - } -} diff --git a/providers/dns/octenium/internal/fixtures/error.json b/providers/dns/octenium/internal/fixtures/error.json deleted file mode 100644 index 85a90e425..000000000 --- a/providers/dns/octenium/internal/fixtures/error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "api-status": "error", - "api-response": [], - "api-error": "missing required fields (type, name, ttl)" -} diff --git a/providers/dns/octenium/internal/fixtures/list_dns_records.json b/providers/dns/octenium/internal/fixtures/list_dns_records.json deleted file mode 100644 index 8fa60d86f..000000000 --- a/providers/dns/octenium/internal/fixtures/list_dns_records.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "records": [ - { - "line": 15, - "type": "A", - "name": "example.com.", - "ttl": 14400, - "value": "203.0.113.10", - "raw": { - "address": "203.0.113.10" - } - }, - { - "line": 22, - "type": "MX", - "name": "example.com.", - "ttl": 14400, - "value": "10 mail.example.com.", - "raw": { - "preference": 10, - "exchange": "mail.example.com." - } - }, - { - "line": 31, - "type": "TXT", - "name": "_dmarc.example.com.", - "ttl": 300, - "value": "v=DMARC1; p=none; rua=mailto:dmarc@example.com", - "raw": { - "txtdata": "v=DMARC1; p=none; rua=mailto:dmarc@example.com" - } - } - ] - } -} diff --git a/providers/dns/octenium/internal/fixtures/list_domains.json b/providers/dns/octenium/internal/fixtures/list_domains.json deleted file mode 100644 index b10b705c9..000000000 --- a/providers/dns/octenium/internal/fixtures/list_domains.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "api-status": "success", - "api-response": { - "domains": { - "2976": { - "domain-name": "example.com", - "registration-date": "12/09/2021", - "expiration-date": "12/09/2024", - "status": "active" - }, - "2977": { - "domain-name": "example.org", - "registration-date": "01/10/2021", - "expiration-date": "01/10/2024", - "status": "active" - }, - "2978": { - "domain-name": "example.net", - "registration-date": "21\/08\/2025", - "expiration-date": "-", - "status": "active" - } - } - } -} diff --git a/providers/dns/octenium/internal/types.go b/providers/dns/octenium/internal/types.go deleted file mode 100644 index a31e40921..000000000 --- a/providers/dns/octenium/internal/types.go +++ /dev/null @@ -1,45 +0,0 @@ -package internal - -import "encoding/json" - -type APIResponse struct { - Status string `json:"api-status,omitempty"` - Response json.RawMessage `json:"api-response,omitempty"` - Error string `json:"api-error,omitempty"` -} - -type Domain struct { - DomainName string `json:"domain-name,omitempty"` - RegistrationDate string `json:"registration-date,omitempty"` - ExpirationDate string `json:"expiration-date,omitempty"` - Status string `json:"status,omitempty"` -} - -type Record struct { - ID int `json:"line,omitempty" url:"-"` - Type string `json:"type,omitempty" url:"type,omitempty"` - Name string `json:"name,omitempty" url:"name,omitempty"` - TTL int `json:"ttl,omitempty" url:"ttl,omitempty"` - Value string `json:"value,omitempty" url:"value,omitempty"` -} - -type DomainsResponse struct { - Domains map[string]Domain `json:"domains,omitempty"` -} - -type AddRecordResponse struct { - Record *Record `json:"record,omitempty"` -} - -type ListRecordsResponse struct { - Records []Record `json:"records,omitempty"` -} - -type DeleteRecordResponse struct { - Deleted *DeletedRecordInfo `json:"deleted,omitempty"` -} - -type DeletedRecordInfo struct { - Count int `json:"count,omitempty"` - Lines []int `json:"lines,omitempty"` -} diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go deleted file mode 100644 index 6032dcce1..000000000 --- a/providers/dns/octenium/octenium.go +++ /dev/null @@ -1,204 +0,0 @@ -// Package octenium implements a DNS provider for solving the DNS-01 challenge using Octenium. -package octenium - -import ( - "context" - "errors" - "fmt" - "net/http" - "sync" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/log" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/octenium/internal" - "github.com/hashicorp/go-retryablehttp" -) - -// Environment variables names. -const ( - envNamespace = "OCTENIUM_" - - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client - - domainIDs map[string]string - domainIDsMu sync.Mutex -} - -// NewDNSProvider returns a DNSProvider instance configured for Octenium. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("octenium: %w", err) - } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for Octenium. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("octenium: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIKey) - if err != nil { - return nil, fmt.Errorf("octenium: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - retryClient := retryablehttp.NewClient() - retryClient.RetryMax = 5 - retryClient.HTTPClient = client.HTTPClient - retryClient.Logger = log.Logger - - client.HTTPClient = clientdebug.Wrap(retryClient.StandardClient()) - - return &DNSProvider{ - config: config, - client: client, - domainIDs: make(map[string]string), - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("octenium: could not find zone for domain '%s': %w", domain, err) - } - - domainID, err := d.getDomainID(ctx, authZone) - if err != nil { - return fmt.Errorf("octenium: get domain ID: %w", err) - } - - d.domainIDsMu.Lock() - d.domainIDs[token] = domainID - d.domainIDsMu.Unlock() - - record := internal.Record{ - Type: "TXT", - Name: info.EffectiveFQDN, - TTL: d.config.TTL, - Value: info.Value, - } - - _, err = d.client.AddDNSRecord(ctx, domainID, record) - if err != nil { - return fmt.Errorf("octenium: add record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - d.domainIDsMu.Lock() - domainID, ok := d.domainIDs[token] - d.domainIDsMu.Unlock() - - if !ok { - return fmt.Errorf("octenium: unknown domain ID for '%s'", info.EffectiveFQDN) - } - - records, err := d.client.ListDNSRecords(ctx, domainID, "TXT") - if err != nil { - return fmt.Errorf("octenium: list records: %w", err) - } - - for _, record := range records { - if record.Type != "TXT" || record.Name != info.EffectiveFQDN || record.Value != info.Value { - continue - } - - _, err = d.client.DeleteDNSRecord(ctx, domainID, record.ID) - if err != nil { - return fmt.Errorf("octenium: delete record: %w", err) - } - - break - } - - d.domainIDsMu.Lock() - delete(d.domainIDs, token) - d.domainIDsMu.Unlock() - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func (d *DNSProvider) getDomainID(ctx context.Context, authZone string) (string, error) { - domains, err := d.client.ListDomains(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return "", fmt.Errorf("list domains: %w", err) - } - - if len(domains) == 0 { - return "", errors.New("domain not found") - } - - if len(domains) > 1 { - return "", errors.New("multiple domains found") - } - - for id := range domains { - return id, nil - } - - return "", errors.New("domain ID not found") -} diff --git a/providers/dns/octenium/octenium.toml b/providers/dns/octenium/octenium.toml deleted file mode 100644 index e3c9d894f..000000000 --- a/providers/dns/octenium/octenium.toml +++ /dev/null @@ -1,22 +0,0 @@ -Name = "Octenium" -Description = '''''' -URL = "https://octenium.com/" -Code = "octenium" -Since = "v4.27.0" - -Example = ''' -OCTENIUM_API_KEY="xxx" \ -lego --dns octenium -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - OCTENIUM_API_KEY = "API key" - [Configuration.Additional] - OCTENIUM_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - OCTENIUM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - OCTENIUM_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - OCTENIUM_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://octenium.com/api#tag/Domains-DNS" diff --git a/providers/dns/octenium/octenium_test.go b/providers/dns/octenium/octenium_test.go deleted file mode 100644 index dbb8d64b3..000000000 --- a/providers/dns/octenium/octenium_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package octenium - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIKey: "secret", - }, - }, - { - desc: "missing API key", - envVars: map[string]string{ - EnvAPIKey: "", - }, - expected: "octenium: some credentials information are missing: OCTENIUM_API_KEY", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "octenium: some credentials information are missing: OCTENIUM_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiKey string - expected string - }{ - { - desc: "success", - apiKey: "secret", - }, - { - desc: "missing API key", - expected: "octenium: credentials missing", - }, - { - desc: "missing credentials", - expected: "octenium: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIKey = "secret" - 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(). - WithAccept("application/json"). - With("X-Api-Key", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /domains", - servermock.ResponseFromFixture("list_domains.json"), - servermock.CheckQueryParameter().Strict(). - With("domain-name", "example.com")). - Route("POST /domains/dns-records/add", - servermock.ResponseFromFixture("add_dns_record.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "2976"). - With("name", "_acme-challenge.example.com."). - With("ttl", "120"). - With("type", "TXT"). - With("value", "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI")). - Build(t) - - err := provider.Present("example.com", "", "foobar") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("POST /domains/dns-records/list", - servermock.ResponseFromFixture("list_dns_records.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "2976"). - With("types[]", "TXT")). - Route("POST /domains/dns-records/delete", - servermock.ResponseFromFixture("delete_dns_record.json"), - servermock.CheckHeader(). - WithContentType("application/x-www-form-urlencoded"), - servermock.CheckForm().Strict(). - With("order-id", "2976"). - With("line", "123")). - Build(t) - - provider.domainIDs["token"] = "2976" - - err := provider.CleanUp("example.com", "token", "foobar") - require.NoError(t, err) -} diff --git a/providers/dns/oraclecloud/configprovider.go b/providers/dns/oraclecloud/configprovider.go new file mode 100644 index 000000000..7a51811a6 --- /dev/null +++ b/providers/dns/oraclecloud/configprovider.go @@ -0,0 +1,97 @@ +package oraclecloud + +import ( + "crypto/rsa" + "encoding/base64" + "errors" + "fmt" + "os" + + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/nrdcg/oci-go-sdk/common/v1065" +) + +type configProvider struct { + values map[string]string + privateKeyPassphrase string +} + +func newConfigProvider(values map[string]string) *configProvider { + return &configProvider{ + values: values, + privateKeyPassphrase: env.GetOrFile(EnvPrivKeyPass), + } +} + +func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { + privateKey, err := getPrivateKey(envPrivKey) + if err != nil { + return nil, err + } + + return common.PrivateKeyFromBytesWithPassword(privateKey, []byte(p.privateKeyPassphrase)) +} + +func (p *configProvider) KeyID() (string, error) { + tenancy, err := p.TenancyOCID() + if err != nil { + return "", err + } + + user, err := p.UserOCID() + if err != nil { + return "", err + } + + fingerprint, err := p.KeyFingerprint() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil +} + +func (p *configProvider) TenancyOCID() (value string, err error) { + return p.values[EnvTenancyOCID], nil +} + +func (p *configProvider) UserOCID() (string, error) { + return p.values[EnvUserOCID], nil +} + +func (p *configProvider) KeyFingerprint() (string, error) { + return p.values[EnvPubKeyFingerprint], nil +} + +func (p *configProvider) Region() (string, error) { + return p.values[EnvRegion], nil +} + +func (p *configProvider) AuthType() (common.AuthConfig, error) { + // Inspired by https://github.com/oracle/oci-go-sdk/blob/e7635c292e60d0a9dcdd3a1e7de180d7c99b1eee/common/configuration.go#L231-L234 + return common.AuthConfig{AuthType: common.UnknownAuthenticationType}, errors.New("unsupported, keep the interface") +} + +func getPrivateKey(envVar string) ([]byte, error) { + envVarValue := os.Getenv(envVar) + if envVarValue != "" { + bytes, err := base64.StdEncoding.DecodeString(envVarValue) + if err != nil { + return nil, fmt.Errorf("failed to read base64 value %s (defined by env var %s): %w", envVarValue, envVar, err) + } + return bytes, nil + } + + fileVar := envVar + "_FILE" + fileVarValue := os.Getenv(fileVar) + if fileVarValue == "" { + return nil, fmt.Errorf("no value provided for: %s or %s", envVar, fileVar) + } + + fileContents, err := os.ReadFile(fileVarValue) + if err != nil { + return nil, fmt.Errorf("failed to read the file %s (defined by env var %s): %w", fileVarValue, fileVar, err) + } + + return fileContents, nil +} diff --git a/providers/dns/oraclecloud/configurationprovider.go b/providers/dns/oraclecloud/configurationprovider.go deleted file mode 100644 index 97710108c..000000000 --- a/providers/dns/oraclecloud/configurationprovider.go +++ /dev/null @@ -1,144 +0,0 @@ -package oraclecloud - -import ( - "crypto/rsa" - "encoding/base64" - "errors" - "fmt" - "os" - "slices" - "strings" - - "github.com/go-acme/lego/v4/log" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/nrdcg/oci-go-sdk/common/v1065" -) - -type environmentConfigurationProvider struct { - values map[string]string -} - -func newEnvironmentConfigurationProvider() (*environmentConfigurationProvider, error) { - values, err := env.GetWithFallback( - []string{EnvRegion, altEnvTFVarRegion}, - []string{EnvUserOCID, altEnvTFVarUserOCID}, - []string{EnvTenancyOCID, altEnvTFVarTenancyOCID}, - []string{EnvPubKeyFingerprint, altEnvFingerprint, altEnvTFVarFingerprint}, - ) - if err != nil { - return nil, err - } - - return &environmentConfigurationProvider{ - values: values, - }, nil -} - -func (p *environmentConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { - privateKey, err := getPrivateKey() - if err != nil { - return nil, err - } - - return common.PrivateKeyFromBytesWithPassword(privateKey, []byte(p.privateKeyPassword())) -} - -func (p *environmentConfigurationProvider) KeyID() (string, error) { - tenancy, err := p.TenancyOCID() - if err != nil { - return "", err - } - - user, err := p.UserOCID() - if err != nil { - return "", err - } - - fingerprint, err := p.KeyFingerprint() - if err != nil { - return "", err - } - - return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil -} - -func (p *environmentConfigurationProvider) TenancyOCID() (string, error) { - return p.values[EnvTenancyOCID], nil -} - -func (p *environmentConfigurationProvider) UserOCID() (string, error) { - return p.values[EnvUserOCID], nil -} - -func (p *environmentConfigurationProvider) KeyFingerprint() (string, error) { - return p.values[EnvPubKeyFingerprint], nil -} - -func (p *environmentConfigurationProvider) Region() (string, error) { - return p.values[EnvRegion], nil -} - -func (p *environmentConfigurationProvider) AuthType() (common.AuthConfig, error) { - // Inspired by https://github.com/oracle/oci-go-sdk/blob/e7635c292e60d0a9dcdd3a1e7de180d7c99b1eee/common/configuration.go#L231-L234 - return common.AuthConfig{AuthType: common.UnknownAuthenticationType}, errors.New("unsupported, keep the interface") -} - -func (p *environmentConfigurationProvider) privateKeyPassword() string { - return env.GetOneWithFallback(EnvPrivKeyPass, "", env.ParseString, altEnvPrivateKeyPassword, altEnvTFVarPrivateKeyPassword) -} - -func getPrivateKey() ([]byte, error) { - base64EnvKeys := []string{envPrivKey, altEnvPrivateKey} - - envVarValue := getEnvWithStrictFallback(base64EnvKeys...) - if envVarValue != "" { - bytes, err := base64.StdEncoding.DecodeString(envVarValue) - if err != nil { - return nil, fmt.Errorf("failed to read base64 value %s (defined by env vars %s): %w", envVarValue, - strings.Join(base64EnvKeys, " or "), err) - } - - return bytes, nil - } - - fileEnvKeys := []string{EnvPrivKeyFile, altEnvPrivateKeyPath, altEnvTFVarPrivateKeyPath} - - fileVarValue := getEnvFileWithStrictFallback(fileEnvKeys...) - if len(fileVarValue) == 0 { - return nil, fmt.Errorf("no value provided for: %s", - strings.Join(slices.Concat(base64EnvKeys, fileEnvKeys), " or "), - ) - } - - return fileVarValue, nil -} - -func getEnvWithStrictFallback(keys ...string) string { - for _, key := range keys { - envVarValue := os.Getenv(key) - if envVarValue != "" { - return envVarValue - } - } - - return "" -} - -func getEnvFileWithStrictFallback(keys ...string) []byte { - for _, key := range keys { - fileVarValue := os.Getenv(key) - if fileVarValue == "" { - continue - } - - fileContents, err := os.ReadFile(fileVarValue) - if err != nil { - log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, key, err) - return nil - } - - return fileContents - } - - return nil -} diff --git a/providers/dns/oraclecloud/fixtures/cert.pem b/providers/dns/oraclecloud/fixtures/cert.pem deleted file mode 100644 index fc1dcfb53..000000000 --- a/providers/dns/oraclecloud/fixtures/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDHzCCAgegAwIBAgIQKIExaCLIXtXecrT1dWGLszANBgkqhkiG9w0BAQsFADAS -MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw -MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAwM4wEPHOGAu8tZNNWx3cH6AMuqKwAmB2RwbA3OK034MzhydOjnDm -igw93eUc4nd3dnICyNpb2rbP9FgGlAuMlJ8raHQkG4DSXF1Bf14neOhLpfBItaX9 -+EB3oO0NupKZhaHrsTKzLGD7bauAPX6PDXuAPp3u5mgGGuZjpLZoKqg3//WImb/2 -xEMVsmvPKTb5FxS/tAMtywjGSUtCTCrudUEh4Gnj6IboVdwYmt539ETDK/Rerxf3 -/GsmEbuOkDUdBixQwLo0U+UAoMOw4zoyQDrrtyUmvffDxI50RAdZDFyFtqZ0ZQa8 -lQqrMdQdf+x1Wb7BKozSktAw4igRP/mknQIDAQABo28wbTAOBgNVHQ8BAf8EBAMC -AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUcetTliVbYxxutNS8JRkotRY4DRkwFgYDVR0RBA8wDYILZXhhbXBsZS5vcmcw -DQYJKoZIhvcNAQELBQADggEBAEJP74/XB+12aGQ+EMERIX2Pn6YaaBLt6rTLqV7A -zFxI9YGIc4xlGa0qkpDhpz6RSypTQG6HN5aZ5b8dz3foMleUVP2cXd8zduc8GQCb -p4/8PpEhSl6dQb5+mg/qyHGUAaDl40VAbTLXHtn98dhacaJc+TKuXVJAgYRU3Sm3 -wFJxULZSnx+aGdE9s2brOGhvz1fVWnhvWzDvJSM+8xDURz8UiEnimTpV6m3CKItz -2GatNjM8ADKC7MHQI4I5v4fEwronN/g3NfPfFSmnOKk+lPSAW42WEvhFol+2VvdX -3p5X2QracSLCIj/DUBebZP9110C8Lj/YfFtOjFokqtQ9Fh4= ------END CERTIFICATE----- diff --git a/providers/dns/oraclecloud/fixtures/key.pem b/providers/dns/oraclecloud/fixtures/key.pem deleted file mode 100644 index 1a56bb5a4..000000000 --- a/providers/dns/oraclecloud/fixtures/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAzjAQ8c4YC7y1 -k01bHdwfoAy6orACYHZHBsDc4rTfgzOHJ06OcOaKDD3d5Rzid3d2cgLI2lvats/0 -WAaUC4yUnytodCQbgNJcXUF/Xid46Eul8Ei1pf34QHeg7Q26kpmFoeuxMrMsYPtt -q4A9fo8Ne4A+ne7maAYa5mOktmgqqDf/9YiZv/bEQxWya88pNvkXFL+0Ay3LCMZJ -S0JMKu51QSHgaePohuhV3Bia3nf0RMMr9F6vF/f8ayYRu46QNR0GLFDAujRT5QCg -w7DjOjJAOuu3JSa998PEjnREB1kMXIW2pnRlBryVCqsx1B1/7HVZvsEqjNKS0DDi -KBE/+aSdAgMBAAECggEAWl2pWJ/ErS9/HIl0NbMKk0YEAUuz/AEzHnoTVdPp22KW -eY+aOZe/7c7sBj7WqWw98SVhmbsCV0HcuNSzDJtXIedyRGw+6icYMVNCGgzKqlgR -8K3snjq1DLBGgYXpq9r/Got4ON6e7LttzIqXufrB2JtcUbzbFmGGDwCRjkcyDl9l -M8ufwD/Xgcd2L8jainU43d2pVxvxUIpRlRdoupCCSlkRYPsXiWlqav7YO4F/Txos -z3gJyzkXzc3WwfNZdQtEMYwBwozO+Dp2p4TUBr0Ta3MbfrKfDoTs4XT/Ce9IwJJS -/h6E9cxZD8t5oMT50quFjwhHBKodMiUqIlh2YQEAbwKBgQDIULzo/tgDgTwveyEn -L9n8yVbEh/SfrE9QtXcjkDB5+tYmIsIaz16NRWlAqnJVGZvcanrCq7ZTxgUcs/hW -Ag+sfWkeg7lmfeJAkiZ6kmi1h2qJjXMOBri+Cm6MTOsE6qdIc3eT4PnYkNpV7o6S -70hWNncVadXLV4Thm9BLAbMbQwKBgQD2ZwKe/2zRQcbuBe1loF0HWIsJPxcKQ3LH -hVf7f0YLQlIuzOhK8TQXgM0G4hxLlk1XeLjgf3z4Ju7hfh2JQLor1QYPRGUj66SX -KTE5eDwE0yEX1c9m5PW6M+f8vkOU4LQ/OtPw5OrKyYxpLf9dp42nmDYY/8IvUk96 -iKZNY1sSnwKBgQC27tS2SxVmjf0yt1WdfdurOQueSzKhJzD/2djFh4Zdvy8WgKOW -7E3C4eKvBXmIMezeq/cUFNBbTPmaLtjZYuSBd74p+c20xb17jnzJby9kqBgpKh4q -bwUDuG8gfZYbVVgTmC9ZwxkoJ5Dc7RETKqZ65R53VcHDA1f82Nitxw2UFQKBgBDl -c2qPvViEGC4OPf8wBfERA0e5Cc1sXpyL6kKWsajn/Va0OmGZNKc/788/Bg2w2tDa -uGK8m0cw9ESGL2RQCfQjgWzelcjmybyL2JJGSmdSSvylbrlxjeAc2xWbvmqhFfsX -/5yPNgJ926ECxHYZnT8W0u7X6urvy/9tC2pXG9GlAoGBAKOAfij4fMbHY+Z1m825 -VhY110FDnePYFJWmExP8GAVqOzhCs0mzyCnYh6nvS/OY8moH2LOuwPUlDfF3IzyT -hTUuXnykWT3w40eYQXXIaXEGhue+guL8ch16vEEJy5ltwEdIPNMTErbqAAk2W6Ps -NB46HzETzEIWnzoamX6iQVWj ------END PRIVATE KEY----- diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index 730b3f212..2fb392e3e 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -11,9 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/oci-go-sdk/common/v1065" - "github.com/nrdcg/oci-go-sdk/common/v1065/auth" "github.com/nrdcg/oci-go-sdk/dns/v1065" ) @@ -21,22 +19,14 @@ import ( const ( envNamespace = "OCI_" - EnvAuthType = envNamespace + "AUTH_TYPE" - - EnvCompartmentOCID = envNamespace + "COMPARTMENT_OCID" - EnvRegion = envNamespace + "REGION" - + EnvCompartmentOCID = envNamespace + "COMPARTMENT_OCID" envPrivKey = envNamespace + "PRIVKEY" EnvPrivKeyFile = envPrivKey + "_FILE" EnvPrivKeyPass = envPrivKey + "_PASS" EnvTenancyOCID = envNamespace + "TENANCY_OCID" EnvUserOCID = envNamespace + "USER_OCID" EnvPubKeyFingerprint = envNamespace + "PUBKEY_FINGERPRINT" - - altEnvPrivateKey = envNamespace + "PRIVATE_KEY" // alias on OCI_PRIVKEY - altEnvPrivateKeyPath = altEnvPrivateKey + "_PATH" // alias on OCI_PRIVKEY_FILE - altEnvPrivateKeyPassword = altEnvPrivateKey + "_PASSWORD" // alias on OCI_PRIVKEY_PASS - altEnvFingerprint = envNamespace + "FINGERPRINT" // alias on OCI_PUBKEY_FINGERPRINT + EnvRegion = envNamespace + "REGION" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -44,25 +34,12 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -// https://github.com/oracle/oci-go-sdk/blob/7f425f74c74fd0c6a5acb74466c85eb5346e0092/common/client.go#L350 -// https://github.com/oracle/oci-go-sdk/blob/7f425f74c74fd0c6a5acb74466c85eb5346e0092/common/configuration.go#L174-L175 -const ( - altEnvTFVarNamespace = "TF_VAR_" - altEnvTFVarRegion = altEnvTFVarNamespace + "region" // alias on OCI_REGION - altEnvTFVarFingerprint = altEnvTFVarNamespace + "fingerprint" // alias on OCI_PUBKEY_FINGERPRINT - altEnvTFVarUserOCID = altEnvTFVarNamespace + "user_ocid" // alias on OCI_USER_OCID - altEnvTFVarTenancyOCID = altEnvTFVarNamespace + "tenancy_ocid" // alias on OCI_TENANCY_OCID - altEnvTFVarPrivateKeyPath = altEnvTFVarNamespace + "private_key_path" // alias on OCI_PRIVKEY_FILE - altEnvTFVarPrivateKeyPassword = altEnvTFVarNamespace + "private_key_password" // alias on OCI_PRIVKEY_PASS -) - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - CompartmentID string - OCIConfigProvider common.ConfigurationProvider - + CompartmentID string + OCIConfigProvider common.ConfigurationProvider PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -89,42 +66,15 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for OracleCloud. func NewDNSProvider() (*DNSProvider, error) { - config := NewDefaultConfig() - - switch env.GetOrFile(EnvAuthType) { - case string(common.InstancePrincipal): - values, err := env.Get(EnvCompartmentOCID) - if err != nil { - return nil, fmt.Errorf("oraclecloud: %w", err) - } - - config.CompartmentID = values[EnvCompartmentOCID] - - region := env.GetOneWithFallback(EnvRegion, "", env.ParseString, altEnvTFVarRegion) - - configurationProvider, err := auth.InstancePrincipalConfigurationProviderForRegion(common.Region(region)) - if err != nil { - return nil, fmt.Errorf("oraclecloud: %w", err) - } - - config.OCIConfigProvider = configurationProvider - - default: - values, err := env.Get(EnvCompartmentOCID) - if err != nil { - return nil, fmt.Errorf("oraclecloud: %w", err) - } - - config.CompartmentID = values[EnvCompartmentOCID] - - ecp, err := newEnvironmentConfigurationProvider() - if err != nil { - return nil, fmt.Errorf("oraclecloud: %w", err) - } - - config.OCIConfigProvider = ecp + values, err := env.Get(envPrivKey, EnvTenancyOCID, EnvUserOCID, EnvPubKeyFingerprint, EnvRegion, EnvCompartmentOCID) + if err != nil { + return nil, fmt.Errorf("oraclecloud: %w", err) } + config := NewDefaultConfig() + config.CompartmentID = values[EnvCompartmentOCID] + config.OCIConfigProvider = newConfigProvider(values) + return NewDNSProviderConfig(config) } @@ -148,7 +98,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } if config.HTTPClient != nil { - client.HTTPClient = clientdebug.Wrap(config.HTTPClient) + client.HTTPClient = config.HTTPClient } return &DNSProvider{client: &client, config: config}, nil @@ -218,7 +168,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var deleteHash *string - for _, record := range domainRecords.Items { if record.Rdata != nil && *record.Rdata == `"`+info.Value+`"` { deleteHash = record.RecordHash diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index f6155052e..8c756a374 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -5,39 +5,26 @@ Code = "oraclecloud" Since = "v2.3.0" Example = ''' -# Using API Key authentication: -OCI_PRIVATE_KEY_PATH="~/.oci/oci_api_key.pem" \ -OCI_PRIVATE_KEY_PASSWORD="secret" \ +OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ +OCI_PRIVKEY_PASS="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ 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_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --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] [Configuration.Credentials] + OCI_PRIVKEY_FILE = "Private key file" + OCI_PRIVKEY_PASS = "Private key password" + OCI_TENANCY_OCID = "Tenancy OCID" + OCI_USER_OCID = "User OCID" + OCI_PUBKEY_FINGERPRINT = "Public key fingerprint" + OCI_REGION = "Region" OCI_COMPARTMENT_OCID = "Compartment OCID" - OCI_REGION = "Region (it can be empty if `OCI_AUTH_TYPE=instance_principal`)." - OCI_PRIVATE_KEY_PATH = "Private key file (ignored if `OCI_AUTH_TYPE=instance_principal`)" - OCI_PRIVATE_KEY_PASSWORD = "Private key password (ignored if `OCI_AUTH_TYPE=instance_principal`)" - OCI_TENANCY_OCID = "Tenancy OCID (ignored if `OCI_AUTH_TYPE=instance_principal`)" - OCI_USER_OCID = "User OCID (ignored if `OCI_AUTH_TYPE=instance_principal`)" - OCI_FINGERPRINT = "Public key fingerprint (ignored if `OCI_AUTH_TYPE=instance_principal`)" [Configuration.Additional] - OCI_AUTH_TYPE = "Authorization type. Possible values: 'instance_principal', '' (Default: '')" - TF_VAR_region = "Alias on `OCI_REGION`" - TF_VAR_fingerprint = "Alias on `OCI_FINGERPRINT`" - TF_VAR_user_ocid = "Alias on `OCI_USER_OCID`" - TF_VAR_tenancy_ocid = "Alias on `OCI_TENANCY_OCID`" - TF_VAR_private_key_path = "Alias on `OCI_PRIVATE_KEY_PATH`" OCI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OCI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" OCI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 74ee06eac..5d35c01a8 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -6,31 +6,19 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" - "maps" - "net/http/httptest" "os" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/nrdcg/oci-go-sdk/common/v1065" "github.com/stretchr/testify/require" ) const envDomain = envNamespace + "DOMAIN" -// Used by Instance Principal authentication. -const ( - envMetadataBaseURL = "OCI_METADATA_BASE_URL" - envSDKAuthClientRegionURL = "OCI_SDK_AUTH_CLIENT_REGION_URL" -) - var envTest = tester.NewEnvTest( envPrivKey, - EnvAuthType, - envMetadataBaseURL, - envSDKAuthClientRegionURL, EnvPrivKeyFile, EnvPrivKeyPass, EnvTenancyOCID, @@ -61,7 +49,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", @@ -73,7 +61,7 @@ func TestNewDNSProvider(t *testing.T) { { desc: "missing credentials", envVars: map[string]string{}, - expected: "oraclecloud: some credentials information are missing: OCI_COMPARTMENT_OCID", + expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY,OCI_TENANCY_OCID,OCI_USER_OCID,OCI_PUBKEY_FINGERPRINT,OCI_REGION,OCI_COMPARTMENT_OCID", }, { desc: "missing CompartmentID", @@ -99,7 +87,7 @@ func TestNewDNSProvider(t *testing.T) { EnvRegion: "us-phoenix-1", EnvCompartmentOCID: "123", }, - expected: "oraclecloud: can not create client, bad configuration: no value provided for: OCI_PRIVKEY or OCI_PRIVATE_KEY or OCI_PRIVKEY_FILE or OCI_PRIVATE_KEY_PATH or TF_VAR_private_key_path", + expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY", }, { desc: "missing OCI_PRIVKEY_PASS", @@ -188,10 +176,8 @@ func TestNewDNSProvider(t *testing.T) { if privKeyFile != "" { _ = os.Remove(privKeyFile) } - envTest.RestoreEnv() }() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -211,74 +197,6 @@ func TestNewDNSProvider(t *testing.T) { } } -func TestNewDNSProvider_instance_principal(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAuthType: "instance_principal", - EnvCompartmentOCID: "123", - }, - }, - { - desc: "missing CompartmentID", - envVars: map[string]string{ - EnvAuthType: "instance_principal", - }, - expected: "oraclecloud: some credentials information are missing: OCI_COMPARTMENT_OCID", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer func() { - envTest.RestoreEnv() - }() - - envTest.ClearEnv() - - serverURL := servermock.NewBuilder( - func(server *httptest.Server) (string, error) { - return server.URL, nil - }). - Route("GET /instance/region", servermock.RawStringResponse("oc1")). - // To generate fake certificates: - // go run `go env GOROOT`/src/crypto/tls/generate_cert.go --host example.org --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h - Route("GET /identity/cert.pem", servermock.ResponseFromFixture("cert.pem")). - Route("GET /identity/key.pem", servermock.ResponseFromFixture("key.pem")). - Route("GET /identity/intermediate.pem", servermock.ResponseFromFixture("cert.pem")). - // https://github.com/oracle/oci-go-sdk/blob/413a2f277f95c5eb76e26a0e0833c396a518bf50/common/auth/jwt_test.go#L12 - Route("POST /v1/x509", servermock.RawStringResponse(`{"token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFzdyIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJvcGMub3JhY2xlLmNvbSIsImV4cCI6MTUxMTgzODc5MywiaWF0IjoxNTExODE3MTkzLCJpc3MiOiJhdXRoU2VydmljZS5vcmFjbGUuY29tIiwib3BjLWNlcnR0eXBlIjoiaW5zdGFuY2UiLCJvcGMtY29tcGFydG1lbnQiOiJvY2lkMS5jb21wYXJ0bWVudC5vYzEuLmJsdWhibHVoYmx1aCIsIm9wYy1pbnN0YW5jZSI6Im9jaWQxLmluc3RhbmNlLm9jMS5waHguYmx1aGJsdWhibHVoIiwib3BjLXRlbmFudCI6Im9jaWR2MTp0ZW5hbmN5Om9jMTpwaHg6MTIzNDU2Nzg5MDpibHVoYmx1aGJsdWgiLCJwdHlwZSI6Imluc3RhbmNlIiwic3ViIjoib2NpZDEuaW5zdGFuY2Uub2MxLnBoeC5ibHVoYmx1aGJsdWgiLCJ0ZW5hbnQiOiJvY2lkdjE6dGVuYW5jeTpvYzE6cGh4OjEyMzQ1Njc4OTA6Ymx1aGJsdWhibHVoIiwidHR5cGUiOiJ4NTA5In0.zen7q2yJSpMjzH4ym_H7VEwZA0-vTT4Wcild-HRfLxX6A1ej4tlpACa7A24j5JoZYI4mHooZVJ8e7ZezFenK0zZx5j8RbIjsqJKwroYXExOiBXLCUwMWOLXIndEsUzzGLqnPfKHXd80vrhMLmtkVTCJqBMzvPUSYkH_ciWgmjP9m0YETdQ9ifghkADhZGt9IlnOswg0s3Bx9ASwxFZEtom0BmU9GwEuITTTZfKvndk785BlNeZMOjhovaD97-LYpv5B_PiWEz8zialK5zxjijLCw06zyA8CQRQqmVCagNUPilfz_BcPyImzvFDuzQcPyDkTcsB7weX35tafHmA_Ul"}`)). - Build(t) - - envVars := map[string]string{ - envMetadataBaseURL: serverURL, - envSDKAuthClientRegionURL: serverURL, - } - - maps.Copy(envVars, test.envVars) - - envTest.Apply(envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), test.expected) - } - }) - } -} - func TestNewDNSProviderConfig(t *testing.T) { envTest.ClearEnv() defer envTest.RestoreEnv() @@ -333,7 +251,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -347,7 +264,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -357,20 +273,21 @@ func TestLiveCleanUp(t *testing.T) { require.NoError(t, err) } -func mockConfigurationProvider(keyPassphrase string) *environmentConfigurationProvider { +func mockConfigurationProvider(keyPassphrase string) *configProvider { envTest.Apply(map[string]string{ envPrivKey: mustGeneratePrivateKey("secret"), }) - return &environmentConfigurationProvider{ + return &configProvider{ values: map[string]string{ EnvCompartmentOCID: "test", - EnvPrivKeyPass: keyPassphrase, + EnvPrivKeyPass: "test", EnvTenancyOCID: "test", EnvUserOCID: "test", EnvPubKeyFingerprint: "test", EnvRegion: "test", }, + privateKeyPassphrase: keyPassphrase, } } @@ -383,21 +300,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/internal/client.go b/providers/dns/otc/internal/client.go index adb0682e1..e3e225314 100644 --- a/providers/dns/otc/internal/client.go +++ b/providers/dns/otc/internal/client.go @@ -42,8 +42,8 @@ func NewClient(username, password, domainName, projectName string) *Client { } } -func (c *Client) GetZoneID(ctx context.Context, zone string, privateZone bool) (string, error) { - zonesResp, err := c.getZones(ctx, zone, privateZone) +func (c *Client) GetZoneID(ctx context.Context, zone string) (string, error) { + zonesResp, err := c.getZones(ctx, zone) if err != nil { return "", err } @@ -62,18 +62,13 @@ func (c *Client) GetZoneID(ctx context.Context, zone string, privateZone bool) ( } // https://docs.otc.t-systems.com/domain-name-service/api-ref/apis/public_zone_management/querying_public_zones.html -func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (*ZonesResponse, error) { +func (c *Client) getZones(ctx context.Context, zone string) (*ZonesResponse, error) { c.muBaseURL.Lock() endpoint := c.baseURL.JoinPath("zones") c.muBaseURL.Unlock() query := endpoint.Query() query.Set("name", zone) - - if privateZone { - query.Set("type", "private") - } - endpoint.RawQuery = query.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -82,7 +77,6 @@ func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (* } var zones ZonesResponse - err = c.do(req, &zones) if err != nil { return nil, err @@ -129,7 +123,6 @@ func (c *Client) getRecordSet(ctx context.Context, zoneID, fqdn string) (*Record } var recordSetsRes RecordSetsResponse - err = c.do(req, &recordSetsRes) if err != nil { return nil, err @@ -170,11 +163,9 @@ func (c *Client) DeleteRecordSet(ctx context.Context, zoneID, recordID string) e func (c *Client) do(req *http.Request, result any) error { c.muToken.Lock() - if c.token != "" { req.Header.Set("X-Auth-Token", c.token) } - c.muToken.Unlock() resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/otc/internal/client_test.go b/providers/dns/otc/internal/client_test.go index 74b5bb3af..ea3835a56 100644 --- a/providers/dns/otc/internal/client_test.go +++ b/providers/dns/otc/internal/client_test.go @@ -33,22 +33,7 @@ func TestClient_GetZoneID(t *testing.T) { With("name", "example.com.")). Build(t) - zoneID, err := client.GetZoneID(context.Background(), "example.com.", false) - require.NoError(t, err) - - assert.Equal(t, "123123", zoneID) -} - -func TestClient_GetZoneID_private(t *testing.T) { - client := mockBuilder(). - Route("GET /zones", - servermock.ResponseFromFixture("zones_GET.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "example.com."). - With("type", "private")). - Build(t) - - zoneID, err := client.GetZoneID(context.Background(), "example.com.", true) + zoneID, err := client.GetZoneID(context.Background(), "example.com.") require.NoError(t, err) assert.Equal(t, "123123", zoneID) @@ -62,7 +47,7 @@ func TestClient_GetZoneID_error(t *testing.T) { With("name", "example.com.")). Build(t) - _, err := client.GetZoneID(context.Background(), "example.com.", false) + _, err := client.GetZoneID(context.Background(), "example.com.") require.EqualError(t, err, "zone example.com. not found") } @@ -99,8 +84,7 @@ func TestClient_GetRecordSetID_error(t *testing.T) { func TestClient_CreateRecordSet(t *testing.T) { client := mockBuilder(). Route("POST /zones/123123/recordsets", - servermock.ResponseFromFixture("zones-recordsets_POST.json"), - servermock.CheckRequestJSONBodyFromFixture("zones-recordsets_POST-request.json")). + servermock.ResponseFromFixture("zones-recordsets_POST.json")). Build(t) rs := RecordSets{ @@ -108,7 +92,7 @@ func TestClient_CreateRecordSet(t *testing.T) { Description: "Added TXT record for ACME dns-01 challenge using lego client", Type: "TXT", TTL: 300, - Records: []string{strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")}, + Records: []string{strconv.Quote("w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI")}, } err := client.CreateRecordSet(context.Background(), "123123", rs) require.NoError(t, err) diff --git a/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json b/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json deleted file mode 100644 index 41cab72a8..000000000 --- a/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "_acme-challenge.example.com.", - "description": "Added TXT record for ACME dns-01 challenge using lego client", - "type": "TXT", - "ttl": 300, - "records": [ - "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"" - ] -} diff --git a/providers/dns/otc/internal/identity.go b/providers/dns/otc/internal/identity.go index 154ec65e2..f9e7cb08f 100644 --- a/providers/dns/otc/internal/identity.go +++ b/providers/dns/otc/internal/identity.go @@ -46,7 +46,6 @@ func (c *Client) Login(ctx context.Context) error { c.muToken.Lock() defer c.muToken.Unlock() - c.token = token if c.token == "" { @@ -97,7 +96,6 @@ func (c *Client) obtainUserToken(ctx context.Context, payload LoginRequest) (*To } var newToken TokenResponse - err = json.Unmarshal(raw, &newToken) if err != nil { return nil, "", errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -108,7 +106,6 @@ func (c *Client) obtainUserToken(ctx context.Context, payload LoginRequest) (*To func getBaseURL(tokenResp *TokenResponse) (*url.URL, error) { var endpoints []Endpoint - for _, v := range tokenResp.Token.Catalog { if v.Type == "dns" { endpoints = append(endpoints, v.Endpoints...) diff --git a/providers/dns/otc/internal/types.go b/providers/dns/otc/internal/types.go index e7bfe8fcb..38da4f110 100644 --- a/providers/dns/otc/internal/types.go +++ b/providers/dns/otc/internal/types.go @@ -41,8 +41,8 @@ type TokenResponse struct { } type Token struct { - User UserR `json:"user"` - Domain Domain `json:"domain"` + User UserR `json:"user,omitempty"` + Domain Domain `json:"domain,omitempty"` Catalog []Catalog `json:"catalog,omitempty"` Methods []string `json:"methods,omitempty"` Roles []Role `json:"roles,omitempty"` @@ -59,7 +59,7 @@ type Catalog struct { type UserR struct { ID string `json:"id,omitempty"` - Domain Domain `json:"domain"` + Domain Domain `json:"domain,omitempty"` Name string `json:"name,omitempty"` PasswordExpiresAt string `json:"password_expires_at,omitempty"` } @@ -106,7 +106,7 @@ type RecordSets struct { // ZonesResponse type ZonesResponse struct { - Links Links `json:"links"` + Links Links `json:"links,omitempty"` Zones []Zone `json:"zones"` Metadata Metadata `json:"metadata"` } diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go index 65b362124..3569e6343 100644 --- a/providers/dns/otc/otc.go +++ b/providers/dns/otc/otc.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/otc/internal" ) @@ -24,7 +23,6 @@ const ( EnvPassword = envNamespace + "PASSWORD" EnvProjectName = envNamespace + "PROJECT_NAME" EnvIdentityEndpoint = envNamespace + "IDENTITY_ENDPOINT" - EnvPrivateZone = envNamespace + "PRIVATE_ZONE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -42,13 +40,11 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - DomainName string - ProjectName string - UserName string - Password string - IdentityEndpoint string - PrivateZone bool - + IdentityEndpoint string + DomainName string + ProjectName string + UserName string + Password string PropagationTimeout time.Duration PollingInterval time.Duration SequenceInterval time.Duration @@ -69,12 +65,10 @@ func NewDefaultConfig() *Config { tr.DisableKeepAlives = true return &Config{ - PrivateZone: env.GetOrDefaultBool(EnvPrivateZone, false), - IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint), - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint), SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), @@ -131,8 +125,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -152,7 +144,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("otc: %w", err) } - zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone) + zoneID, err := d.client.GetZoneID(ctx, authZone) if err != nil { return fmt.Errorf("otc: unable to get zone: %w", err) } @@ -189,7 +181,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("otc: %w", err) } - zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone) + zoneID, err := d.client.GetZoneID(ctx, authZone) if err != nil { return fmt.Errorf("otc: %w", err) } diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index e63077fda..cb1910d26 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -4,13 +4,7 @@ URL = "https://cloud.telekom.de/en" Code = "otc" Since = "v0.4.1" -Example = ''' -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 -''' +Example = '''''' [Configuration] [Configuration.Credentials] @@ -18,9 +12,8 @@ lego --dns otc -d '*.example.com' -d example.com run OTC_PASSWORD = "Password" OTC_PROJECT_NAME = "Project name" OTC_DOMAIN_NAME = "Domain name" + OTC_IDENTITY_ENDPOINT = "Identity endpoint URL" [Configuration.Additional] - OTC_IDENTITY_ENDPOINT = "Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens)" - OTC_PRIVATE_ZONE = "Set to true to use private zones only (default: use public zones only)" OTC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" OTC_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 518ce0f19..c8b32bc78 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -18,7 +18,6 @@ var envTest = tester.NewEnvTest( EnvDomainName, EnvUserName, EnvPassword, - EnvPrivateZone, EnvProjectName, EnvIdentityEndpoint). WithDomain(envDomain) @@ -93,7 +92,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -192,7 +190,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -206,7 +203,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -217,30 +213,12 @@ func TestLiveCleanUp(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(false). + provider := mockBuilder(). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). - Route("POST /v2/zones/123123/recordsets", - servermock.Noop(), - servermock.CheckRequestJSONBodyFromInternal("zones-recordsets_POST-request.json")). - Build(t) - - err := provider.Present("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_Present_private(t *testing.T) { - provider := mockBuilder(true). - Route("GET /v2/zones", - servermock.ResponseFromInternal("zones_GET.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "example.com."). - With("type", "private")). - Route("POST /v2/zones/123123/recordsets", - servermock.Noop(), - servermock.CheckRequestJSONBodyFromInternal("zones-recordsets_POST-request.json")). + Route("/", servermock.DumpRequest()). Build(t) err := provider.Present("example.com", "", "123d==") @@ -248,11 +226,12 @@ func TestDNSProvider_Present_private(t *testing.T) { } func TestDNSProvider_Present_emptyZone(t *testing.T) { - provider := mockBuilder(false). + provider := mockBuilder(). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET_empty.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). + Route("/", servermock.DumpRequest()). Build(t) err := provider.Present("example.com", "", "123d==") @@ -260,7 +239,7 @@ func TestDNSProvider_Present_emptyZone(t *testing.T) { } func TestDNSProvider_Cleanup(t *testing.T) { - provider := mockBuilder(false). + provider := mockBuilder(). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). @@ -278,28 +257,8 @@ func TestDNSProvider_Cleanup(t *testing.T) { require.NoError(t, err) } -func TestDNSProvider_Cleanup_private(t *testing.T) { - provider := mockBuilder(true). - Route("GET /v2/zones", - servermock.ResponseFromInternal("zones_GET.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "example.com."). - With("type", "private")). - Route("GET /v2/zones/123123/recordsets", - servermock.ResponseFromInternal("zones-recordsets_GET.json"), - servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.example.com."). - With("type", "TXT")). - Route("DELETE /v2/zones/123123/recordsets/321321", - servermock.ResponseFromInternal("zones-recordsets_DELETE.json")). - Build(t) - - err := provider.CleanUp("example.com", "", "123d==") - require.NoError(t, err) -} - func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { - provider := mockBuilder(false). + provider := mockBuilder(). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). @@ -315,17 +274,15 @@ func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { require.EqualError(t, err, "otc: unable to get record _acme-challenge.example.com. for zone example.com: record not found") } -func mockBuilder(private bool) *servermock.Builder[*DNSProvider] { +func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.UserName = "user" config.Password = "secret" config.DomainName = "example.com" config.ProjectName = "test" config.IdentityEndpoint = fmt.Sprintf("%s/v3/auth/token", server.URL) - config.PrivateZone = private return NewDNSProviderConfig(config) }, diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index a8d12d819..c70e943bc 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/ovh/go-ovh/ovh" ) @@ -102,9 +101,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 } @@ -192,7 +190,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Create TXT record var respData Record - err = d.client.Post(reqURL, reqData, &respData) if err != nil { return fmt.Errorf("ovh: error when call api to add record (%s): %w", reqURL, err) @@ -200,7 +197,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Apply the change reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone) - err = d.client.Post(reqURL, nil, nil) if err != nil { return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err) @@ -221,7 +217,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("ovh: unknown record ID for '%s'", info.EffectiveFQDN) } @@ -242,7 +237,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Apply the change reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone) - err = d.client.Post(reqURL, nil, nil) if err != nil { return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err) @@ -263,10 +257,8 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { } func newClient(config *Config) (*ovh.Client, error) { - var ( - client *ovh.Client - err error - ) + var client *ovh.Client + var err error switch { case config.hasAppKeyAuth(): @@ -285,11 +277,5 @@ func newClient(config *Config) (*ovh.Client, error) { client.UserAgent = useragent.Get() - if config.HTTPClient != nil { - client.Client = config.HTTPClient - } - - client.Client = clientdebug.Wrap(client.Client) - return client, nil } diff --git a/providers/dns/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/ovh/ovh_test.go b/providers/dns/ovh/ovh_test.go index 332e7f192..fcb2300b6 100644 --- a/providers/dns/ovh/ovh_test.go +++ b/providers/dns/ovh/ovh_test.go @@ -162,7 +162,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -316,7 +315,6 @@ func TestNewDNSProviderConfig(t *testing.T) { // The OVH client use the same env vars than lego, so it requires to clean them. defer envTest.RestoreEnv() - envTest.ClearEnv() for _, test := range testCases { @@ -356,7 +354,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -370,7 +367,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/pdns/internal/client.go b/providers/dns/pdns/internal/client.go index f72dd4d78..f6b55d5de 100644 --- a/providers/dns/pdns/internal/client.go +++ b/providers/dns/pdns/internal/client.go @@ -69,7 +69,6 @@ func (c *Client) getAPIVersion(ctx context.Context) (int, error) { } var versions []apiVersion - err = json.Unmarshal(result, &versions) if err != nil { return 0, err @@ -99,7 +98,6 @@ func (c *Client) GetHostedZone(ctx context.Context, authZone string) (*HostedZon } var zone HostedZone - err = json.Unmarshal(result, &zone) if err != nil { return nil, err @@ -182,7 +180,6 @@ func (c *Client) do(req *http.Request) (json.RawMessage, error) { } var msg json.RawMessage - err = json.NewDecoder(resp.Body).Decode(&msg) if err != nil { if errors.Is(err, io.EOF) { @@ -196,12 +193,10 @@ func (c *Client) do(req *http.Request) (json.RawMessage, error) { // check for PowerDNS error message if len(msg) > 0 && msg[0] == '{' { var errInfo apiError - err = json.Unmarshal(msg, &errInfo) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, msg, err) } - if errInfo.ShortMsg != "" { return nil, fmt.Errorf("error talking to PDNS API: %w", errInfo) } diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index e7ead7078..ec0ae2a70 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/pdns/internal" ) @@ -104,12 +103,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Host, config.ServerName, config.APIVersion, config.APIKey) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.APIVersion <= 0 { err := client.SetAPIVersion(context.Background()) if err != nil { @@ -213,7 +206,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var records []internal.Record - for _, r := range set.Records { if r.Content != strconv.Quote(info.Value) { records = append(records, r) diff --git a/providers/dns/pdns/pdns.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/pdns/pdns_test.go b/providers/dns/pdns/pdns_test.go index 0213ba17c..6762e892e 100644 --- a/providers/dns/pdns/pdns_test.go +++ b/providers/dns/pdns/pdns_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -137,7 +136,6 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -157,6 +155,5 @@ func mustParse(rawURL string) *url.URL { if err != nil { panic(err) } - return u } diff --git a/providers/dns/plesk/internal/client.go b/providers/dns/plesk/internal/client.go index 47abba805..88a7fdd9f 100644 --- a/providers/dns/plesk/internal/client.go +++ b/providers/dns/plesk/internal/client.go @@ -121,7 +121,6 @@ func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*Res endpoint := c.baseURL.JoinPath("/enterprise/control/agent.php") body := new(bytes.Buffer) - err := xml.NewEncoder(body).Encode(payload) if err != nil { return nil, err @@ -154,7 +153,6 @@ func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*Res } var response ResponsePacketType - err = xml.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/plesk/plesk.go b/providers/dns/plesk/plesk.go index 5f07dcb50..b7a7ebf77 100644 --- a/providers/dns/plesk/plesk.go +++ b/providers/dns/plesk/plesk.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/plesk/internal" ) @@ -108,8 +107,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -163,7 +160,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("plesk: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -173,9 +169,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/plesk/plesk_test.go b/providers/dns/plesk/plesk_test.go index 506a26a2a..417e2c1da 100644 --- a/providers/dns/plesk/plesk_test.go +++ b/providers/dns/plesk/plesk_test.go @@ -67,7 +67,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -150,7 +149,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -164,7 +162,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go index 2f999ebcc..44bf1857b 100644 --- a/providers/dns/porkbun/porkbun.go +++ b/providers/dns/porkbun/porkbun.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/porkbun" ) @@ -101,8 +100,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -154,7 +151,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("porkbun: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -171,10 +167,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/porkbun/porkbun_test.go b/providers/dns/porkbun/porkbun_test.go index 7c69edfdb..cdf022b5d 100644 --- a/providers/dns/porkbun/porkbun_test.go +++ b/providers/dns/porkbun/porkbun_test.go @@ -54,7 +54,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,7 +124,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,7 +137,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rackspace/internal/client.go b/providers/dns/rackspace/internal/client.go index 4a1872484..076409ebd 100644 --- a/providers/dns/rackspace/internal/client.go +++ b/providers/dns/rackspace/internal/client.go @@ -113,7 +113,6 @@ func (c *Client) listDomainsByName(ctx context.Context, domain string) (*ZoneSea } var zoneSearchResponse ZoneSearchResponse - err = c.do(req, &zoneSearchResponse) if err != nil { return nil, err @@ -155,7 +154,6 @@ func (c *Client) searchRecords(ctx context.Context, zoneID, recordName, recordTy } var records Records - err = c.do(req, &records) if err != nil { return nil, err diff --git a/providers/dns/rackspace/internal/identity.go b/providers/dns/rackspace/internal/identity.go index 3ff667fb8..062350df5 100644 --- a/providers/dns/rackspace/internal/identity.go +++ b/providers/dns/rackspace/internal/identity.go @@ -65,7 +65,6 @@ func (a *Identifier) Login(ctx context.Context, apiUser, apiKey string) (*Identi } var identity Identity - err = json.Unmarshal(raw, &identity) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/rackspace/rackspace.go b/providers/dns/rackspace/rackspace.go index b4c7b4a0f..b9ce8f6e3 100644 --- a/providers/dns/rackspace/rackspace.go +++ b/providers/dns/rackspace/rackspace.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rackspace/internal" ) @@ -99,7 +98,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Iterate through the Service Catalog to get the DNS Endpoint var dnsEndpoint string - for _, service := range identity.Access.ServiceCatalog { if service.Name == "cloudDNS" { dnsEndpoint = service.Endpoints[0].PublicURL @@ -120,8 +118,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/rackspace/rackspace.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/rackspace/rackspace_test.go b/providers/dns/rackspace/rackspace_test.go index de0749fd3..cefb46134 100644 --- a/providers/dns/rackspace/rackspace_test.go +++ b/providers/dns/rackspace/rackspace_test.go @@ -75,7 +75,6 @@ func TestLiveNewDNSProvider_ValidEnv(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -88,7 +87,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -102,7 +100,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -116,7 +113,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.APIUser = "testUser" config.APIKey = "testKey" config.HTTPClient = server.Client() diff --git a/providers/dns/rainyun/internal/client.go b/providers/dns/rainyun/internal/client.go index 595b39f29..3d99bd9be 100644 --- a/providers/dns/rainyun/internal/client.go +++ b/providers/dns/rainyun/internal/client.go @@ -84,7 +84,6 @@ func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error } var recordData APIResponse[Record] - err = c.do(req, &recordData) if err != nil { return nil, err @@ -174,7 +173,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/rainyun/rainyun.go b/providers/dns/rainyun/rainyun.go index a4d1c4035..43ef9cb1b 100644 --- a/providers/dns/rainyun/rainyun.go +++ b/providers/dns/rainyun/rainyun.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rainyun/internal" ) @@ -86,8 +85,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/rainyun/rainyun.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/rainyun/rainyun_test.go b/providers/dns/rainyun/rainyun_test.go index d27d47e81..d0048e5d0 100644 --- a/providers/dns/rainyun/rainyun_test.go +++ b/providers/dns/rainyun/rainyun_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rcodezero/internal/client.go b/providers/dns/rcodezero/internal/client.go index 5cf39907e..d37fec2dd 100644 --- a/providers/dns/rcodezero/internal/client.go +++ b/providers/dns/rcodezero/internal/client.go @@ -64,7 +64,6 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) { } result := &APIResponse{} - raw, err := io.ReadAll(resp.Body) if err != nil { return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) @@ -106,7 +105,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIResponse{} - err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/rcodezero/rcodezero.go b/providers/dns/rcodezero/rcodezero.go index 010a6dadc..93f3e957a 100644 --- a/providers/dns/rcodezero/rcodezero.go +++ b/providers/dns/rcodezero/rcodezero.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rcodezero/internal" ) @@ -87,8 +86,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/rcodezero/rcodezero_test.go b/providers/dns/rcodezero/rcodezero_test.go index a4a242c30..1f0946072 100644 --- a/providers/dns/rcodezero/rcodezero_test.go +++ b/providers/dns/rcodezero/rcodezero_test.go @@ -37,7 +37,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,7 +94,6 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/regfish/regfish.go b/providers/dns/regfish/regfish.go index 85aac92e5..6a8ccee98 100644 --- a/providers/dns/regfish/regfish.go +++ b/providers/dns/regfish/regfish.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" regfishapi "github.com/regfish/regfish-dnsapi-go" ) @@ -85,15 +84,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := regfishapi.NewClient(config.APIKey) - if config.HTTPClient != nil { - client.Client = config.HTTPClient - } else { - // Because the regfishapi.NewClient uses an empty http.Client. - client.Client = &http.Client{Timeout: 30 * time.Second} - } - - client.Client = clientdebug.Wrap(client.Client) - return &DNSProvider{ config: config, client: client, @@ -132,7 +122,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("regfish: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/regfish/regfish.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/regfish/regfish_test.go b/providers/dns/regfish/regfish_test.go index 6613bd508..80928048f 100644 --- a/providers/dns/regfish/regfish_test.go +++ b/providers/dns/regfish/regfish_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,7 +92,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,7 +105,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/regru/internal/client.go b/providers/dns/regru/internal/client.go index b0b86d567..4b0205b0f 100644 --- a/providers/dns/regru/internal/client.go +++ b/providers/dns/regru/internal/client.go @@ -111,7 +111,6 @@ func (c *Client) doRequest(ctx context.Context, request any, fragments ...string } var apiResp APIResponse - err = json.Unmarshal(raw, &apiResp) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -124,7 +123,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/regru/internal/client_test.go b/providers/dns/regru/internal/client_test.go index 002da0185..0779f0d5f 100644 --- a/providers/dns/regru/internal/client_test.go +++ b/providers/dns/regru/internal/client_test.go @@ -13,7 +13,6 @@ func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") - client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) return client, nil diff --git a/providers/dns/regru/regru.go b/providers/dns/regru/regru.go index b06b355c1..1501863bd 100644 --- a/providers/dns/regru/regru.go +++ b/providers/dns/regru/regru.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/regru/internal" ) @@ -98,8 +97,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.TLSCert != "" || config.TLSKey != "" { if config.TLSCert == "" { return nil, errors.New("regru: TLS certificate is missing") diff --git a/providers/dns/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/regru/regru_test.go b/providers/dns/regru/regru_test.go index 762eeb4d3..15d86d75c 100644 --- a/providers/dns/regru/regru_test.go +++ b/providers/dns/regru/regru_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,7 +129,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,7 +142,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 2c4fe7aeb..6b5c47072 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -131,7 +131,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.TSIGSecret = "" } else { // zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) - config.TSIGKey = dns.CanonicalName(config.TSIGKey) + config.TSIGKey = strings.ToLower(dns.Fqdn(config.TSIGKey)) } if config.TSIGAlgorithm == "" { @@ -171,7 +171,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("rfc2136: failed to insert: %w", err) } - return nil } @@ -183,7 +182,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("rfc2136: failed to remove: %w", err) } - return nil } @@ -195,14 +193,14 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { } // Create RR - rrs := []dns.RR{&dns.TXT{ - Hdr: dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)}, - Txt: []string{value}, - }} + rr := new(dns.TXT) + rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)} + rr.Txt = []string{value} + rrs := []dns.RR{rr} // Create dynamic update packet - m := new(dns.Msg).SetUpdate(zone) - + m := new(dns.Msg) + m.SetUpdate(zone) switch action { case "INSERT": // Always remove old challenge left over from who knows what. @@ -230,7 +228,6 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { if err != nil { return fmt.Errorf("DNS update failed: %w", err) } - if reply != nil && reply.Rcode != dns.RcodeSuccess { return fmt.Errorf("DNS update failed: server replied: %s", dns.RcodeToString[reply.Rcode]) } diff --git a/providers/dns/rfc2136/rfc2136.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/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index ce4859e84..80fdc69cb 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -2,21 +2,24 @@ package rfc2136 import ( "bytes" + "fmt" + "net" "strings" + "sync" "testing" "time" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/dnsmock" "github.com/miekg/dns" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( fakeDomain = "123456789.www.example.com" fakeKeyAuth = "123d==" - fakeValue = "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + fakeValue = "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo" fakeFqdn = "_acme-challenge.123456789.www.example.com." fakeZone = "example.com." fakeTTL = 120 @@ -84,7 +87,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -161,16 +163,39 @@ func TestNewDNSProviderConfig(t *testing.T) { } } -func TestDNSProvider_Present_success(t *testing.T) { +func TestCanaryLocalTestServer(t *testing.T) { dns01.ClearFqdnCache() + dns.HandleFunc("example.com.", serverHandlerHello) + defer dns.HandleRemove("example.com.") - addr := dnsmock.NewServer(). - Query(fakeZone+" SOA", dnsmock.SOA("")). - Update(fakeZone+" SOA", dnsmock.Noop). - Build(t) + server, addr, err := runLocalDNSTestServer(false) + require.NoError(t, err, "Failed to start test server") + defer func() { _ = server.Shutdown() }() + + c := new(dns.Client) + m := new(dns.Msg) + + m.SetQuestion("example.com.", dns.TypeTXT) + + r, _, err := c.Exchange(m, addr) + require.NoError(t, err, "Failed to communicate with test server") + assert.Len(t, r.Extra, 1, "Failed to communicate with test server") + + txt := r.Extra[0].(*dns.TXT).Txt[0] + assert.Equal(t, "Hello world", txt) +} + +func TestServerSuccess(t *testing.T) { + dns01.ClearFqdnCache() + dns.HandleFunc(fakeZone, serverHandlerReturnSuccess) + defer dns.HandleRemove(fakeZone) + + server, addr, err := runLocalDNSTestServer(false) + require.NoError(t, err, "Failed to start test server") + defer func() { _ = server.Shutdown() }() config := NewDefaultConfig() - config.Nameserver = addr.String() + config.Nameserver = addr provider, err := NewDNSProviderConfig(config) require.NoError(t, err) @@ -179,98 +204,39 @@ func TestDNSProvider_Present_success(t *testing.T) { require.NoError(t, err) } -func TestDNSProvider_Present_success_updatePacket(t *testing.T) { +func TestServerError(t *testing.T) { dns01.ClearFqdnCache() + dns.HandleFunc(fakeZone, serverHandlerReturnErr) + defer dns.HandleRemove(fakeZone) - reqChan := make(chan *dns.Msg, 1) - - addr := dnsmock.NewServer(). - Query("_acme-challenge.123456789.www.example.com. SOA", dnsmock.SOA(fakeZone)). - Update(fakeZone+" SOA", func(w dns.ResponseWriter, req *dns.Msg) { - dnsmock.Noop(w, req) - - // Only talk back when it is not the SOA RR. - reqChan <- req - }). - Build(t) + server, addr, err := runLocalDNSTestServer(false) + require.NoError(t, err, "Failed to start test server") + defer func() { _ = server.Shutdown() }() config := NewDefaultConfig() - config.Nameserver = addr.String() - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = provider.Present(fakeDomain, "", fakeKeyAuth) - require.NoError(t, err) - - select { - case <-time.After(time.Second): - t.Fatal("timeout waiting for request") - - case rcvMsg := <-reqChan: - txtRR := &dns.TXT{ - Hdr: dns.RR_Header{Name: fakeFqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: fakeTTL}, - Txt: []string{fakeValue}, - } - - m := new(dns.Msg).SetUpdate(fakeZone) - - m.RemoveRRset([]dns.RR{txtRR}) - m.Insert([]dns.RR{txtRR}) - - expected, err := m.Pack() - require.NoError(t, err, "error packing") - - rcvMsg.Id = m.Id - - actual, err := rcvMsg.Pack() - require.NoError(t, err, "error packing") - - if !bytes.Equal(actual, expected) { - tmp := new(dns.Msg) - require.NoError(t, tmp.Unpack(actual)) - - t.Errorf("Expected msg:\n%s", m) - t.Errorf("Actual msg:\n%s", tmp) - } - } -} - -func TestDNSProvider_Present_error(t *testing.T) { - dns01.ClearFqdnCache() - - addr := dnsmock.NewServer(). - Query(fakeZone+" SOA", dnsmock.Error(dns.RcodeNotZone)). - Build(t) - - config := NewDefaultConfig() - config.Nameserver = addr.String() + config.Nameserver = addr provider, err := NewDNSProviderConfig(config) require.NoError(t, err) err = provider.Present(fakeDomain, "", fakeKeyAuth) require.Error(t, err) - if !strings.Contains(err.Error(), "NOTZONE") { t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string, but it did not: %v", err) } } -func TestDNSProvider_Present_tsig_success(t *testing.T) { +func TestTsigClient(t *testing.T) { dns01.ClearFqdnCache() + dns.HandleFunc(fakeZone, serverHandlerReturnSuccess) + defer dns.HandleRemove(fakeZone) - addr := dnsmock.NewServer(). - Query(fakeZone+" SOA", dnsmock.SOA("")). - Update(fakeZone+" SOA", handleTSIG). - Build(t, func(server *dns.Server) error { - server.TsigSecret = map[string]string{fakeTsigKey: fakeTsigSecret} - - return nil - }) + server, addr, err := runLocalDNSTestServer(true) + require.NoError(t, err, "Failed to start test server") + defer func() { _ = server.Shutdown() }() config := NewDefaultConfig() - config.Nameserver = addr.String() + config.Nameserver = addr config.TSIGKey = fakeTsigKey config.TSIGSecret = fakeTsigSecret @@ -281,50 +247,143 @@ func TestDNSProvider_Present_tsig_success(t *testing.T) { require.NoError(t, err) } -func TestDNSProvider_Present_tsig_error(t *testing.T) { +func TestValidUpdatePacket(t *testing.T) { + reqChan := make(chan *dns.Msg, 10) + dns01.ClearFqdnCache() + dns.HandleFunc(fakeZone, serverHandlerPassBackRequest(reqChan)) + defer dns.HandleRemove(fakeZone) - addr := dnsmock.NewServer(). - Query(fakeZone+" SOA", dnsmock.SOA("")). - Update(fakeZone+" SOA", handleTSIG). - Build(t, func(server *dns.Server) error { - server.TsigSecret = map[string]string{"example.org": fakeTsigSecret} + server, addr, err := runLocalDNSTestServer(false) + require.NoError(t, err, "Failed to start test server") + defer func() { _ = server.Shutdown() }() - return nil - }) - - config := NewDefaultConfig() - config.Nameserver = addr.String() - config.TSIGKey = fakeTsigKey - config.TSIGSecret = fakeTsigSecret - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = provider.Present(fakeDomain, "", fakeKeyAuth) - require.Error(t, err) - require.EqualError(t, err, "rfc2136: failed to insert: DNS update failed: server replied: NOTZONE") -} - -func handleTSIG(w dns.ResponseWriter, req *dns.Msg) { + txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", fakeFqdn, fakeTTL, fakeValue)) + rrs := []dns.RR{txtRR} m := new(dns.Msg) + m.SetUpdate(fakeZone) + m.RemoveRRset(rrs) + m.Insert(rrs) + expectStr := m.String() - tsig := req.IsTsig() - if tsig == nil { - _ = w.WriteMsg(m.SetRcode(req, dns.RcodeRefused)) - return + expect, err := m.Pack() + require.NoError(t, err, "error packing") + + config := NewDefaultConfig() + config.Nameserver = addr + + provider, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + err = provider.Present(fakeDomain, "", "1234d==") + require.NoError(t, err) + + rcvMsg := <-reqChan + rcvMsg.Id = m.Id + + actual, err := rcvMsg.Pack() + require.NoError(t, err, "error packing") + + if !bytes.Equal(actual, expect) { + tmp := new(dns.Msg) + if err := tmp.Unpack(actual); err != nil { + t.Fatalf("Error unpacking actual msg: %v", err) + } + t.Errorf("Expected msg:\n%s", expectStr) + t.Errorf("Actual msg:\n%v", tmp) + } +} + +func runLocalDNSTestServer(tsig bool) (*dns.Server, string, error) { + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + return nil, "", err + } + + server := &dns.Server{ + PacketConn: pc, + ReadTimeout: time.Hour, + WriteTimeout: time.Hour, + MsgAcceptFunc: func(dh dns.Header) dns.MsgAcceptAction { + // bypass defaultMsgAcceptFunc to allow dynamic update (https://github.com/miekg/dns/pull/830) + return dns.MsgAccept + }, + } + + if tsig { + server.TsigSecret = map[string]string{fakeTsigKey: fakeTsigSecret} + } + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + + go func() { + _ = server.ActivateAndServe() + pc.Close() + }() + + waitLock.Lock() + return server, pc.LocalAddr().String(), nil +} + +func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + m.Extra = make([]dns.RR, 1) + m.Extra[0] = &dns.TXT{ + Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, + Txt: []string{"Hello world"}, + } + _ = w.WriteMsg(m) +} + +func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { + // Return SOA to appease findZoneByFqdn() + soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", fakeZone, fakeTTL, fakeZone, fakeZone)) + m.Answer = []dns.RR{soaRR} + } + + if t := req.IsTsig(); t != nil { + if w.TsigStatus() == nil { + // Validated + m.SetTsig(fakeZone, dns.HmacSHA1, 300, time.Now().Unix()) + } + } + + _ = w.WriteMsg(m) +} + +func serverHandlerReturnErr(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetRcode(req, dns.RcodeNotZone) + _ = w.WriteMsg(m) +} + +func serverHandlerPassBackRequest(reqChan chan *dns.Msg) func(w dns.ResponseWriter, req *dns.Msg) { + return func(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { + // Return SOA to appease findZoneByFqdn() + soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", fakeZone, fakeTTL, fakeZone, fakeZone)) + m.Answer = []dns.RR{soaRR} + } + + if t := req.IsTsig(); t != nil { + if w.TsigStatus() == nil { + // Validated + m.SetTsig(fakeZone, dns.HmacSHA1, 300, time.Now().Unix()) + } + } + + _ = w.WriteMsg(m) + if req.Opcode != dns.OpcodeQuery || req.Question[0].Qtype != dns.TypeSOA || req.Question[0].Qclass != dns.ClassINET { + // Only talk back when it is not the SOA RR. + reqChan <- req + } } - - err := w.TsigStatus() - if err != nil { - _ = w.WriteMsg(m.SetRcode(req, dns.RcodeNotZone)) - - return - } - - // Validated - _ = w.WriteMsg(m. - SetReply(req). - SetTsig(tsig.Hdr.Name, tsig.Algorithm, tsig.Fudge, time.Now().Unix()), - ) } diff --git a/providers/dns/rimuhosting/rimuhosting.go b/providers/dns/rimuhosting/rimuhosting.go index 7a7e99f60..9051d0add 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" @@ -28,12 +29,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 +52,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 +76,48 @@ 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 + } + + 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 +125,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..cbdacedc4 100644 --- a/providers/dns/rimuhosting/rimuhosting_test.go +++ b/providers/dns/rimuhosting/rimuhosting_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -46,7 +45,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 +83,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) } @@ -98,7 +97,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -112,7 +110,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index b41c95dac..db578eb00 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -17,7 +17,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/route53" awstypes "github.com/aws/aws-sdk-go-v2/service/route53/types" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -155,7 +154,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { realValue := `"` + info.Value + `"` var found bool - for _, record := range records { if ptr.Deref(record.Value) == realValue { found = true @@ -201,7 +199,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var nonLegoRecords []awstypes.ResourceRecord - for _, record := range existingRecords { if ptr.Deref(record.Value) != `"`+info.Value+`"` { nonLegoRecords = append(nonLegoRecords, record) @@ -252,22 +249,18 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action awstypes.ChangeAc changeID := resp.ChangeInfo.Id if d.config.WaitForRecordSetsChanged { - return wait.Retry(ctx, - func() error { - resp, err := d.client.GetChange(ctx, &route53.GetChangeInput{Id: changeID}) - if err != nil { - return fmt.Errorf("failed to query change status: %w", err) - } + return wait.For("route53", d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { + resp, err := d.client.GetChange(ctx, &route53.GetChangeInput{Id: changeID}) + if err != nil { + return false, fmt.Errorf("failed to query change status: %w", err) + } - if resp.ChangeInfo.Status != awstypes.ChangeStatusInsync { - return fmt.Errorf("unable to retrieve change: ID=%s, status=%s", ptr.Deref(changeID), resp.ChangeInfo.Status) - } + if resp.ChangeInfo.Status == awstypes.ChangeStatusInsync { + return true, nil + } - return nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), - ) + return false, fmt.Errorf("unable to retrieve change: ID=%s", ptr.Deref(changeID)) + }) } return nil @@ -314,14 +307,12 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(dns01.UnFqdn(authZone)), } - resp, err := d.client.ListHostedZonesByName(ctx, reqParams) if err != nil { return "", err } var hostedZoneID string - for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot if ptr.Deref(hostedZone.Name) == authZone && d.config.PrivateZone == hostedZone.Config.PrivateZone { @@ -357,7 +348,6 @@ func createAWSConfig(ctx context.Context, config *Config) (aws.Config, error) { retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) - return time.Duration(delay) * time.Millisecond, nil }) }) diff --git a/providers/dns/route53/route53.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/route53/route53_test.go b/providers/dns/route53/route53_test.go index 41ed824bc..6079bb4e6 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -34,7 +34,6 @@ var envTest = tester.NewEnvTest( func Test_loadCredentials_FromEnv(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() _ = os.Setenv(EnvAccessKeyID, "123") @@ -61,7 +60,6 @@ func Test_loadCredentials_FromEnv(t *testing.T) { func Test_loadRegion_FromEnv(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() _ = os.Setenv(EnvRegion, "foo") @@ -74,7 +72,6 @@ func Test_loadRegion_FromEnv(t *testing.T) { func Test_getHostedZoneID_FromEnv(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() expectedZoneID := "zoneID" @@ -85,7 +82,7 @@ func Test_getHostedZoneID_FromEnv(t *testing.T) { require.NoError(t, err) hostedZoneID, err := provider.getHostedZoneID(t.Context(), "whatever") - require.NoError(t, err) + require.NoError(t, err, "HostedZoneID") assert.Equal(t, expectedZoneID, hostedZoneID) } @@ -131,7 +128,6 @@ func TestNewDefaultConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { envTest.ClearEnv() - for key, value := range test.envVars { _ = os.Setenv(key, value) } @@ -145,13 +141,11 @@ func TestNewDefaultConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { cfg := aws.Config{ - HTTPClient: server.Client(), Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), Region: "mock-region", BaseEndpoint: aws.String(server.URL), @@ -187,7 +181,7 @@ func TestDNSProvider_Present(t *testing.T) { keyAuth := "123456d==" err := provider.Present(domain, "", keyAuth) - require.NoError(t, err) + require.NoError(t, err, "Expected Present to return no error") } func Test_createAWSConfig(t *testing.T) { @@ -276,7 +270,6 @@ func Test_createAWSConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.env) diff --git a/providers/dns/safedns/internal/client.go b/providers/dns/safedns/internal/client.go index 628618032..3e6f99919 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 @@ -48,7 +48,6 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record Record) (*Ad } respData := &AddRecordResponse{} - err = c.do(req, respData) if err != nil { return nil, fmt.Errorf("add record: %w", err) @@ -133,7 +132,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/safedns/internal/client_test.go b/providers/dns/safedns/internal/client_test.go index 161a9f078..f984d2d8f 100644 --- a/providers/dns/safedns/internal/client_test.go +++ b/providers/dns/safedns/internal/client_test.go @@ -17,7 +17,6 @@ func mockBuilder() *servermock.Builder[*Client] { func(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() return client, nil }, diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index 154cfc5ee..5066db59f 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 ( @@ -12,9 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/safedns/internal" - "github.com/miekg/dns" ) // Environment variables. @@ -75,7 +73,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") @@ -91,8 +89,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -110,7 +106,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) + zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("safedns: could not find zone for domain %q: %w", domain, err) } @@ -146,7 +142,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("safedns: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/safedns/safedns.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/safedns/safedns_test.go b/providers/dns/safedns/safedns_test.go index ce7568056..dcb374718 100644 --- a/providers/dns/safedns/safedns_test.go +++ b/providers/dns/safedns/safedns_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,7 +95,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,7 +108,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sakuracloud/sakuracloud.go b/providers/dns/sakuracloud/sakuracloud.go index 1adbe3a88..f12248d42 100644 --- a/providers/dns/sakuracloud/sakuracloud.go +++ b/providers/dns/sakuracloud/sakuracloud.go @@ -2,7 +2,6 @@ package sakuracloud import ( - "context" "errors" "fmt" "net/http" @@ -12,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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" client "github.com/sacloud/api-client-go" "github.com/sacloud/iaas-api-go" @@ -102,7 +100,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { Options: &client.Options{ AccessToken: config.Token, AccessTokenSecret: config.Secret, - HttpClient: clientdebug.Wrap(config.HTTPClient), + HttpClient: config.HTTPClient, UserAgent: fmt.Sprintf("%s %s", iaas.DefaultUserAgent, useragent.Get()), }, } @@ -117,7 +115,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.addTXTRecord(context.Background(), info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.addTXTRecord(info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("sakuracloud: %w", err) } @@ -129,7 +127,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.cleanupTXTRecord(context.Background(), info.EffectiveFQDN, info.Value) + err := d.cleanupTXTRecord(info.EffectiveFQDN, info.Value) if err != nil { return fmt.Errorf("sakuracloud: %w", err) } @@ -171,7 +169,6 @@ func newCaller(opts *api.CallerOptions) iaas.APICaller { if strings.HasSuffix(opts.APIRootURL, "/") { opts.APIRootURL = strings.TrimRight(opts.APIRootURL, "/") } - iaas.SakuraCloudAPIRoot = opts.APIRootURL } diff --git a/providers/dns/sakuracloud/sakuracloud.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/sakuracloud/sakuracloud_test.go b/providers/dns/sakuracloud/sakuracloud_test.go index 789a27544..93cf20ea1 100644 --- a/providers/dns/sakuracloud/sakuracloud_test.go +++ b/providers/dns/sakuracloud/sakuracloud_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,7 +129,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,7 +142,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sakuracloud/wrapper.go b/providers/dns/sakuracloud/wrapper.go index ff0b78e09..0898ccd3b 100644 --- a/providers/dns/sakuracloud/wrapper.go +++ b/providers/dns/sakuracloud/wrapper.go @@ -14,11 +14,11 @@ import ( // see: https://github.com/go-acme/lego/pull/850 var mu sync.Mutex -func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl int) error { +func (d *DNSProvider) addTXTRecord(fqdn, value string, ttl int) error { mu.Lock() defer mu.Unlock() - zone, err := d.getHostedZone(ctx, fqdn) + zone, err := d.getHostedZone(fqdn) if err != nil { return err } @@ -35,7 +35,7 @@ func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl TTL: ttl, }) - _, err = d.client.UpdateSettings(ctx, zone.ID, &iaas.DNSUpdateSettingsRequest{ + _, err = d.client.UpdateSettings(context.Background(), zone.ID, &iaas.DNSUpdateSettingsRequest{ Records: records, SettingsHash: zone.SettingsHash, }) @@ -46,11 +46,11 @@ func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl return nil } -func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) error { +func (d *DNSProvider) cleanupTXTRecord(fqdn, value string) error { mu.Lock() defer mu.Unlock() - zone, err := d.getHostedZone(ctx, fqdn) + zone, err := d.getHostedZone(fqdn) if err != nil { return err } @@ -61,7 +61,6 @@ func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) } var updRecords iaas.DNSRecords - for _, r := range zone.Records { if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { //nolint:staticcheck // Clearer without De Morgan's law. updRecords = append(updRecords, r) @@ -72,8 +71,7 @@ func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) Records: updRecords, SettingsHash: zone.SettingsHash, } - - _, err = d.client.UpdateSettings(ctx, zone.ID, settings) + _, err = d.client.UpdateSettings(context.Background(), zone.ID, settings) if err != nil { return fmt.Errorf("API call failed: %w", err) } @@ -81,7 +79,7 @@ func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) return nil } -func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*iaas.DNS, error) { +func (d *DNSProvider) getHostedZone(domain string) (*iaas.DNS, error) { authZone, err := dns01.FindZoneByFqdn(domain) if err != nil { return nil, fmt.Errorf("could not find zone: %w", err) @@ -95,7 +93,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*iaas.D }, } - res, err := d.client.Find(ctx, conditions) + res, err := d.client.Find(context.Background(), conditions) if err != nil { if iaas.IsNotFoundError(err) { return nil, fmt.Errorf("zone %s not found on SakuraCloud DNS: %w", zoneName, err) diff --git a/providers/dns/sakuracloud/wrapper_test.go b/providers/dns/sakuracloud/wrapper_test.go index 7432c67a6..15eb19618 100644 --- a/providers/dns/sakuracloud/wrapper_test.go +++ b/providers/dns/sakuracloud/wrapper_test.go @@ -44,7 +44,6 @@ func createDummyZone(t *testing.T, caller iaas.APICaller) { if zone.Name == "example.com" { err = dnsOp.Delete(ctx, zone.ID) require.NoError(t, err) - break } } @@ -65,12 +64,10 @@ func TestDNSProvider_addAndCleanupRecords(t *testing.T) { require.NoError(t, err) t.Run("addTXTRecord", func(t *testing.T) { - ctx := t.Context() - - err = p.addTXTRecord(ctx, "test.example.com.", "dummyValue", 10) + err = p.addTXTRecord("test.example.com.", "dummyValue", 10) require.NoError(t, err) - updZone, e := p.getHostedZone(ctx, "test.example.com.") + updZone, e := p.getHostedZone("test.example.com.") require.NoError(t, e) require.NotNil(t, updZone) @@ -78,12 +75,10 @@ func TestDNSProvider_addAndCleanupRecords(t *testing.T) { }) t.Run("cleanupTXTRecord", func(t *testing.T) { - ctx := t.Context() - - err = p.cleanupTXTRecord(ctx, "test.example.com.", "dummyValue") + err = p.cleanupTXTRecord("test.example.com.", "dummyValue") require.NoError(t, err) - updZone, e := p.getHostedZone(ctx, "test.example.com.") + updZone, e := p.getHostedZone("test.example.com.") require.NoError(t, e) require.NotNil(t, updZone) @@ -97,7 +92,6 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { dummyRecordCount := 10 var providers []*DNSProvider - for range dummyRecordCount { config := NewDefaultConfig() config.Token = "token3" @@ -114,11 +108,9 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { t.Run("addTXTRecord", func(t *testing.T) { wg.Add(len(providers)) - ctx := t.Context() - for i, p := range providers { go func(j int, client *DNSProvider) { - err := client.addTXTRecord(ctx, fmt.Sprintf("test%d.example.com.", j), "dummyValue", 10) + err := client.addTXTRecord(fmt.Sprintf("test%d.example.com.", j), "dummyValue", 10) require.NoError(t, err) wg.Done() }(i, p) @@ -126,7 +118,7 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { wg.Wait() - updZone, err := providers[0].getHostedZone(ctx, "example.com.") + updZone, err := providers[0].getHostedZone("example.com.") require.NoError(t, err) require.NotNil(t, updZone) @@ -136,11 +128,9 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { t.Run("cleanupTXTRecord", func(t *testing.T) { wg.Add(len(providers)) - ctx := t.Context() - for i, p := range providers { go func(i int, client *DNSProvider) { - err := client.cleanupTXTRecord(ctx, fmt.Sprintf("test%d.example.com.", i), "dummyValue") + err := client.cleanupTXTRecord(fmt.Sprintf("test%d.example.com.", i), "dummyValue") require.NoError(t, err) wg.Done() }(i, p) @@ -148,7 +138,7 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { wg.Wait() - updZone, err := providers[0].getHostedZone(ctx, "example.com.") + updZone, err := providers[0].getHostedZone("example.com.") require.NoError(t, err) require.NotNil(t, updZone) diff --git a/providers/dns/scaleway/scaleway.go b/providers/dns/scaleway/scaleway.go index 9d08f93b9..5976e77a2 100644 --- a/providers/dns/scaleway/scaleway.go +++ b/providers/dns/scaleway/scaleway.go @@ -5,7 +5,6 @@ package scaleway import ( "errors" "fmt" - "net/http" "strconv" "strings" "time" @@ -13,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" scwdomain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" "github.com/scaleway/scaleway-sdk-go/scw" @@ -34,7 +32,6 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const ( @@ -50,14 +47,12 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - ProjectID string - Token string // TODO(ldez) rename to SecretKey in the next major. - AccessKey string - + ProjectID string + Token string // TODO(ldez) rename to SecretKey in the next major. + AccessKey string PropagationTimeout time.Duration PollingInterval time.Duration TTL int - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -67,9 +62,6 @@ func NewDefaultConfig() *Config { TTL: env.GetOneWithFallback(EnvTTL, minTTL, strconv.Atoi, altEnvName(EnvTTL)), PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, defaultPropagationTimeout, env.ParseSecond, altEnvName(EnvPropagationTimeout)), PollingInterval: env.GetOneWithFallback(EnvPollingInterval, defaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } @@ -115,10 +107,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { scw.WithUserAgent(useragent.Get()), } - if config.HTTPClient != nil { - configuration = append(configuration, scw.WithHTTPClient(clientdebug.Wrap(config.HTTPClient))) - } - if config.ProjectID != "" { configuration = append(configuration, scw.WithDefaultProjectID(config.ProjectID)) } diff --git a/providers/dns/scaleway/scaleway.toml b/providers/dns/scaleway/scaleway.toml index 8b556e8b1..21839e061 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] @@ -18,7 +18,6 @@ lego --dns scaleway -d '*.example.com' -d example.com run SCW_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" SCW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" SCW_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" - SCW_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.scaleway.com/en/products/domain/dns/api/" diff --git a/providers/dns/scaleway/scaleway_test.go b/providers/dns/scaleway/scaleway_test.go index b683d751a..bf950e84e 100644 --- a/providers/dns/scaleway/scaleway_test.go +++ b/providers/dns/scaleway/scaleway_test.go @@ -41,7 +41,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,7 +105,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -120,7 +118,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index 63ddd81ac..c5da2215f 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -4,9 +4,11 @@ package selectel import ( + "context" "errors" "fmt" "net/http" + "net/url" "time" "github.com/go-acme/lego/v4/challenge" @@ -28,16 +30,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 +59,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 +83,89 @@ 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 + } + + 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..0e2de2dbe 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" ) @@ -37,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -47,7 +45,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 +76,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 +91,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) } @@ -106,7 +106,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -120,7 +119,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 1fcb48583..ca0a9107d 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -11,9 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" - "github.com/miekg/dns" selectelapi "github.com/selectel/domains-go/pkg/v2" "github.com/selectel/go-selvpcclient/v4/selvpcclient" "golang.org/x/net/idna" @@ -22,14 +20,11 @@ import ( const ( envNamespace = "SELECTELV2_" - EnvBaseURL = envNamespace + "BASE_URL" - EnvUsernameOS = envNamespace + "USERNAME" - EnvPasswordOS = envNamespace + "PASSWORD" - EnvDomainName = envNamespace + "ACCOUNT_ID" - EnvProjectID = envNamespace + "PROJECT_ID" - EnvAuthRegion = envNamespace + "AUTH_REGION" - EnvAuthURL = envNamespace + "AUTH_URL" - EnvUserDomainName = envNamespace + "USER_DOMAIN_NAME" + EnvBaseURL = envNamespace + "BASE_URL" + EnvUsernameOS = envNamespace + "USERNAME" + EnvPasswordOS = envNamespace + "PASSWORD" + EnvAccount = envNamespace + "ACCOUNT_ID" + EnvProjectID = envNamespace + "PROJECT_ID" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -38,12 +33,7 @@ const ( ) const ( - defaultBaseURL = "https://api.selectel.ru/domains/v2" - defaultAuthRegion = "ru-1" - defaultAuthURL = "https://cloud.api.selcloud.ru/identity/v3/" -) - -const ( + defaultBaseURL = "https://api.selectel.ru/domains/v2" defaultTTL = 60 defaultPropagationTimeout = 120 * time.Second defaultPollingInterval = 5 * time.Second @@ -56,15 +46,11 @@ var errNotFound = errors.New("rrset not found") // Config is used to configure the creation of the DNSProvider. type Config struct { - BaseURL string - Username string - Password string - DomainName string - ProjectID string - AuthURL string - AuthRegion string - UserDomainName string - + BaseURL string + Username string + Password string + Account string + ProjectID string TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -74,10 +60,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), - AuthRegion: env.GetOrDefaultString(EnvAuthRegion, defaultAuthRegion), - AuthURL: env.GetOrDefaultString(EnvAuthURL, defaultAuthURL), - + BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), @@ -94,7 +77,7 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Selectel Domains APIv2. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsernameOS, EnvPasswordOS, EnvDomainName, EnvProjectID) + values, err := env.Get(EnvUsernameOS, EnvPasswordOS, EnvAccount, EnvProjectID) if err != nil { return nil, fmt.Errorf("selectelv2: %w", err) } @@ -102,9 +85,8 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Username = values[EnvUsernameOS] config.Password = values[EnvPasswordOS] - config.DomainName = values[EnvDomainName] + config.Account = values[EnvAccount] config.ProjectID = values[EnvProjectID] - config.UserDomainName = env.GetOrDefaultString(EnvUserDomainName, "") return NewDNSProviderConfig(config) } @@ -123,8 +105,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("selectelv2: missing password") } - if config.DomainName == "" { - return nil, errors.New("selectelv2: missing account ID") + if config.Account == "" { + return nil, errors.New("selectelv2: missing account") } if config.ProjectID == "" { @@ -135,7 +117,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { useragent.SetHeader(headers) return &DNSProvider{ - baseClient: selectelapi.NewClient(config.BaseURL, clientdebug.Wrap(config.HTTPClient), headers), + baseClient: selectelapi.NewClient(config.BaseURL, config.HTTPClient, headers), config: config, }, nil } @@ -150,7 +132,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, _, keyAuth string) error { ctx := context.Background() - client, err := d.authorize(ctx) + client, err := d.authorize() if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -197,7 +179,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { ctx := context.Background() - client, err := d.authorize(ctx) + client, err := d.authorize() if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -238,8 +220,8 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { return nil } -func (d *DNSProvider) authorize(ctx context.Context) (*clientWrapper, error) { - token, err := obtainOpenstackToken(ctx, d.config) +func (d *DNSProvider) authorize() (*clientWrapper, error) { + token, err := obtainOpenstackToken(d.config) if err != nil { return nil, err } @@ -252,16 +234,12 @@ func (d *DNSProvider) authorize(ctx context.Context) (*clientWrapper, error) { }, nil } -func obtainOpenstackToken(ctx context.Context, config *Config) (string, error) { +func obtainOpenstackToken(config *Config) (string, error) { vpcClient, err := selvpcclient.NewClient(&selvpcclient.ClientOptions{ - Context: ctx, - DomainName: config.DomainName, - AuthURL: config.AuthURL, - AuthRegion: config.AuthRegion, Username: config.Username, Password: config.Password, + UserDomainName: config.Account, ProjectID: config.ProjectID, - UserDomainName: config.UserDomainName, }) if err != nil { return "", fmt.Errorf("new VPC client: %w", err) @@ -288,7 +266,7 @@ func (w *clientWrapper) getZone(ctx context.Context, name string) (*selectelapi. } for _, zone := range zones.GetItems() { - if zone.Name == dns.Fqdn(unicodeName) { + if zone.Name == dns01.ToFqdn(unicodeName) { return zone, nil } } @@ -297,10 +275,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) { @@ -317,7 +295,7 @@ func (w *clientWrapper) getRRset(ctx context.Context, name, zoneID string) (*sel } for _, rrset := range resp.GetItems() { - if rrset.Name == dns.Fqdn(unicodeName) { + if rrset.Name == dns01.ToFqdn(unicodeName) { return rrset, nil } } diff --git a/providers/dns/selectelv2/selectelv2.toml b/providers/dns/selectelv2/selectelv2.toml index 480c7756e..4993cb0f8 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] @@ -20,9 +20,6 @@ lego --dns selectelv2 -d '*.example.com' -d example.com run SELECTELV2_PROJECT_ID = "Cloud project ID (UUID)" [Configuration.Additional] SELECTELV2_BASE_URL = "API endpoint URL" - SELECTELV2_AUTH_REGION = "Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1')" - SELECTELV2_AUTH_URL = "Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/')" - SELECTELV2_USER_DOMAIN_NAME = "To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID)" SELECTELV2_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" SELECTELV2_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" SELECTELV2_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" diff --git a/providers/dns/selectelv2/selectelv2_test.go b/providers/dns/selectelv2/selectelv2_test.go index 2627fa023..4859b9932 100644 --- a/providers/dns/selectelv2/selectelv2_test.go +++ b/providers/dns/selectelv2/selectelv2_test.go @@ -11,15 +11,7 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest( - EnvUsernameOS, - EnvPasswordOS, - EnvDomainName, - EnvUserDomainName, - EnvProjectID, - EnvAuthRegion, - EnvAuthURL, -). +var envTest = tester.NewEnvTest(EnvUsernameOS, EnvPasswordOS, EnvAccount, EnvProjectID). WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { @@ -33,7 +25,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvUsernameOS: "someName", EnvPasswordOS: "qwerty", - EnvDomainName: "1", + EnvAccount: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, }, @@ -41,7 +33,7 @@ func TestNewDNSProvider(t *testing.T) { desc: "missing username", envVars: map[string]string{ EnvPasswordOS: "qwerty", - EnvDomainName: "1", + EnvAccount: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_USERNAME", @@ -50,7 +42,7 @@ func TestNewDNSProvider(t *testing.T) { desc: "missing password", envVars: map[string]string{ EnvUsernameOS: "someName", - EnvDomainName: "1", + EnvAccount: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_PASSWORD", @@ -69,7 +61,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvUsernameOS: "someName", EnvPasswordOS: "qwerty", - EnvDomainName: "1", + EnvAccount: "1", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_PROJECT_ID", }, @@ -78,7 +70,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -132,7 +123,7 @@ func TestNewDNSProviderConfig(t *testing.T) { username: "user", password: "secret", projectID: "111a11111aaa11aa1a11aaa11111aa1a", - expected: "selectelv2: missing account ID", + expected: "selectelv2: missing account", }, { desc: "missing projectID", @@ -148,7 +139,7 @@ func TestNewDNSProviderConfig(t *testing.T) { config := NewDefaultConfig() config.Username = test.username config.Password = test.password - config.DomainName = test.account + config.Account = test.account config.ProjectID = test.projectID p, err := NewDNSProviderConfig(config) @@ -171,7 +162,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -185,7 +175,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selfhostde/mapping.go b/providers/dns/selfhostde/mapping.go index fe11ceda1..0984419ef 100644 --- a/providers/dns/selfhostde/mapping.go +++ b/providers/dns/selfhostde/mapping.go @@ -88,10 +88,8 @@ func parseLine(line string) (string, *Seq, error) { name, rawIDs := line[:idx], line[idx+1:] - var ( - ids []string - count int - ) + var ids []string + var count int for { idx, err = safeIndex(rawIDs, recordSep) diff --git a/providers/dns/selfhostde/selfhostde.go b/providers/dns/selfhostde/selfhostde.go index 035cd5363..0fea9f1d0 100644 --- a/providers/dns/selfhostde/selfhostde.go +++ b/providers/dns/selfhostde/selfhostde.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/selfhostde/internal" ) @@ -133,8 +132,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -176,7 +173,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("selfhostde: unknown record ID for %q", dns01.UnFqdn(info.EffectiveFQDN)) } @@ -186,9 +182,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/selfhostde/selfhostde_test.go b/providers/dns/selfhostde/selfhostde_test.go index 7c12195fa..1161049b0 100644 --- a/providers/dns/selfhostde/selfhostde_test.go +++ b/providers/dns/selfhostde/selfhostde_test.go @@ -71,7 +71,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -186,7 +185,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -200,7 +198,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/servercow/internal/client.go b/providers/dns/servercow/internal/client.go index e15237201..3695b0979 100644 --- a/providers/dns/servercow/internal/client.go +++ b/providers/dns/servercow/internal/client.go @@ -47,7 +47,6 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var records []Record - err = c.do(req, &records) if err != nil { return nil, err @@ -66,7 +65,6 @@ func (c *Client) CreateUpdateRecord(ctx context.Context, domain string, data Rec } var msg Message - err = c.do(req, &msg) if err != nil { return nil, err @@ -89,7 +87,6 @@ func (c *Client) DeleteRecord(ctx context.Context, domain string, data Record) ( } var msg Message - err = c.do(req, &msg) if err != nil { return nil, err @@ -171,7 +168,6 @@ func unmarshal(raw []byte, v any) error { } var apiErr Message - errU := json.Unmarshal(raw, &apiErr) if errU != nil { return fmt.Errorf("unmarshaling %T error: %w: %s", v, err, string(raw)) diff --git a/providers/dns/servercow/internal/client_test.go b/providers/dns/servercow/internal/client_test.go index 3733ccad1..2092bf907 100644 --- a/providers/dns/servercow/internal/client_test.go +++ b/providers/dns/servercow/internal/client_test.go @@ -29,10 +29,10 @@ func mockBuilder() *servermock.Builder[*Client] { func TestClient_GetRecords(t *testing.T) { client := mockBuilder(). - Route("GET /example.com", servermock.ResponseFromFixture("records-01.json")). + Route("GET /lego.wtf", servermock.ResponseFromFixture("records-01.json")). Build(t) - records, err := client.GetRecords(t.Context(), "example.com") + records, err := client.GetRecords(t.Context(), "lego.wtf") require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -46,10 +46,10 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := mockBuilder(). - Route("GET /example.com", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). + Route("GET /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). Build(t) - records, err := client.GetRecords(t.Context(), "example.com") + records, err := client.GetRecords(t.Context(), "lego.wtf") require.Error(t, err) assert.Nil(t, records) @@ -57,7 +57,7 @@ func TestClient_GetRecords_error(t *testing.T) { func TestClient_CreateUpdateRecord(t *testing.T) { client := mockBuilder(). - Route("POST /example.com", + Route("POST /lego.wtf", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT","ttl":30,"content":["aaa","bbb"]}`)). Build(t) @@ -69,7 +69,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { Content: Value{"aaa", "bbb"}, } - msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) + msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -78,7 +78,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { func TestClient_CreateUpdateRecord_error(t *testing.T) { client := mockBuilder(). - Route("POST /example.com", + Route("POST /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -86,7 +86,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) + msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) require.Error(t, err) assert.Nil(t, msg) @@ -94,7 +94,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). - Route("DELETE /example.com", + Route("DELETE /lego.wtf", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT"}`)). Build(t) @@ -104,7 +104,7 @@ func TestClient_DeleteRecord(t *testing.T) { Type: "TXT", } - msg, err := client.DeleteRecord(t.Context(), "example.com", record) + msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -113,7 +113,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := mockBuilder(). - Route("DELETE /example.com", + Route("DELETE /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -121,7 +121,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.DeleteRecord(t.Context(), "example.com", record) + msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) require.Error(t, err) assert.Nil(t, msg) diff --git a/providers/dns/servercow/internal/types.go b/providers/dns/servercow/internal/types.go index 9a951e806..5a8fb6ff8 100644 --- a/providers/dns/servercow/internal/types.go +++ b/providers/dns/servercow/internal/types.go @@ -43,7 +43,6 @@ func (v *Value) UnmarshalJSON(b []byte) error { } *v = append(*v, s) - return nil } diff --git a/providers/dns/servercow/servercow.go b/providers/dns/servercow/servercow.go index 557c6b1ec..56f89f900 100644 --- a/providers/dns/servercow/servercow.go +++ b/providers/dns/servercow/servercow.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/servercow/internal" ) @@ -86,8 +85,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -140,7 +137,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("servercow: failed to update TXT records: %w", err) } - return nil } @@ -195,7 +191,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("servercow: failed to delete TXT records: %w", err) } - return nil } diff --git a/providers/dns/servercow/servercow.toml b/providers/dns/servercow/servercow.toml index 5cbacbb88..655257b84 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] @@ -21,4 +21,4 @@ lego --dns servercow -d '*.example.com' -d example.com run SERVERCOW_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://wiki.servercow.de/en/domains/dns_api/api-syntax/" + API = "https://cp.servercow.de/client/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/" diff --git a/providers/dns/servercow/servercow_test.go b/providers/dns/servercow/servercow_test.go index f2328fe1a..1c3facad9 100644 --- a/providers/dns/servercow/servercow_test.go +++ b/providers/dns/servercow/servercow_test.go @@ -57,7 +57,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,7 +129,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,7 +142,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/shellrent/internal/client.go b/providers/dns/shellrent/internal/client.go index a70ff5452..8ec02bfc0 100644 --- a/providers/dns/shellrent/internal/client.go +++ b/providers/dns/shellrent/internal/client.go @@ -114,7 +114,6 @@ func (c *Client) GetDomainDetails(ctx context.Context, domainID int) (*DomainDet if result.Code != 0 { return nil, result.Base } - return result.Data, nil } @@ -138,7 +137,6 @@ func (c *Client) CreateRecord(ctx context.Context, domainID int, record Record) if result.Code != 0 { return 0, result.Base } - return result.Data.ID.Value(), nil } @@ -221,7 +219,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response Base - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/shellrent/internal/types.go b/providers/dns/shellrent/internal/types.go index 6bdd82330..a27b06347 100644 --- a/providers/dns/shellrent/internal/types.go +++ b/providers/dns/shellrent/internal/types.go @@ -7,7 +7,6 @@ import ( type Response[T any] struct { Base - Data T `json:"data"` } @@ -58,7 +57,6 @@ func (m *IntOrString) UnmarshalJSON(data []byte) error { raw := string(data) if data[0] == '"' { var err error - raw, err = strconv.Unquote(string(data)) if err != nil { return err diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index 0cd33e19a..488509a84 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/shellrent/internal" ) @@ -104,8 +103,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -162,7 +159,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() key, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("shellrent: unknown request key for '%s' '%s'", info.EffectiveFQDN, token) } @@ -172,10 +168,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/shellrent/shellrent_test.go b/providers/dns/shellrent/shellrent_test.go index 8c4e3f6bf..e5d529917 100644 --- a/providers/dns/shellrent/shellrent_test.go +++ b/providers/dns/shellrent/shellrent_test.go @@ -47,7 +47,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -118,7 +117,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -132,7 +130,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/simply/internal/client.go b/providers/dns/simply/internal/client.go index 0c0655463..74f5fe671 100644 --- a/providers/dns/simply/internal/client.go +++ b/providers/dns/simply/internal/client.go @@ -16,7 +16,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://api.simply.com/2/" +const defaultBaseURL = "https://api.simply.com/1/" // Client is a Simply.com API client. type Client struct { @@ -60,7 +60,6 @@ func (c *Client) GetRecords(ctx context.Context, zoneName string) ([]Record, err } result := &apiResponse[[]Record, json.RawMessage]{} - err = c.do(req, result) if err != nil { return nil, err @@ -79,7 +78,6 @@ func (c *Client) AddRecord(ctx context.Context, zoneName string, record Record) } result := &apiResponse[json.RawMessage, recordHeader]{} - err = c.do(req, result) if err != nil { return 0, err @@ -113,12 +111,10 @@ func (c *Client) DeleteRecord(ctx context.Context, zoneName string, id int64) er } func (c *Client) createEndpoint(zoneName, uri string) *url.URL { - return c.baseURL.JoinPath("my", "products", zoneName, "dns", "records", strings.TrimSuffix(uri, "/")) + return c.baseURL.JoinPath(c.accountName, c.apiKey, "my", "products", zoneName, "dns", "records", strings.TrimSuffix(uri, "/")) } func (c *Client) do(req *http.Request, result Response) error { - req.SetBasicAuth(c.accountName, c.apiKey) - resp, err := c.HTTPClient.Do(req) if err != nil { return errutils.NewHTTPDoError(req, err) diff --git a/providers/dns/simply/internal/client_test.go b/providers/dns/simply/internal/client_test.go index b0bdac6b3..83aa714bf 100644 --- a/providers/dns/simply/internal/client_test.go +++ b/providers/dns/simply/internal/client_test.go @@ -24,13 +24,12 @@ func mockBuilder() *servermock.Builder[*Client] { return client, nil }, - servermock.CheckHeader().WithJSONHeaders(). - WithBasicAuth("accountname", "apikey")) + servermock.CheckHeader().WithJSONHeaders()) } func TestClient_GetRecords(t *testing.T) { client := mockBuilder(). - Route("GET /my/products/azone01/dns/records", + Route("GET /accountname/apikey/my/products/azone01/dns/records", servermock.ResponseFromFixture("get_records.json")). Build(t) @@ -77,7 +76,7 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := mockBuilder(). - Route("GET /my/products/azone01/dns/records", + Route("GET /accountname/apikey/my/products/azone01/dns/records", servermock.ResponseFromFixture("bad_auth_error.json"). WithStatusCode(http.StatusBadRequest)). Build(t) @@ -90,7 +89,7 @@ func TestClient_GetRecords_error(t *testing.T) { func TestClient_AddRecord(t *testing.T) { client := mockBuilder(). - Route("POST /my/products/azone01/dns/records", + Route("POST /accountname/apikey/my/products/azone01/dns/records", servermock.ResponseFromFixture("add_record.json")). Build(t) @@ -110,7 +109,7 @@ func TestClient_AddRecord(t *testing.T) { func TestClient_AddRecord_error(t *testing.T) { client := mockBuilder(). - Route("POST /my/products/azone01/dns/records", + Route("POST /accountname/apikey/my/products/azone01/dns/records", servermock.ResponseFromFixture("bad_zone_error.json"). WithStatusCode(http.StatusNotFound)). Build(t) @@ -131,7 +130,7 @@ func TestClient_AddRecord_error(t *testing.T) { func TestClient_EditRecord(t *testing.T) { client := mockBuilder(). - Route("PUT /my/products/azone01/dns/records/123456789", + Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("success.json")). Build(t) @@ -149,7 +148,7 @@ func TestClient_EditRecord(t *testing.T) { func TestClient_EditRecord_error(t *testing.T) { client := mockBuilder(). - Route("PUT /my/products/azone01/dns/records/123456789", + Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("invalid_record_id.json"). WithStatusCode(http.StatusNotFound)). Build(t) @@ -168,7 +167,7 @@ func TestClient_EditRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). - Route("DELETE /my/products/azone01/dns/records/123456789", + Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("success.json")). Build(t) @@ -178,7 +177,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := mockBuilder(). - Route("DELETE /my/products/azone01/dns/records/123456789", + Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("invalid_record_id.json"). WithStatusCode(http.StatusNotFound)). Build(t) diff --git a/providers/dns/simply/simply.go b/providers/dns/simply/simply.go index fc3afd310..d2bfb1874 100644 --- a/providers/dns/simply/simply.go +++ b/providers/dns/simply/simply.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/simply/internal" ) @@ -100,8 +99,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -165,7 +162,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("simply: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/simply/simply.toml b/providers/dns/simply/simply.toml index a838e245a..2814fd955 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] @@ -22,4 +22,3 @@ lego --dns simply -d '*.example.com' -d example.com run [Links] API = "https://www.simply.com/en/docs/api/" - Spec = "https://generator.swagger.io/?url=https://api.simply.com/2/openapi.json#/" diff --git a/providers/dns/simply/simply_test.go b/providers/dns/simply/simply_test.go index e6de60d43..ace8e0b72 100644 --- a/providers/dns/simply/simply_test.go +++ b/providers/dns/simply/simply_test.go @@ -53,7 +53,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,7 +121,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -136,7 +134,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sonic/internal/client.go b/providers/dns/sonic/internal/client.go index cf8f7f067..3007a8248 100644 --- a/providers/dns/sonic/internal/client.go +++ b/providers/dns/sonic/internal/client.go @@ -83,7 +83,6 @@ func (c *Client) SetRecord(ctx context.Context, hostname, value string, ttl int) } r := APIResponse{} - err = json.Unmarshal(raw, &r) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/sonic/sonic.go b/providers/dns/sonic/sonic.go index 5bda2b533..80f5ea295 100644 --- a/providers/dns/sonic/sonic.go +++ b/providers/dns/sonic/sonic.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/sonic/internal" ) @@ -92,8 +91,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/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/sonic/sonic_test.go b/providers/dns/sonic/sonic_test.go index 7dc7fc586..f9087f8e3 100644 --- a/providers/dns/sonic/sonic_test.go +++ b/providers/dns/sonic/sonic_test.go @@ -49,7 +49,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,7 +119,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -134,7 +132,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/spaceship/internal/client.go b/providers/dns/spaceship/internal/client.go index e690fa467..f739e01ba 100644 --- a/providers/dns/spaceship/internal/client.go +++ b/providers/dns/spaceship/internal/client.go @@ -114,7 +114,6 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var result GetRecordsResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -151,7 +150,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/spaceship/spaceship.go b/providers/dns/spaceship/spaceship.go index e34c584c5..9e8f0158e 100644 --- a/providers/dns/spaceship/spaceship.go +++ b/providers/dns/spaceship/spaceship.go @@ -10,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/spaceship/internal" ) @@ -85,8 +84,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/spaceship/spaceship_test.go b/providers/dns/spaceship/spaceship_test.go index d4eb37d88..ba60cfa5e 100644 --- a/providers/dns/spaceship/spaceship_test.go +++ b/providers/dns/spaceship/spaceship_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/stackpath/internal/client.go b/providers/dns/stackpath/internal/client.go index 8a40a4093..bd11bf235 100644 --- a/providers/dns/stackpath/internal/client.go +++ b/providers/dns/stackpath/internal/client.go @@ -25,13 +25,13 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(stackID string, hc *http.Client) *Client { +func NewClient(ctx context.Context, stackID, clientID, clientSecret string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ baseURL: baseURL, stackID: stackID, - httpClient: hc, + httpClient: createOAuthClient(ctx, clientID, clientSecret), } } @@ -55,7 +55,6 @@ func (c *Client) GetZones(ctx context.Context, domain string) (*Zone, error) { req.URL.RawQuery = query.Encode() var zones Zones - err = c.do(req, &zones) if err != nil { return nil, err @@ -83,7 +82,6 @@ func (c *Client) GetZoneRecords(ctx context.Context, name string, zone *Zone) ([ req.URL.RawQuery = query.Encode() var records Records - err = c.do(req, &records) if err != nil { return nil, err @@ -179,7 +177,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &ErrorResponse{} - err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/stackpath/internal/client_test.go b/providers/dns/stackpath/internal/client_test.go index baac84397..5195aa973 100644 --- a/providers/dns/stackpath/internal/client_test.go +++ b/providers/dns/stackpath/internal/client_test.go @@ -1,6 +1,7 @@ package internal import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -14,8 +15,8 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient("STACK_ID", server.Client()) - + client := NewClient(context.Background(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") + client.httpClient = server.Client() client.baseURL, _ = url.Parse(server.URL + "/") return client, nil diff --git a/providers/dns/stackpath/internal/identity.go b/providers/dns/stackpath/internal/identity.go index fa3e9df07..5c6e6ab17 100644 --- a/providers/dns/stackpath/internal/identity.go +++ b/providers/dns/stackpath/internal/identity.go @@ -9,7 +9,7 @@ import ( const defaultAuthURL = "https://gateway.stackpath.com/identity/v1/oauth2/token" -func CreateOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { +func createOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { config := &clientcredentials.Config{ TokenURL: defaultAuthURL, ClientID: clientID, diff --git a/providers/dns/stackpath/stackpath.go b/providers/dns/stackpath/stackpath.go index 2e193b8a9..6d12ce875 100644 --- a/providers/dns/stackpath/stackpath.go +++ b/providers/dns/stackpath/stackpath.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/stackpath/internal" ) @@ -87,14 +86,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("stackpath: stack id missing") } - return &DNSProvider{ - config: config, - client: internal.NewClient(config.StackID, - clientdebug.Wrap( - internal.CreateOAuthClient(context.Background(), config.ClientID, config.ClientSecret), - ), - ), - }, nil + client := internal.NewClient(context.Background(), config.StackID, config.ClientID, config.ClientSecret) + + return &DNSProvider{config: config, client: client}, nil } // Present creates a TXT record to fulfill the dns-01 challenge. 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/stackpath/stackpath_test.go b/providers/dns/stackpath/stackpath_test.go index a4b959222..f8b83140f 100644 --- a/providers/dns/stackpath/stackpath_test.go +++ b/providers/dns/stackpath/stackpath_test.go @@ -72,7 +72,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -138,7 +137,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -152,7 +150,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/technitium/internal/client.go index 965638b1d..a68008d34 100644 --- a/providers/dns/technitium/internal/client.go +++ b/providers/dns/technitium/internal/client.go @@ -125,7 +125,6 @@ func (c *Client) newFormRequest(ctx context.Context, endpoint *url.URL, payload if payload != nil { var err error - values, err = querystring.Values(payload) if err != nil { return nil, fmt.Errorf("failed to create request body: %w", err) @@ -150,7 +149,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse[any] - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/technitium/technitium.go b/providers/dns/technitium/technitium.go index fc60c09ad..b2cf2d701 100644 --- a/providers/dns/technitium/technitium.go +++ b/providers/dns/technitium/technitium.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/technitium/internal" ) @@ -88,8 +87,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/technitium/technitium_test.go b/providers/dns/technitium/technitium_test.go index 4eee530fd..da50b6fe6 100644 --- a/providers/dns/technitium/technitium_test.go +++ b/providers/dns/technitium/technitium_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/tencentcloud/tencentcloud.toml b/providers/dns/tencentcloud/tencentcloud.toml index 50f4ee9d5..75a950a49 100644 --- a/providers/dns/tencentcloud/tencentcloud.toml +++ b/providers/dns/tencentcloud/tencentcloud.toml @@ -1,13 +1,13 @@ Name = "Tencent Cloud DNS" Description = '''''' -URL = "https://cloud.tencent.com/product/dns" +URL = "https://cloud.tencent.com/product/cns" Code = "tencentcloud" 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/tencentcloud/tencentcloud_test.go b/providers/dns/tencentcloud/tencentcloud_test.go index ce6358174..c5a2fd974 100644 --- a/providers/dns/tencentcloud/tencentcloud_test.go +++ b/providers/dns/tencentcloud/tencentcloud_test.go @@ -55,7 +55,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -128,7 +127,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,7 +140,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/tencentcloud/wrapper.go b/providers/dns/tencentcloud/wrapper.go index 6a66bc1c6..6fdb7f899 100644 --- a/providers/dns/tencentcloud/wrapper.go +++ b/providers/dns/tencentcloud/wrapper.go @@ -38,7 +38,6 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*dnspod } var hostedZone *dnspod.DomainListItem - for _, zone := range domains { unfqdn := dns01.UnFqdn(authZone) if *zone.Name == unfqdn || *zone.Punycode == unfqdn { @@ -74,7 +73,6 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, zone *dnspod.DomainLis return nil, nil } } - return nil, err } diff --git a/providers/dns/timewebcloud/internal/client.go b/providers/dns/timewebcloud/internal/client.go index ec3c8703d..b3030861e 100644 --- a/providers/dns/timewebcloud/internal/client.go +++ b/providers/dns/timewebcloud/internal/client.go @@ -49,7 +49,6 @@ func (c *Client) CreateRecord(ctx context.Context, zone string, record DNSRecord } respData := &CreateRecordResponse{} - err = c.do(req, respData) if err != nil { return nil, err @@ -128,7 +127,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response ErrorResponse - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/timewebcloud/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..a2ab0dd65 100644 --- a/providers/dns/timewebcloud/timewebcloud.go +++ b/providers/dns/timewebcloud/timewebcloud.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/timewebcloud/internal" ) @@ -82,11 +81,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("timewebcloud: authentication token is missing") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), - ), - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken)) return &DNSProvider{ config: config, @@ -110,10 +105,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) @@ -140,7 +140,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("timewebcloud: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/timewebcloud/timewebcloud.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/timewebcloud/timewebcloud_test.go b/providers/dns/timewebcloud/timewebcloud_test.go index 26e107578..cd3e2e26f 100644 --- a/providers/dns/timewebcloud/timewebcloud_test.go +++ b/providers/dns/timewebcloud/timewebcloud_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -98,7 +97,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -112,7 +110,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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.go b/providers/dns/transip/transip.go index bc2913aa4..779704a21 100644 --- a/providers/dns/transip/transip.go +++ b/providers/dns/transip/transip.go @@ -4,7 +4,6 @@ package transip import ( "errors" "fmt" - "net/http" "time" "github.com/go-acme/lego/v4/challenge" @@ -24,7 +23,6 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -36,7 +34,6 @@ type Config struct { PropagationTimeout time.Duration PollingInterval time.Duration TTL int64 - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -45,9 +42,6 @@ func NewDefaultConfig() *Config { TTL: int64(env.GetOrDefaultInt(EnvTTL, 10)), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } @@ -79,19 +73,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("transip: the configuration of the DNS provider is nil") } - cfg := gotransip.ClientConfiguration{ + client, err := gotransip.NewClient(gotransip.ClientConfiguration{ AccountName: config.AccountName, PrivateKeyPath: config.PrivateKeyPath, - } - - if config.HTTPClient != nil { - cfg.HTTPClient = config.HTTPClient - } else { - // Uses an explicit default HTTP client because the desec.NewDefaultClientOptions uses the http.DefaultClient. - cfg.HTTPClient = &http.Client{Timeout: 30 * time.Second} - } - - client, err := gotransip.NewClient(cfg) + }) if err != nil { return nil, fmt.Errorf("transip: %w", err) } @@ -168,7 +153,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err = d.repository.RemoveDNSEntry(domainName, entry); err != nil { return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %w", err) } - return nil } } diff --git a/providers/dns/transip/transip.toml b/providers/dns/transip/transip.toml index bf7d58ee3..0625f819b 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] @@ -18,7 +18,6 @@ lego --dns transip -d '*.example.com' -d example.com run TRANSIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" TRANSIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" TRANSIP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" - TRANSIP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.transip.eu/rest/docs.html" diff --git a/providers/dns/transip/transip_test.go b/providers/dns/transip/transip_test.go index 3c6e86657..b42753680 100644 --- a/providers/dns/transip/transip_test.go +++ b/providers/dns/transip/transip_test.go @@ -58,7 +58,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -80,7 +79,6 @@ func TestNewDNSProvider(t *testing.T) { // Therefore, we test if the error type is the same. t.Run("could not open private key path", func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(map[string]string{ @@ -158,7 +156,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -172,7 +169,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ultradns/ultradns.go b/providers/dns/ultradns/ultradns.go index da76c56f4..0515cbaae 100644 --- a/providers/dns/ultradns/ultradns.go +++ b/providers/dns/ultradns/ultradns.go @@ -4,7 +4,6 @@ package ultradns import ( "errors" "fmt" - "net/http" "time" "github.com/go-acme/lego/v4/challenge" @@ -122,7 +121,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { RecordType: "TXT", } - resp, _, _ := recordService.Read(rrSetKeyData) + res, _, _ := recordService.Read(rrSetKeyData) rrSetData := &rrset.RRSet{ OwnerName: info.EffectiveFQDN, @@ -131,12 +130,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { RData: []string{info.Value}, } - if resp != nil && resp.StatusCode == http.StatusOK { + if res != nil && res.StatusCode == 200 { _, err = recordService.Update(rrSetKeyData, rrSetData) } else { _, err = recordService.Create(rrSetKeyData, rrSetData) } - if err != nil { return fmt.Errorf("ultradns: %w", err) } diff --git a/providers/dns/ultradns/ultradns.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/ultradns/ultradns_test.go b/providers/dns/ultradns/ultradns_test.go index 464bc51cd..eefa63ec3 100644 --- a/providers/dns/ultradns/ultradns_test.go +++ b/providers/dns/ultradns/ultradns_test.go @@ -177,7 +177,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) 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/internal/client.go b/providers/dns/variomedia/internal/client.go index 0e4ef9518..8c2a124cc 100644 --- a/providers/dns/variomedia/internal/client.go +++ b/providers/dns/variomedia/internal/client.go @@ -52,7 +52,6 @@ func (c *Client) CreateDNSRecord(ctx context.Context, record DNSRecord) (*Create } var result CreateDNSRecordResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -72,7 +71,6 @@ func (c *Client) DeleteDNSRecord(ctx context.Context, id string) (*DeleteRecordR } var result DeleteRecordResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -92,7 +90,6 @@ func (c *Client) GetJob(ctx context.Context, id string) (*GetJobResponse, error) } var result GetJobResponse - err = c.do(req, &result) if err != nil { return nil, err @@ -156,7 +153,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError - err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 2d12fd975..548d8bab8 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -10,13 +10,11 @@ import ( "sync" "time" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/variomedia/internal" ) @@ -93,8 +91,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, @@ -165,7 +161,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("variomedia: unknown record ID for '%s'", info.EffectiveFQDN) } @@ -180,30 +175,18 @@ 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 } func (d *DNSProvider) waitJob(ctx context.Context, domain, id string) error { - return wait.Retry(ctx, - func() error { - result, err := d.client.GetJob(ctx, id) - if err != nil { - return fmt.Errorf("apply change on %s: %w", domain, err) - } + return wait.For("variomedia: apply change on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { + result, err := d.client.GetJob(ctx, id) + if err != nil { + return false, err + } - log.Infof("variomedia: [%s] %s: %s %s", domain, result.Data.ID, result.Data.Attributes.JobType, result.Data.Attributes.Status) + log.Infof("variomedia: [%s] %s: %s %s", domain, result.Data.ID, result.Data.Attributes.JobType, result.Data.Attributes.Status) - if result.Data.Attributes.Status != "done" { - return fmt.Errorf("apply change on %s: status: %s", domain, result.Data.Attributes.Status) - } - - return nil - }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), - ) + return result.Data.Attributes.Status == "done", 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/variomedia/variomedia_test.go b/providers/dns/variomedia/variomedia_test.go index 552419fd0..305646070 100644 --- a/providers/dns/variomedia/variomedia_test.go +++ b/providers/dns/variomedia/variomedia_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,7 +91,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,7 +104,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index 9f1f189c3..824f727eb 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -2,17 +2,14 @@ package vegadns import ( - "context" "errors" "fmt" - "net/http" "time" + vegaClient "github.com/OpenDNS/vegadns2client" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/nrdcg/vegadns" ) // Environment variables names. @@ -26,21 +23,18 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - BaseURL string - APIKey string - APISecret string - + BaseURL string + APIKey string + APISecret string PropagationTimeout time.Duration PollingInterval time.Duration TTL int - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -49,16 +43,13 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, 10), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 12*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, time.Minute), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *vegadns.Client + client vegaClient.VegaDNSClient } // NewDNSProvider returns a DNSProvider instance configured for VegaDNS. @@ -84,21 +75,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vegadns: the configuration of the DNS provider is nil") } - if config.HTTPClient == nil { - config.HTTPClient = &http.Client{Timeout: 30 * time.Second} - } + vega := vegaClient.NewVegaDNSClient(config.BaseURL) + vega.APIKey = config.APIKey + vega.APISecret = config.APISecret - config.HTTPClient = clientdebug.Wrap(config.HTTPClient) - - client, err := vegadns.NewClient(config.BaseURL, - vegadns.WithOAuth(config.APIKey, config.APISecret), - vegadns.WithHTTPClient(config.HTTPClient), - ) - if err != nil { - return nil, fmt.Errorf("vegadns: %w", err) - } - - return &DNSProvider{client: client, config: config}, nil + return &DNSProvider{client: vega, config: config}, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -109,71 +90,39 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - domainID, err := d.findDomainID(ctx, info.EffectiveFQDN) + _, domainID, err := d.client.GetAuthZone(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("vegadns: find domain ID for %s: %w", info.EffectiveFQDN, err) + return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in Present: %w", info.EffectiveFQDN, err) } - err = d.client.CreateTXTRecord(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) + err = d.client.CreateTXT(domainID, info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { - return fmt.Errorf("vegadns: create TXT record: %w", err) + return fmt.Errorf("vegadns: %w", err) } - return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) - domainID, err := d.findDomainID(ctx, info.EffectiveFQDN) + _, domainID, err := d.client.GetAuthZone(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("vegadns: find domain ID for %s: %w", info.EffectiveFQDN, err) + return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in CleanUp: %w", info.EffectiveFQDN, err) } - recordID, err := d.findRecordID(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN)) + txt := dns01.UnFqdn(info.EffectiveFQDN) + + recordID, err := d.client.GetRecordID(domainID, txt, "TXT") if err != nil { - return fmt.Errorf("vegadns: find record ID for %d: %w", domainID, err) + return fmt.Errorf("vegadns: couldn't get Record ID in CleanUp: %w", err) } - err = d.client.DeleteRecord(ctx, recordID) + err = d.client.DeleteRecord(recordID) if err != nil { - return fmt.Errorf("vegadns: delete record: %w", err) + return fmt.Errorf("vegadns: %w", err) } - return nil } - -func (d *DNSProvider) findDomainID(ctx context.Context, fqdn string) (int, error) { - for host := range dns01.UnFqdnDomainsSeq(fqdn) { - id, err := d.client.GetDomainID(ctx, host) - if err != nil { - continue - } - - return id, nil - } - - return 0, errors.New("domain not found") -} - -func (d *DNSProvider) findRecordID(ctx context.Context, domainID int, name string) (int, error) { - records, err := d.client.GetRecords(ctx, domainID) - if err != nil { - return 0, fmt.Errorf("get records: %w", err) - } - - for _, r := range records { - if r.Name == name && r.RecordType == "TXT" { - return r.RecordID, nil - } - } - - return 0, errors.New("record not found") -} diff --git a/providers/dns/vegadns/vegadns_test.go b/providers/dns/vegadns/vegadns_test.go index edcd2c60d..48f54faab 100644 --- a/providers/dns/vegadns/vegadns_test.go +++ b/providers/dns/vegadns/vegadns_test.go @@ -19,7 +19,6 @@ var envTest = tester.NewEnvTest(EnvKey, EnvSecret, EnvURL) func TestNewDNSProvider_Fail(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() _, err := NewDNSProvider() @@ -28,7 +27,6 @@ func TestNewDNSProvider_Fail(t *testing.T) { func TestDNSProvider_TimeoutSuccess(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := mockBuilder().Build(t) @@ -46,7 +44,7 @@ func TestDNSProvider_Present(t *testing.T) { expectedError string }{ { - desc: "success", + desc: "Success", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -56,17 +54,17 @@ func TestDNSProvider_Present(t *testing.T) { WithStatusCode(http.StatusCreated)), }, { - desc: "fail to find the zone", + desc: "FailToFindZone", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). Route("GET /1.0/domains", servermock.Noop(). WithStatusCode(http.StatusNotFound)), - expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", + expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in Present: Unable to find auth zone for fqdn _acme-challenge.example.com", }, { - desc: "fail to create TXT record", + desc: "FailToCreateTXT", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -74,14 +72,13 @@ func TestDNSProvider_Present(t *testing.T) { Route("POST /1.0/records", servermock.Noop(). WithStatusCode(http.StatusBadRequest)), - expectedError: "vegadns: create TXT record: bad answer from VegaDNS (code: 400, message: )", + expectedError: "vegadns: Got bad answer from VegaDNS on CreateTXT. Code: 400. Message: ", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := test.builder.Build(t) @@ -103,7 +100,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { expectedError string }{ { - desc: "success", + desc: "Success", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -115,17 +112,17 @@ func TestDNSProvider_CleanUp(t *testing.T) { servermock.ResponseFromFixture("record_delete.json")), }, { - desc: "fail to find the zone", + desc: "FailToFindZone", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). Route("GET /1.0/domains", servermock.Noop(). WithStatusCode(http.StatusNotFound)), - expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", + expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in CleanUp: Unable to find auth zone for fqdn _acme-challenge.example.com", }, { - desc: "fail to get record ID", + desc: "FailToGetRecordID", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -134,14 +131,13 @@ func TestDNSProvider_CleanUp(t *testing.T) { servermock.Noop(). WithStatusCode(http.StatusNotFound), servermock.CheckQueryParameter().With("domain_id", "1")), - expectedError: "vegadns: find record ID for 1: get records: bad answer from VegaDNS (code: 404, message: )", + expectedError: "vegadns: couldn't get Record ID in CleanUp: Got bad answer from VegaDNS on GetRecordID. Code: 404. Message: ", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := test.builder.Build(t) @@ -171,7 +167,6 @@ func getDomainHandler() http.HandlerFunc { ] } `) - return } diff --git a/providers/dns/vercel/internal/client.go b/providers/dns/vercel/internal/client.go index 930f3543e..d852689ae 100644 --- a/providers/dns/vercel/internal/client.go +++ b/providers/dns/vercel/internal/client.go @@ -51,7 +51,6 @@ func (c *Client) CreateRecord(ctx context.Context, zone string, record Record) ( } respData := &CreateRecordResponse{} - err = c.do(req, respData) if err != nil { return nil, err @@ -136,7 +135,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIErrorResponse - err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/vercel/vercel.go b/providers/dns/vercel/vercel.go index 965e3de12..9ba92e21f 100644 --- a/providers/dns/vercel/vercel.go +++ b/providers/dns/vercel/vercel.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/vercel/internal" ) @@ -87,12 +86,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vercel: credentials missing") } - client := internal.NewClient( - clientdebug.Wrap( - internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), - ), - config.TeamID, - ) + client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), config.TeamID) return &DNSProvider{ config: config, @@ -148,7 +142,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("vercel: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/vercel/vercel.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/vercel/vercel_test.go b/providers/dns/vercel/vercel_test.go index d4cf37904..6c19a4db5 100644 --- a/providers/dns/vercel/vercel_test.go +++ b/providers/dns/vercel/vercel_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,7 +95,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,7 +108,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/versio/internal/client.go b/providers/dns/versio/internal/client.go index 6a92cc958..e91913556 100644 --- a/providers/dns/versio/internal/client.go +++ b/providers/dns/versio/internal/client.go @@ -48,7 +48,6 @@ func (c *Client) UpdateDomain(ctx context.Context, domain string, msg *DomainInf } respData := &DomainInfoResponse{} - err = c.do(req, respData) if err != nil { return nil, err @@ -72,7 +71,6 @@ func (c *Client) GetDomain(ctx context.Context, domain string) (*DomainInfoRespo } respData := &DomainInfoResponse{} - err = c.do(req, respData) if err != nil { return nil, err @@ -90,7 +88,6 @@ func (c *Client) do(req *http.Request, result any) error { if resp != nil { defer func() { _ = resp.Body.Close() }() } - if err != nil { return errutils.NewHTTPDoError(req, err) } @@ -143,7 +140,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) response := &ErrorResponse{} - err := json.Unmarshal(raw, response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/versio/versio.go b/providers/dns/versio/versio.go index 05a7263c4..78ddd9bac 100644 --- a/providers/dns/versio/versio.go +++ b/providers/dns/versio/versio.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/versio/internal" ) @@ -92,11 +91,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { return nil, errors.New("versio: the configuration of the DNS provider is nil") } - if config.Username == "" { return nil, errors.New("versio: the versio username is missing") } - if config.Password == "" { return nil, errors.New("versio: the versio password is missing") } @@ -111,8 +108,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -160,7 +155,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("versio: %w", err) } - return nil } @@ -188,7 +182,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // loop through the existing entries and remove the specific record msg := &internal.DomainInfo{} - for _, e := range domains.DomainInfo.DNSRecords { if e.Name != info.EffectiveFQDN { msg.DNSRecords = append(msg.DNSRecords, e) @@ -199,6 +192,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("versio: %w", err) } - return nil } diff --git a/providers/dns/versio/versio.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/versio/versio_test.go b/providers/dns/versio/versio_test.go index 563e70d05..ea1ccc221 100644 --- a/providers/dns/versio/versio_test.go +++ b/providers/dns/versio/versio_test.go @@ -54,7 +54,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -162,7 +161,6 @@ func TestDNSProvider_Present(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := test.builder.Build(t) @@ -206,7 +204,6 @@ func TestDNSProvider_CleanUp(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() provider := test.builder.Build(t) @@ -227,7 +224,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -241,7 +237,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -257,13 +252,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { EnvEndpoint: server.URL, }) - provider, err := NewDNSProvider() - if err != nil { - return nil, err - } - - provider.client.HTTPClient = server.Client() - - return provider, nil + return NewDNSProvider() }) } diff --git a/providers/dns/vinyldns/vinyldns.go b/providers/dns/vinyldns/vinyldns.go index 65a024513..9e36ccc51 100644 --- a/providers/dns/vinyldns/vinyldns.go +++ b/providers/dns/vinyldns/vinyldns.go @@ -2,17 +2,14 @@ package vinyldns import ( - "context" "errors" "fmt" - "net/http" "strconv" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/vinyldns/go-vinyldns/vinyldns" ) @@ -29,7 +26,6 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -44,7 +40,6 @@ type Config struct { TTL int PropagationTimeout time.Duration PollingInterval time.Duration - HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -53,9 +48,6 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, 30), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, } } @@ -104,22 +96,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { UserAgent: useragent.Get(), }) - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } else { - // For compatibility, it should be removed in v5. - client.HTTPClient.Timeout = 30 * time.Second - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + client.HTTPClient.Timeout = 30 * time.Second return &DNSProvider{client: client, config: config}, nil } // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) existingRecord, err := d.getRecordSet(info.EffectiveFQDN) @@ -132,7 +115,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record := vinyldns.Record{Text: value} if existingRecord == nil || existingRecord.ID == "" { - err = d.createRecordSet(ctx, info.EffectiveFQDN, []vinyldns.Record{record}) + err = d.createRecordSet(info.EffectiveFQDN, []vinyldns.Record{record}) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -149,7 +132,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { records := existingRecord.Records records = append(records, record) - err = d.updateRecordSet(ctx, existingRecord, records) + err = d.updateRecordSet(existingRecord, records) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -159,8 +142,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - info := dns01.GetChallengeInfo(domain, keyAuth) existingRecord, err := d.getRecordSet(info.EffectiveFQDN) @@ -175,7 +156,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { value := d.formatValue(info.Value) var records []vinyldns.Record - for _, i := range existingRecord.Records { if i.Text != value { records = append(records, i) @@ -183,7 +163,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } if len(records) == 0 { - err = d.deleteRecordSet(ctx, existingRecord) + err = d.deleteRecordSet(existingRecord) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -191,7 +171,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - err = d.updateRecordSet(ctx, existingRecord, records) + err = d.updateRecordSet(existingRecord, records) if err != nil { return fmt.Errorf("vinyldns: %w", err) } diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index d6dd5810e..8c9f1b3a6 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 = ''' @@ -26,7 +26,6 @@ Users are required to have DELETE ACL level or zone admin permissions on the Vin VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" - VINYLDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.vinyldns.io/api/" diff --git a/providers/dns/vinyldns/vinyldns_test.go b/providers/dns/vinyldns/vinyldns_test.go index 7dfe2c13f..6f5b9b328 100644 --- a/providers/dns/vinyldns/vinyldns_test.go +++ b/providers/dns/vinyldns/vinyldns_test.go @@ -78,7 +78,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -163,7 +162,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.AccessKey = "foo" config.SecretKey = "bar" config.Host = server.URL - config.HTTPClient = server.Client() return NewDNSProviderConfig(config) }) @@ -247,7 +245,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -261,7 +258,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vinyldns/wrapper.go b/providers/dns/vinyldns/wrapper.go index e7b59a82b..f17b3de31 100644 --- a/providers/dns/vinyldns/wrapper.go +++ b/providers/dns/vinyldns/wrapper.go @@ -1,10 +1,8 @@ package vinyldns import ( - "context" "fmt" - "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/wait" "github.com/vinyldns/go-vinyldns/vinyldns" @@ -27,7 +25,6 @@ func (d *DNSProvider) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) { } var recordSets []vinyldns.RecordSet - for _, i := range allRecordSets { if i.Type == "TXT" { recordSets = append(recordSets, i) @@ -44,7 +41,7 @@ func (d *DNSProvider) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) { } } -func (d *DNSProvider) createRecordSet(ctx context.Context, fqdn string, records []vinyldns.Record) error { +func (d *DNSProvider) createRecordSet(fqdn string, records []vinyldns.Record) error { zoneName, hostName, err := splitDomain(fqdn) if err != nil { return err @@ -68,10 +65,10 @@ func (d *DNSProvider) createRecordSet(ctx context.Context, fqdn string, records return err } - return d.waitForChanges(ctx, "CreateRS", resp) + return d.waitForChanges("CreateRS", resp) } -func (d *DNSProvider) updateRecordSet(ctx context.Context, recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error { +func (d *DNSProvider) updateRecordSet(recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error { operation := "delete" if len(recordSet.Records) < len(newRecords) { operation = "add" @@ -85,35 +82,33 @@ func (d *DNSProvider) updateRecordSet(ctx context.Context, recordSet *vinyldns.R return err } - return d.waitForChanges(ctx, "UpdateRS - "+operation, resp) + return d.waitForChanges("UpdateRS - "+operation, resp) } -func (d *DNSProvider) deleteRecordSet(ctx context.Context, existingRecord *vinyldns.RecordSet) error { +func (d *DNSProvider) deleteRecordSet(existingRecord *vinyldns.RecordSet) error { resp, err := d.client.RecordSetDelete(existingRecord.ZoneID, existingRecord.ID) if err != nil { return err } - return d.waitForChanges(ctx, "DeleteRS", resp) + return d.waitForChanges("DeleteRS", resp) } -func (d *DNSProvider) waitForChanges(ctx context.Context, operation string, resp *vinyldns.RecordSetUpdateResponse) error { - return wait.Retry(ctx, - func() error { +func (d *DNSProvider) waitForChanges(operation string, resp *vinyldns.RecordSetUpdateResponse) error { + return wait.For("vinyldns", d.config.PropagationTimeout, d.config.PollingInterval, + func() (bool, error) { change, err := d.client.RecordSetChange(resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) if err != nil { - return fmt.Errorf("failed to query change status: %w", err) + return false, fmt.Errorf("failed to query change status: %w", err) } - if change.Status != "Complete" { - return fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s", - operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) + if change.Status == "Complete" { + return true, nil } - return nil + return false, fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s", + operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) }, - backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), - backoff.WithMaxElapsedTime(d.config.PropagationTimeout), ) } 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/internal/client.go b/providers/dns/vkcloud/internal/client.go index 2b03518db..5ced88d2d 100644 --- a/providers/dns/vkcloud/internal/client.go +++ b/providers/dns/vkcloud/internal/client.go @@ -46,7 +46,6 @@ func (c *Client) ListZones() ([]DNSZone, error) { endpoint := c.baseURL.JoinPath("/") var zones []DNSZone - opts := &gophercloud.RequestOpts{JSONResponse: &zones} err := c.request(http.MethodGet, endpoint, opts) @@ -61,7 +60,6 @@ func (c *Client) ListTXTRecords(zoneUUID string) ([]DNSTXTRecord, error) { endpoint := c.baseURL.JoinPath(zoneUUID, "txt", "/") var records []DNSTXTRecord - opts := &gophercloud.RequestOpts{JSONResponse: &records} err := c.request(http.MethodGet, endpoint, opts) diff --git a/providers/dns/vkcloud/vkcloud.go b/providers/dns/vkcloud/vkcloud.go index ffacdbe52..2aea7838c 100644 --- a/providers/dns/vkcloud/vkcloud.go +++ b/providers/dns/vkcloud/vkcloud.go @@ -135,7 +135,6 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { } var zoneUUID string - for _, zone := range zones { if zone.Zone == authZone { zoneUUID = zone.UUID diff --git a/providers/dns/vkcloud/vkcloud.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/vkcloud/vkcloud_test.go b/providers/dns/vkcloud/vkcloud_test.go index e7883b486..edc32363a 100644 --- a/providers/dns/vkcloud/vkcloud_test.go +++ b/providers/dns/vkcloud/vkcloud_test.go @@ -60,7 +60,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -189,7 +188,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -203,7 +201,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index 765d38adb..a271e0f26 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -159,7 +159,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() - if !ok { return fmt.Errorf("volcengine: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -171,10 +170,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/volcengine/volcengine_test.go b/providers/dns/volcengine/volcengine_test.go index 0f79ed83a..5e9167612 100644 --- a/providers/dns/volcengine/volcengine_test.go +++ b/providers/dns/volcengine/volcengine_test.go @@ -55,7 +55,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -126,7 +125,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -140,7 +138,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index a159db307..6c51ae5ca 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -4,9 +4,11 @@ package vscale import ( + "context" "errors" "fmt" "net/http" + "net/url" "time" "github.com/go-acme/lego/v4/challenge" @@ -28,18 +30,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 +59,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 +83,89 @@ 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 + } + + 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..6a9b25583 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" ) @@ -37,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -47,7 +45,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 +76,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 +91,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) } @@ -106,7 +106,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -120,7 +119,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index f97a321c1..7672d2054 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/vultr/govultr/v3" "golang.org/x/oauth2" ) @@ -39,7 +38,7 @@ type Config struct { PollingInterval time.Duration TTL int HTTPClient *http.Client - HTTPTimeout time.Duration // TODO(ldez): remove in v5 + HTTPTimeout time.Duration } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -85,7 +84,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { authClient := OAuthStaticAccessToken(config.HTTPClient, config.APIKey) authClient.Timeout = config.HTTPTimeout - client := govultr.NewClient(clientdebug.Wrap(authClient)) + client := govultr.NewClient(authClient) return &DNSProvider{client: client, config: config}, nil } @@ -107,7 +106,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 + `"`, @@ -136,7 +135,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var allErr []string - for _, rec := range records { err := d.client.DomainRecord.Delete(ctx, zoneDomain, rec.ID) if err != nil { @@ -206,7 +204,6 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, domain, fqdn string) ( listOptions := &govultr.ListOptions{PerPage: 25} var records []govultr.DomainRecord - for { result, meta, resp, err := d.client.DomainRecord.List(ctx, zoneDomain, listOptions) if err != nil { diff --git a/providers/dns/vultr/vultr.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/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index 17d962b2a..9be1a19b0 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -45,7 +45,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -165,7 +164,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { provider := servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { - client := govultr.NewClient(server.Client()) + client := govultr.NewClient(nil) err := client.SetBaseURL(server.URL) require.NoError(t, err) @@ -222,7 +221,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -236,7 +234,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/webnames/internal/client.go b/providers/dns/webnames/internal/client.go index 985503d2a..5b1a8b357 100644 --- a/providers/dns/webnames/internal/client.go +++ b/providers/dns/webnames/internal/client.go @@ -83,7 +83,6 @@ func (c *Client) doRequest(ctx context.Context, data url.Values) error { } var r APIResponse - err = json.Unmarshal(raw, &r) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/webnames/webnames.go b/providers/dns/webnames/webnames.go index 9c27164e3..78905e22c 100644 --- a/providers/dns/webnames/webnames.go +++ b/providers/dns/webnames/webnames.go @@ -6,20 +6,17 @@ import ( "errors" "fmt" "net/http" - "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/webnames/internal" ) // Environment variables names. const ( - envNamespace = "WEBNAMESRU_" - altEnvNamespace = "WEBNAMES_" + envNamespace = "WEBNAMES_" EnvAPIKey = envNamespace + "API_KEY" @@ -42,10 +39,10 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, dns01.DefaultPropagationTimeout, env.ParseSecond, altEnvName(EnvPropagationTimeout)), - PollingInterval: env.GetOneWithFallback(EnvPollingInterval, dns01.DefaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ - Timeout: env.GetOneWithFallback(EnvHTTPTimeout, 20*time.Second, env.ParseSecond, altEnvName(EnvHTTPTimeout)), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, } } @@ -57,11 +54,11 @@ type DNSProvider struct { } // NewDNSProvider returns a new DNS provider using -// environment variable WEBNAMESRU_API_KEY for adding and removing the DNS record. +// environment variable WEBNAMES_API_KEY for adding and removing the DNS record. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.GetWithFallback([]string{EnvAPIKey, altEnvName(EnvAPIKey)}) + values, err := env.Get(EnvAPIKey) if err != nil { - return nil, fmt.Errorf("webnamesru: %w", err) + return nil, fmt.Errorf("webnames: %w", err) } config := NewDefaultConfig() @@ -73,11 +70,11 @@ func NewDNSProvider() (*DNSProvider, error) { // NewDNSProviderConfig return a DNSProvider instance configured for Webnames. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { - return nil, errors.New("webnamesru: the configuration of the DNS provider is nil") + return nil, errors.New("webnames: the configuration of the DNS provider is nil") } if config.APIKey == "" { - return nil, errors.New("webnamesru: credentials missing") + return nil, errors.New("webnames: credentials missing") } client := internal.NewClient(config.APIKey) @@ -86,8 +83,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } @@ -97,17 +92,17 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("webnamesru: could not find zone for domain %q: %w", domain, err) + return fmt.Errorf("webnames: could not find zone for domain %q: %w", domain, err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { - return fmt.Errorf("webnamesru: %w", err) + return fmt.Errorf("webnames: %w", err) } err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), subDomain, info.Value) if err != nil { - return fmt.Errorf("webnamesru: failed to create TXT records [domain: %s, sub domain: %s]: %w", + return fmt.Errorf("webnames: failed to create TXT records [domain: %s, sub domain: %s]: %w", dns01.UnFqdn(authZone), subDomain, err) } @@ -120,17 +115,17 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("webnamesru: could not find zone for domain %q: %w", domain, err) + return fmt.Errorf("webnames: could not find zone for domain %q: %w", domain, err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { - return fmt.Errorf("webnamesru: %w", err) + return fmt.Errorf("webnames: %w", err) } err = d.client.RemoveTXTRecord(context.Background(), dns01.UnFqdn(authZone), subDomain, info.Value) if err != nil { - return fmt.Errorf("webnamesru: failed to remove TXT records [domain: %s, sub domain: %s]: %w", + return fmt.Errorf("webnames: failed to remove TXT records [domain: %s, sub domain: %s]: %w", dns01.UnFqdn(authZone), subDomain, err) } @@ -142,7 +137,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } - -func altEnvName(v string) string { - return strings.ReplaceAll(v, envNamespace, altEnvNamespace) -} diff --git a/providers/dns/webnames/webnames.toml b/providers/dns/webnames/webnames.toml index b038deaf5..1962a69eb 100644 --- a/providers/dns/webnames/webnames.toml +++ b/providers/dns/webnames/webnames.toml @@ -1,13 +1,12 @@ -Name = "webnames.ru" +Name = "Webnames" Description = '''''' URL = "https://www.webnames.ru/" Code = "webnames" -Aliases = ["webnamesru"] Since = "v4.15.0" Example = ''' -WEBNAMESRU_API_KEY=xxxxxx \ -lego --dns webnamesru -d '*.example.com' -d example.com run +WEBNAMES_API_KEY=xxxxxx \ +lego --email you@example.com --dns webnames -d '*.example.com' -d example.com run ''' Additional = ''' @@ -20,11 +19,11 @@ The API key can be found: Personal account / My domains and services / Select th [Configuration] [Configuration.Credentials] - WEBNAMESRU_API_KEY = "Domain API key" + WEBNAMES_API_KEY = "Domain API key" [Configuration.Additional] - WEBNAMESRU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - WEBNAMESRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - WEBNAMESRU_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + WEBNAMES_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + WEBNAMES_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + WEBNAMES_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://github.com/regtime-ltd/certbot-dns-webnames" diff --git a/providers/dns/webnames/webnames_test.go b/providers/dns/webnames/webnames_test.go index 072591c68..3ec69501f 100644 --- a/providers/dns/webnames/webnames_test.go +++ b/providers/dns/webnames/webnames_test.go @@ -29,14 +29,13 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvAPIKey: "", }, - expected: "webnamesru: some credentials information are missing: WEBNAMESRU_API_KEY", + expected: "webnames: some credentials information are missing: WEBNAMES_API_KEY", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -66,7 +65,7 @@ func TestNewDNSProviderConfig(t *testing.T) { }, { desc: "missing credentials", - expected: "webnamesru: credentials missing", + expected: "webnames: credentials missing", }, } @@ -94,7 +93,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,7 +106,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/webnamesca/internal/client.go b/providers/dns/webnamesca/internal/client.go deleted file mode 100644 index 203ff9eac..000000000 --- a/providers/dns/webnamesca/internal/client.go +++ /dev/null @@ -1,162 +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.webnames.ca/_/APICore" - -// Client the webnames.ca API client. -type Client struct { - user string - key string - - BaseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(user, key string) (*Client, error) { - if user == "" || key == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - user: user, - key: key, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -func (c *Client) AddTXTRecord(ctx context.Context, domainName, hostName, value string) ([]DNSRecordSet, error) { - endpoint := c.BaseURL.JoinPath("domains", domainName, "add-txt-record") - - query := endpoint.Query() - query.Set("hostName", hostName) - query.Set("txt", value) - - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, nil) - if err != nil { - return nil, err - } - - var result APIResponse[*DNSInfo] - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Result.DNSRecordSets, nil -} - -func (c *Client) DeleteTXTRecord(ctx context.Context, domainName, hostName, value string) ([]DNSRecordSet, error) { - endpoint := c.BaseURL.JoinPath("domains", domainName, "delete-txt-record") - - query := endpoint.Query() - query.Set("hostName", hostName) - query.Set("txt", value) - - endpoint.RawQuery = query.Encode() - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return nil, err - } - - var result APIResponse[*DNSInfo] - - err = c.do(req, &result) - if err != nil { - return nil, err - } - - return result.Result.DNSRecordSets, nil -} - -func (c *Client) do(req *http.Request, result any) error { - useragent.SetHeader(req.Header) - - req.Header.Set("API-User", c.user) - req.Header.Set("API-Key", c.key) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return parseError(req, resp) - } - - if result == nil { - return nil - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/webnamesca/internal/client_test.go b/providers/dns/webnamesca/internal/client_test.go deleted file mode 100644 index ad8571ed0..000000000 --- a/providers/dns/webnamesca/internal/client_test.go +++ /dev/null @@ -1,96 +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("user", "secret") - if err != nil { - return nil, err - } - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, nil - }, - servermock.CheckHeader(). - With("API-User", "user"). - With("API-Key", "secret"). - WithJSONHeaders(), - ) -} - -func TestClient_AddTXTRecord(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/example.com/add-txt-record", - servermock.ResponseFromFixture("add_txt_record.json"), - servermock.CheckQueryParameter().Strict(). - With("hostName", "foo.example.com"). - With("txt", "value")). - Build(t) - - result, err := client.AddTXTRecord(t.Context(), "example.com", "foo.example.com", "value") - require.NoError(t, err) - - expected := []DNSRecordSet{{ - Hostname: "_acme-challenge.example.com", - Type: "TXT", - Records: []string{"value"}, - }} - - assert.Equal(t, expected, result) -} - -func TestClient_AddTXTRecord_error(t *testing.T) { - client := mockBuilder(). - Route("POST /domains/example.com/add-txt-record", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - _, err := client.AddTXTRecord(t.Context(), "example.com", "foo.example.com", "value") - require.EqualError(t, err, "message: User does not exist., details: string, logiD: 35579, result: {}") -} - -func TestClient_DeleteTXTRecord(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/delete-txt-record", - servermock.ResponseFromFixture("delete_txt_record.json"), - servermock.CheckQueryParameter().Strict(). - With("hostName", "foo.example.com"). - With("txt", "value")). - Build(t) - - result, err := client.DeleteTXTRecord(t.Context(), "example.com", "foo.example.com", "value") - require.NoError(t, err) - - expected := []DNSRecordSet{{ - Hostname: "_acme-challenge.example.com", - Type: "TXT", - Records: []string{"value"}, - }} - - assert.Equal(t, expected, result) -} - -func TestClient_DeleteTXTRecord_error(t *testing.T) { - client := mockBuilder(). - Route("DELETE /domains/example.com/delete-txt-record", - servermock.ResponseFromFixture("error.json"). - WithStatusCode(http.StatusBadRequest)). - Build(t) - - _, err := client.DeleteTXTRecord(t.Context(), "example.com", "foo.example.com", "value") - require.EqualError(t, err, "message: User does not exist., details: string, logiD: 35579, result: {}") -} diff --git a/providers/dns/webnamesca/internal/fixtures/add_txt_record.json b/providers/dns/webnamesca/internal/fixtures/add_txt_record.json deleted file mode 100644 index 9754689a7..000000000 --- a/providers/dns/webnamesca/internal/fixtures/add_txt_record.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "result": { - "domainAdvancedDNSConfigID": 3258480, - "domainID": 1333334, - "dtCreated": "2025-10-30T11:55:23.243", - "dtModified": "2025-10-30T11:55:23.177", - "timeToLive": 21600, - "soAorigin": "hosting.webnames.ca", - "soArefresh": 21600, - "soAretry": 180, - "soAexpire": 1209600, - "soAnegcache": 3600, - "forwardingURL": null, - "gripping": false, - "name": null, - "dtSubmitted": "2025-10-30T11:55:24.927", - "dtRequestedDNSChange": null, - "type": "REAL_DOMAIN", - "userManaged": false, - "effectiveMgmtOption": "AD", - "urlForwardRootOnly": false, - "enableDNSSEC": false, - "dnsRecordSets": [ - { - "hostname": "_acme-challenge.example.com", - "type": "TXT", - "records": [ - "value" - ] - } - ] - }, - "logID": 36014 -} diff --git a/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json b/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json deleted file mode 100644 index be2279ef6..000000000 --- a/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "errorMessage": "string", - "errorDetails": "string", - "logID": 0, - "result": { - "domainAdvancedDNSConfigID": 0, - "domainID": 0, - "dtCreated": "2025-10-29T21:22:31.478", - "dtModified": "2025-10-29T21:22:31.478", - "timeToLive": 0, - "soAorigin": "string", - "soArefresh": 0, - "soAretry": 0, - "soAexpire": 0, - "soAnegcache": 0, - "forwardingURL": "string", - "gripping": true, - "name": "string", - "dtSubmitted": "2025-10-29T21:22:31.478", - "dtRequestedDNSChange": "2025-10-29T21:22:31.478", - "type": "string", - "userManaged": true, - "effectiveMgmtOption": "string", - "urlForwardRootOnly": true, - "enableDNSSEC": true, - "dnsRecordSets": [ - { - "hostname": "_acme-challenge.example.com", - "type": "TXT", - "records": [ - "value" - ] - } - ] - } -} diff --git a/providers/dns/webnamesca/internal/fixtures/error.json b/providers/dns/webnamesca/internal/fixtures/error.json deleted file mode 100644 index 3e7548abb..000000000 --- a/providers/dns/webnamesca/internal/fixtures/error.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "errorMessage": "User does not exist.", - "errorDetails": "string", - "logID": 35579, - "result": {} -} diff --git a/providers/dns/webnamesca/internal/types.go b/providers/dns/webnamesca/internal/types.go deleted file mode 100644 index 8dc56c33a..000000000 --- a/providers/dns/webnamesca/internal/types.go +++ /dev/null @@ -1,33 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" -) - -type APIError struct { - ErrorMessage string `json:"errorMessage,omitempty"` - ErrorDetails string `json:"errorDetails,omitempty"` - LogID int `json:"logID,omitempty"` - Result json.RawMessage `json:"result,omitempty"` -} - -func (a *APIError) Error() string { - return fmt.Sprintf("message: %s, details: %s, logiD: %d, result: %s", a.ErrorMessage, a.ErrorDetails, a.LogID, a.Result) -} - -type APIResponse[T any] struct { - Result T `json:"result,omitempty"` - LogID int `json:"logID,omitempty"` -} - -type DNSInfo struct { - DomainID int `json:"domainID,omitempty"` - DNSRecordSets []DNSRecordSet `json:"dnsRecordSets,omitempty"` -} - -type DNSRecordSet struct { - Hostname string `json:"hostname"` - Type string `json:"type"` - Records []string `json:"records"` -} diff --git a/providers/dns/webnamesca/webnamesca.go b/providers/dns/webnamesca/webnamesca.go deleted file mode 100644 index 874c1c48e..000000000 --- a/providers/dns/webnamesca/webnamesca.go +++ /dev/null @@ -1,134 +0,0 @@ -// Package webnamesca implements a DNS provider for solving the DNS-01 challenge using webnames.ca. -package webnamesca - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/webnamesca/internal" -) - -// Environment variables names. -const ( - envNamespace = "WEBNAMESCA_" - - EnvAPIUser = envNamespace + "API_USER" - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" -) - -// Config is used to configure the creation of the DNSProvider. -type Config struct { - APIUser string - APIKey string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} - -// NewDefaultConfig returns a default configuration for the DNSProvider. -func NewDefaultConfig() *Config { - return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } -} - -// DNSProvider implements the challenge.Provider interface. -type DNSProvider struct { - config *Config - client *internal.Client -} - -// NewDNSProvider returns a DNSProvider instance configured for webnames.ca. -func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIUser, EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("webnamesca: %w", err) - } - - config := NewDefaultConfig() - config.APIUser = values[EnvAPIUser] - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) -} - -// NewDNSProviderConfig return a DNSProvider instance configured for webnames.ca. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("webnamesca: the configuration of the DNS provider is nil") - } - - client, err := internal.NewClient(config.APIUser, config.APIKey) - if err != nil { - return nil, fmt.Errorf("webnamesca: %w", err) - } - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil -} - -// Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("webnamesca: could not find zone for domain %q: %w", domain, err) - } - - _, err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) - if err != nil { - return fmt.Errorf("webnamesca: add TXT record: %w", err) - } - - return nil -} - -// CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("webnamesca: could not find zone for domain %q: %w", domain, err) - } - - _, err = d.client.DeleteTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) - if err != nil { - return fmt.Errorf("webnamesca: delete TXT record: %w", err) - } - - return nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -// Adjusting here to cope with spikes in propagation times. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} diff --git a/providers/dns/webnamesca/webnamesca.toml b/providers/dns/webnamesca/webnamesca.toml deleted file mode 100644 index ab68a04a0..000000000 --- a/providers/dns/webnamesca/webnamesca.toml +++ /dev/null @@ -1,24 +0,0 @@ -Name = "webnames.ca" -Description = '''''' -URL = "https://www.webnames.ca/" -Code = "webnamesca" -Since = "v4.28.0" - -Example = ''' -WEBNAMESCA_API_USER="xxx" \ -WEBNAMESCA_API_KEY="yyy" \ -lego --dns webnamesca -d '*.example.com' -d example.com run -''' - -[Configuration] - [Configuration.Credentials] - WEBNAMESCA_API_USER = "API username" - WEBNAMESCA_API_KEY = "API key" - [Configuration.Additional] - WEBNAMESCA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - WEBNAMESCA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - WEBNAMESCA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" - WEBNAMESCA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" - -[Links] - API = "https://www.webnames.ca/_/swagger/index.html" diff --git a/providers/dns/webnamesca/webnamesca_test.go b/providers/dns/webnamesca/webnamesca_test.go deleted file mode 100644 index 0459ef44e..000000000 --- a/providers/dns/webnamesca/webnamesca_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package webnamesca - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAPIUser, EnvAPIKey).WithDomain(envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAPIUser: "user", - EnvAPIKey: "secret", - }, - }, - { - desc: "missing EnvAPIUser", - envVars: map[string]string{ - EnvAPIUser: "", - EnvAPIKey: "secret", - }, - expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_USER", - }, - { - desc: "missing EnvAPIKey", - envVars: map[string]string{ - EnvAPIUser: "user", - EnvAPIKey: "", - }, - expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_KEY", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_USER,WEBNAMESCA_API_KEY", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - defer envTest.RestoreEnv() - - envTest.ClearEnv() - - envTest.Apply(test.envVars) - - p, err := NewDNSProvider() - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - apiUser string - apiKey string - expected string - }{ - { - desc: "success", - apiUser: "user", - apiKey: "secret", - }, - { - desc: "missing apiUser", - apiKey: "secret", - expected: "webnamesca: credentials missing", - }, - { - desc: "missing apiKey", - apiUser: "user", - expected: "webnamesca: credentials missing", - }, - { - desc: "missing credentials", - expected: "webnamesca: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.APIUser = test.apiUser - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) - } else { - require.EqualError(t, err, test.expected) - } - }) - } -} - -func TestLivePresent(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func TestLiveCleanUp(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - envTest.RestoreEnv() - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - config.APIUser = "user" - config.APIKey = "secret" - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader(). - WithJSONHeaders(). - With("API-User", "user"). - With("API-Key", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("POST /domains/example.com/add-txt-record", - servermock.ResponseFromInternal("add_txt_record.json"), - servermock.CheckQueryParameter().Strict(). - With("hostName", "_acme-challenge.example.com"). - With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /domains/example.com/delete-txt-record", - servermock.ResponseFromInternal("delete_txt_record.json"), - servermock.CheckQueryParameter().Strict(). - With("hostName", "_acme-challenge.example.com"). - With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")). - Build(t) - - err := provider.CleanUp("example.com", "abc", "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go index 4187ba32b..aa3c93578 100644 --- a/providers/dns/websupport/websupport.go +++ b/providers/dns/websupport/websupport.go @@ -2,12 +2,13 @@ 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" @@ -29,7 +30,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 +54,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 +79,81 @@ 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 + } + + 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 +162,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..051fdb837 100644 --- a/providers/dns/websupport/websupport_test.go +++ b/providers/dns/websupport/websupport_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -60,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.client) } else { require.EqualError(t, err, test.expected) } @@ -109,7 +109,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) } @@ -123,7 +124,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +137,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/wedos/internal/client.go b/providers/dns/wedos/internal/client.go index 48c89d189..1f573e397 100644 --- a/providers/dns/wedos/internal/client.go +++ b/providers/dns/wedos/internal/client.go @@ -69,7 +69,6 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record DNSRow) erro } cmd := commandDNSRowAdd - if record.ID == "" { payload.Name = record.Name } else { diff --git a/providers/dns/wedos/internal/token.go b/providers/dns/wedos/internal/token.go index 11e680cb8..dd126b442 100644 --- a/providers/dns/wedos/internal/token.go +++ b/providers/dns/wedos/internal/token.go @@ -15,7 +15,6 @@ func authToken(userName, wapiPass string) string { func sha1string(txt string) string { h := sha1.New() _, _ = io.WriteString(h, txt) - return hex.EncodeToString(h.Sum(nil)) } @@ -47,13 +46,11 @@ func utcToCet(utc time.Time) time.Time { if utcMonth < time.March || utcMonth > time.October { return utc.Add(time.Hour) } - if utcMonth > time.March && utcMonth < time.October { return utc.Add(time.Hour * 2) } dayOff := 0 - breaking := time.Date(utc.Year(), utcMonth+1, dayOff, 1, 0, 0, 0, time.UTC) for breaking.Weekday() != time.Sunday { dayOff-- @@ -68,7 +65,6 @@ func utcToCet(utc time.Time) time.Time { if (utcMonth == time.March && utc.Before(breaking)) || (utcMonth == time.October && utc.After(breaking)) { return utc.Add(time.Hour) } - return utc.Add(time.Hour * 2) } diff --git a/providers/dns/wedos/wedos.go b/providers/dns/wedos/wedos.go index 164fb5f10..85187ec46 100644 --- a/providers/dns/wedos/wedos.go +++ b/providers/dns/wedos/wedos.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/wedos/internal" ) @@ -95,8 +94,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/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/wedos/wedos_test.go b/providers/dns/wedos/wedos_test.go index 25f70d0fc..9363002b5 100644 --- a/providers/dns/wedos/wedos_test.go +++ b/providers/dns/wedos/wedos_test.go @@ -54,7 +54,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,7 +120,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +133,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/internal/westcn/internal/client.go b/providers/dns/westcn/internal/client.go similarity index 96% rename from providers/dns/internal/westcn/internal/client.go rename to providers/dns/westcn/internal/client.go index 621c7865f..4d967f5e1 100644 --- a/providers/dns/internal/westcn/internal/client.go +++ b/providers/dns/westcn/internal/client.go @@ -14,8 +14,8 @@ import ( "strings" "time" - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" querystring "github.com/google/go-querystring/query" + "github.com/nrdcg/mailinabox/errutils" "golang.org/x/text/encoding" "golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/transform" @@ -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..37f357b70 100644 --- a/providers/dns/westcn/westcn.go +++ b/providers/dns/westcn/westcn.go @@ -2,14 +2,17 @@ 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/westcn/internal" ) // Environment variables names. @@ -25,12 +28,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 +55,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 +82,88 @@ 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 + } + + 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..71632d99f 100644 --- a/providers/dns/westcn/westcn_test.go +++ b/providers/dns/westcn/westcn_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -60,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.client) } else { require.EqualError(t, err, test.expected) } @@ -107,7 +107,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) } @@ -121,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandex/internal/client.go b/providers/dns/yandex/internal/client.go index 4b0421f49..98480a793 100644 --- a/providers/dns/yandex/internal/client.go +++ b/providers/dns/yandex/internal/client.go @@ -51,7 +51,6 @@ func (c *Client) AddRecord(ctx context.Context, payload Record) (*Record, error) } r := AddResponse{} - err = c.do(req, &r) if err != nil { return nil, err @@ -69,7 +68,6 @@ func (c *Client) RemoveRecord(ctx context.Context, payload Record) (int, error) } r := RemoveResponse{} - err = c.do(req, &r) if err != nil { return 0, err @@ -91,7 +89,6 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } r := ListResponse{} - err = c.do(req, &r) if err != nil { return nil, err diff --git a/providers/dns/yandex/internal/types.go b/providers/dns/yandex/internal/types.go index 48a85042c..ed1873cef 100644 --- a/providers/dns/yandex/internal/types.go +++ b/providers/dns/yandex/internal/types.go @@ -30,21 +30,18 @@ func (r BaseResponse) GetError() string { type AddResponse struct { BaseResponse - Domain string `json:"domain,omitempty"` Record *Record `json:"record,omitempty"` } type RemoveResponse struct { BaseResponse - Domain string `json:"domain,omitempty"` RecordID int `json:"record_id,omitempty"` } type ListResponse struct { BaseResponse - Domain string `json:"domain,omitempty"` Records []Record `json:"records,omitempty"` } diff --git a/providers/dns/yandex/yandex.go b/providers/dns/yandex/yandex.go index 7ae505ec0..c51602f67 100644 --- a/providers/dns/yandex/yandex.go +++ b/providers/dns/yandex/yandex.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/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/yandex/internal" "github.com/miekg/dns" ) @@ -89,8 +88,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{client: client, config: config}, nil } @@ -136,7 +133,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var record *internal.Record - for _, rcd := range records { if rcd.Type == "TXT" && rcd.SubDomain == subDomain && rcd.Content == info.Value { record = &rcd @@ -157,7 +153,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("yandex: %w", err) } - return nil } diff --git a/providers/dns/yandex/yandex.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/yandex/yandex_test.go b/providers/dns/yandex/yandex_test.go index 8a0a7534a..144a24126 100644 --- a/providers/dns/yandex/yandex_test.go +++ b/providers/dns/yandex/yandex_test.go @@ -33,7 +33,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,7 +95,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,7 +108,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandex360/internal/client.go b/providers/dns/yandex360/internal/client.go index 33aeb0daa..9ffececaf 100644 --- a/providers/dns/yandex360/internal/client.go +++ b/providers/dns/yandex360/internal/client.go @@ -138,7 +138,6 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var apiErr APIError - err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/yandex360/yandex360.go b/providers/dns/yandex360/yandex360.go index 0f4571750..e2ee7beb2 100644 --- a/providers/dns/yandex360/yandex360.go +++ b/providers/dns/yandex360/yandex360.go @@ -13,9 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/yandex360/internal" - "github.com/miekg/dns" ) // Environment variables names. @@ -99,8 +97,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ client: client, config: config, @@ -112,7 +108,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("yandex360: could not find zone for domain %q: %w", domain, err) } @@ -147,7 +143,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("yandex360: could not find zone for domain %q: %w", domain, err) } 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/yandex360/yandex360_test.go b/providers/dns/yandex360/yandex360_test.go index c1d37ad12..545c90985 100644 --- a/providers/dns/yandex360/yandex360_test.go +++ b/providers/dns/yandex360/yandex360_test.go @@ -43,7 +43,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -110,7 +109,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -124,7 +122,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandexcloud/yandexcloud.go b/providers/dns/yandexcloud/yandexcloud.go index f9c64def1..346b6d952 100644 --- a/providers/dns/yandexcloud/yandexcloud.go +++ b/providers/dns/yandexcloud/yandexcloud.go @@ -229,7 +229,6 @@ func (d *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, val } var deletions []*ycdnsproto.RecordSet - if exist != nil { record.SetData(append(record.GetData(), exist.GetData()...)) deletions = append(deletions, exist) @@ -308,7 +307,6 @@ func decodeCredentials(accountB64 string) (credentials.Credentials, error) { } key := &iamkey.Key{} - err = json.Unmarshal(account, key) if err != nil { return nil, err diff --git a/providers/dns/yandexcloud/yandexcloud.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/yandexcloud/yandexcloud_test.go b/providers/dns/yandexcloud/yandexcloud_test.go index 52dad574d..48f75d134 100644 --- a/providers/dns/yandexcloud/yandexcloud_test.go +++ b/providers/dns/yandexcloud/yandexcloud_test.go @@ -71,7 +71,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -144,7 +143,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -158,7 +156,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/zoneedit/internal/client.go b/providers/dns/zoneedit/internal/client.go index c8b99e173..e97f4beb9 100644 --- a/providers/dns/zoneedit/internal/client.go +++ b/providers/dns/zoneedit/internal/client.go @@ -98,7 +98,6 @@ func (c *Client) do(req *http.Request) error { } var apiErr APIError - err = xml.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/zoneedit/zoneedit.go b/providers/dns/zoneedit/zoneedit.go index c815f975a..875b84233 100644 --- a/providers/dns/zoneedit/zoneedit.go +++ b/providers/dns/zoneedit/zoneedit.go @@ -9,7 +9,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/zoneedit/internal" ) @@ -81,8 +80,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/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/zoneedit/zoneedit_test.go b/providers/dns/zoneedit/zoneedit_test.go index 0b251fddf..2a9b1754d 100644 --- a/providers/dns/zoneedit/zoneedit_test.go +++ b/providers/dns/zoneedit/zoneedit_test.go @@ -50,7 +50,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -123,7 +122,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,7 +135,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/zoneee/zoneee.go b/providers/dns/zoneee/zoneee.go index 5c34ea1c9..7dbbc4314 100644 --- a/providers/dns/zoneee/zoneee.go +++ b/providers/dns/zoneee/zoneee.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/zoneee/internal" ) @@ -70,7 +69,6 @@ func NewDNSProvider() (*DNSProvider, error) { } rawEndpoint := env.GetOrDefaultString(EnvEndpoint, internal.DefaultEndpoint) - endpoint, err := url.Parse(rawEndpoint) if err != nil { return nil, fmt.Errorf("zoneee: %w", err) @@ -107,9 +105,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - if config.Endpoint != nil { client.BaseURL = config.Endpoint } @@ -143,7 +138,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("zoneee: %w", err) } - return nil } @@ -166,7 +160,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var id string - for _, record := range records { if record.Destination == info.Value { id = record.ID diff --git a/providers/dns/zoneee/zoneee.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/zoneee/zoneee_test.go b/providers/dns/zoneee/zoneee_test.go index 9ad87c02a..6f50cf36e 100644 --- a/providers/dns/zoneee/zoneee_test.go +++ b/providers/dns/zoneee/zoneee_test.go @@ -77,7 +77,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -248,7 +247,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -262,7 +260,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -276,7 +273,6 @@ func mockBuilder(username, apiKey string) *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Username = username config.APIKey = apiKey @@ -289,7 +285,6 @@ func mockBuilder(username, apiKey string) *servermock.Builder[*DNSProvider] { func mockHandlerCreateRecord() http.HandlerFunc { return encodeJSONHandler(func(req *http.Request, rw http.ResponseWriter) (any, error) { record := internal.TXTRecord{} - err := json.NewDecoder(req.Body).Decode(&record) if err != nil { return nil, err @@ -344,7 +339,6 @@ func checkBasicAuth() servermock.LinkFunc { if username != fakeUsername || apiKey != fakeAPIKey || !ok { rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return } diff --git a/providers/dns/zonomi/zonomi.go b/providers/dns/zonomi/zonomi.go index fe54b80fc..8c7a2943f 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" @@ -25,17 +26,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 +52,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 +76,48 @@ 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 + } + + 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 +125,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..fb1b68773 100644 --- a/providers/dns/zonomi/zonomi_test.go +++ b/providers/dns/zonomi/zonomi_test.go @@ -36,7 +36,6 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() - envTest.ClearEnv() envTest.Apply(test.envVars) @@ -46,7 +45,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 +83,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) } @@ -98,7 +97,6 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) @@ -112,7 +110,6 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() - provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 9c4bc9e61..59205f7f6 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -6,14 +6,11 @@ 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" @@ -22,11 +19,8 @@ import ( "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" - "github.com/go-acme/lego/v4/providers/dns/beget" - "github.com/go-acme/lego/v4/providers/dns/binarylane" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" - "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 +31,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 +53,9 @@ 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 +64,11 @@ 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 +83,9 @@ 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 +95,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 +105,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" @@ -141,7 +114,6 @@ import ( "github.com/go-acme/lego/v4/providers/dns/njalla" "github.com/go-acme/lego/v4/providers/dns/nodion" "github.com/go-acme/lego/v4/providers/dns/ns1" - "github.com/go-acme/lego/v4/providers/dns/octenium" "github.com/go-acme/lego/v4/providers/dns/oraclecloud" "github.com/go-acme/lego/v4/providers/dns/otc" "github.com/go-acme/lego/v4/providers/dns/ovh" @@ -168,26 +140,21 @@ 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" "github.com/go-acme/lego/v4/providers/dns/vultr" "github.com/go-acme/lego/v4/providers/dns/webnames" - "github.com/go-acme/lego/v4/providers/dns/webnamesca" "github.com/go-acme/lego/v4/providers/dns/websupport" "github.com/go-acme/lego/v4/providers/dns/wedos" "github.com/go-acme/lego/v4/providers/dns/westcn" @@ -202,22 +169,16 @@ 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": @@ -234,16 +195,10 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return azuredns.NewDNSProvider() case "baiducloud": return baiducloud.NewDNSProvider() - case "beget": - return beget.NewDNSProvider() - case "binarylane": - return binarylane.NewDNSProvider() case "bindman": return bindman.NewDNSProvider() case "bluecat": return bluecat.NewDNSProvider() - case "bluecatv2": - return bluecatv2.NewDNSProvider() case "bookmyname": return bookmyname.NewDNSProvider() case "brandit": @@ -264,8 +219,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 +229,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 +239,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,20 +263,12 @@ 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": - return edgeone.NewDNSProvider() case "efficientip": 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 +285,16 @@ 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 +323,12 @@ 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 +347,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 +367,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": @@ -472,8 +385,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return nodion.NewDNSProvider() case "ns1": return ns1.NewDNSProvider() - case "octenium": - return octenium.NewDNSProvider() case "oraclecloud": return oraclecloud.NewDNSProvider() case "otc": @@ -526,22 +437,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 +457,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": @@ -562,10 +465,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return vscale.NewDNSProvider() case "vultr": return vultr.NewDNSProvider() - case "webnames", "webnamesru": + case "webnames": return webnames.NewDNSProvider() - case "webnamesca": - return webnamesca.NewDNSProvider() case "websupport": return websupport.NewDNSProvider() case "wedos": diff --git a/providers/http/memcached/memcached.go b/providers/http/memcached/memcached.go index 376ae8c16..b26def2c4 100644 --- a/providers/http/memcached/memcached.go +++ b/providers/http/memcached/memcached.go @@ -33,14 +33,12 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error { var errs []error challengePath := path.Join("/", http01.ChallengePath(token)) - for _, host := range w.hosts { mc, err := memcache.New(host) if err != nil { errs = append(errs, err) continue } - _ = mc.Add(&memcache.Item{ Key: challengePath, Value: []byte(keyAuth), diff --git a/providers/http/memcached/memcached_test.go b/providers/http/memcached/memcached_test.go index 5862efbc6..fb450f988 100644 --- a/providers/http/memcached/memcached_test.go +++ b/providers/http/memcached/memcached_test.go @@ -25,7 +25,6 @@ func loadMemcachedHosts() []string { if memcachedHostsStr != "" { return strings.Split(memcachedHostsStr, ",") } - return nil } @@ -39,7 +38,6 @@ func TestNewMemcachedProviderValid(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } - _, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) } @@ -48,7 +46,6 @@ func TestMemcachedPresentSingleHost(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } - p, err := NewMemcachedProvider(memcachedHosts[0:1]) require.NoError(t, err) @@ -67,7 +64,6 @@ func TestMemcachedPresentMultiHost(t *testing.T) { if len(memcachedHosts) <= 1 { t.Skip("Skipping memcached multi-host tests") } - p, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) @@ -75,7 +71,6 @@ func TestMemcachedPresentMultiHost(t *testing.T) { err = p.Present(domain, token, keyAuth) require.NoError(t, err) - for _, host := range memcachedHosts { mc, err := memcache.New(host) require.NoError(t, err) @@ -89,7 +84,6 @@ func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } - hosts := append(memcachedHosts, "5.5.5.5:11211") p, err := NewMemcachedProvider(hosts) require.NoError(t, err) @@ -98,7 +92,6 @@ func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) { err = p.Present(domain, token, keyAuth) require.NoError(t, err) - for _, host := range memcachedHosts { mc, err := memcache.New(host) require.NoError(t, err) @@ -112,7 +105,6 @@ func TestMemcachedCleanup(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } - p, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) require.NoError(t, p.CleanUp(domain, token, keyAuth)) diff --git a/providers/http/s3/s3.go b/providers/http/s3/s3.go index e277deeea..07e1eed63 100644 --- a/providers/http/s3/s3.go +++ b/providers/http/s3/s3.go @@ -57,7 +57,6 @@ func (s *HTTPProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("s3: failed to upload token to s3: %w", err) } - return nil } diff --git a/providers/http/webroot/webroot.go b/providers/http/webroot/webroot.go index c94c4579c..c5b49caee 100644 --- a/providers/http/webroot/webroot.go +++ b/providers/http/webroot/webroot.go @@ -29,7 +29,6 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error { var err error challengeFilePath := filepath.Join(w.path, http01.ChallengePath(token)) - err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755) if err != nil { return fmt.Errorf("could not create required directories in webroot for HTTP challenge: %w", err) diff --git a/providers/http/webroot/webroot_test.go b/providers/http/webroot/webroot_test.go index 4c55e2b90..124b324a3 100644 --- a/providers/http/webroot/webroot_test.go +++ b/providers/http/webroot/webroot_test.go @@ -29,7 +29,6 @@ func TestHTTPProvider(t *testing.T) { } var data []byte - data, err = os.ReadFile(challengeFilePath) require.NoError(t, err) diff --git a/registration/registar.go b/registration/registar.go index 5d3ea250b..15c28bad6 100644 --- a/registration/registar.go +++ b/registration/registar.go @@ -15,7 +15,7 @@ const mailTo = "mailto:" // of which the client needs to keep track itself. // WARNING: will be removed in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855. type Resource struct { - Body acme.Account `json:"body"` + Body acme.Account `json:"body,omitempty"` URI string `json:"uri,omitempty"` } @@ -160,7 +160,6 @@ func (r *Registrar) ResolveAccountByKey() (*Resource, error) { log.Infof("acme: Trying to resolve account by key") accMsg := acme.Account{OnlyReturnExisting: true} - account, err := r.core.Accounts.New(accMsg) if err != nil { return nil, err diff --git a/registration/registar_test.go b/registration/registar_test.go index 43df1d648..45fe33e82 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -3,28 +3,29 @@ package registration import ( "crypto/rand" "crypto/rsa" - "fmt" "net/http" "testing" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - server := tester.MockACMEServer(). - Route("/account", - http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Location", - fmt.Sprintf("http://%s/account", req.Context().Value(http.LocalAddrContextKey))) + mux, apiURL := tester.SetupFakeAPI(t) - servermock.JSONEncode(acme.Account{Status: "valid"}).ServeHTTP(rw, req) - })). - BuildHTTPS(t) + mux.HandleFunc("/account", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", apiURL+"/account") + err := tester.WriteJSONResponse(w, acme.Account{ + Status: "valid", + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -35,7 +36,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { privatekey: key, } - core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) + core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) registrar := NewRegistrar(core, user)