Compare commits

...

99 commits

Author SHA1 Message Date
Fernandez Ludovic 96bb0ba904 chore: improve issue templates 2024-05-31 16:22:18 +02:00
Fernandez Ludovic e508b664b3 Detach v4.17.3 2024-05-28 16:24:48 +02:00
Fernandez Ludovic 8abe1008bb Prepare release v4.17.3 2024-05-28 16:24:35 +02:00
Fernandez Ludovic 96a8381a6b fix: disable snap release 2024-05-28 16:20:55 +02:00
Fernandez Ludovic 4d7fcb6048 Detach v4.17.2 2024-05-28 14:56:30 +02:00
Fernandez Ludovic 65e8940311 Prepare release v4.17.2 2024-05-28 14:56:10 +02:00
Fernandez Ludovic 71b48f8217 fix: disable Solaris build 2024-05-28 14:55:05 +02:00
Fernandez Ludovic 942ba6f591 Detach v4.17.1 2024-05-28 13:30:57 +02:00
Fernandez Ludovic de6f9d26b1 Prepare release v4.17.1 2024-05-28 13:30:57 +02:00
Fernandez Ludovic f038102ef9 chore: fix snapcraft release 2024-05-28 13:28:21 +02:00
Fernandez Ludovic 975c40a8bb Detach v4.17.0 2024-05-28 12:46:36 +02:00
Fernandez Ludovic d3d59c166e Prepare release v4.17.0 2024-05-28 12:46:36 +02:00
jotasi 5eb87685e2
pdns: reconstruct zone URLs to enable non-root folder API endpoints (#2141)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-05-27 22:18:24 +02:00
Henrik Alves f89e257694
dode: update API URL (#2191) 2024-05-27 15:28:32 +02:00
Ludovic Fernandez 220c608c80
chore: update linter (#2190) 2024-05-27 13:04:32 +02:00
Igor Zornik 92bde4cd56
chore: remove remaining deprecated ARI call (#2189) 2024-05-21 20:14:25 +02:00
Ludovic Fernandez d39d57fbc9
chore: remove useless file 2024-05-12 03:23:19 +02:00
Ludovic Fernandez bf10a46784
chore: update linter (#2183) 2024-05-09 19:31:20 +00:00
Ludovic Fernandez c61aeba3e2
godaddy: documentation new API limitations (#2182) 2024-05-09 21:05:42 +02:00
Ludovic Fernandez 11b4beff7e
route53: adds option to not wait for changes (#2181) 2024-05-09 21:05:21 +02:00
Orgad Shaneh 2ec9e42ee3
azuredns: use TenantID also for cli authentication (#2176) 2024-05-07 18:56:05 +00:00
Igor Zornik 983c181e45
feat: renewal retry after value (#2170) 2024-05-07 17:01:12 +00:00
Ludovic Fernandez f6d1413a3f
exoscale: simplify record creation (#2179) 2024-05-07 10:30:44 +00:00
Idix acd338259d
ovh: add OAuth2 authentication (#2173)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-05-06 15:43:25 +02:00
Ludovic Fernandez 42aa57e2b9
exec: stream command output (#2166) 2024-04-21 18:01:40 +02:00
Ludovic Fernandez 76eb1eac8a
feat: add LEGO_ISSUER_CERT_PATH to hook (#2164) 2024-04-17 13:09:06 +00:00
Ludovic Fernandez d60c335cc0
feat: fills LEGO_CERT_PFX_PATH and LEGO_CERT_PEM_PATH only when needed (#2160) 2024-04-16 23:10:59 +02:00
Ludovic Fernandez 55dd478cb2
chore: update dependencies (#2156) 2024-04-15 15:54:21 +02:00
Ludovic Fernandez ca32b56550
oracle: update API client (#2155) 2024-04-14 23:27:26 +02:00
Artem Chirkov 8623f0df01
Add DNS provider for Selectel v2 (#2152)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-04-10 10:29:29 +00:00
Fernandez Ludovic a832515ad5 chore: fix typos 2024-04-10 11:19:21 +02:00
Ludovic Fernandez ef9086a0f9
httpnet: add provider to NewDNSChallengeProviderByName (#2146) 2024-03-27 11:19:07 +01:00
xpume 8dd1fa5a32
docs: fix link to alibaba API documentation (#2145) 2024-03-24 21:39:59 +00:00
Ludovic Fernandez 3371145f01
chore: update to go1.22 (#2144) 2024-03-20 20:30:35 +01:00
Markus Blaschke 874e3ea023
azuredns: servicediscovery for zones (#2140)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-20 04:36:35 +01:00
Ludovic Fernandez 27fd142ca1
scaleway: add alternative env var names (#2136) 2024-03-20 03:31:18 +00:00
Guillaume Belanger 61553c4195
chore: add snap to release packages (#2134) 2024-03-15 03:12:08 +01:00
Fernandez Ludovic 441775ec65 Detach v4.16.1 2024-03-11 00:49:12 +01:00
Fernandez Ludovic 40dcce60be Prepare release v4.16.1 2024-03-11 00:49:12 +01:00
Ludovic Fernandez 19bbefbc8c
fix: don't generate ARI cert ID if ARI is not enable (#2138) 2024-03-11 00:04:53 +01:00
Fernandez Ludovic 719d26c0fc Detach v4.16.0 2024-03-10 00:15:13 +01:00
Fernandez Ludovic 5bde3fbedd Prepare release v4.16.0 2024-03-10 00:15:13 +01:00
Ludovic Fernandez 008adfddca
Add DNS provider for Shellrent (#2126) 2024-03-08 17:49:57 +01:00
Samantha 6dd8d035d1
feat: implement 'replaces' field in newOrder and draft-ietf-acme-ari-03 CertID changes (#2114) 2024-03-08 15:22:09 +00:00
Ludovic Fernandez adea063bb1
chore: update to github.com/go-jose/go-jose/v4 (#2130) 2024-03-08 14:07:55 +01:00
fuku 82e9a5e2a9
nifcloud: fix bug in case of same auth zone (#2125)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-04 20:17:28 +01:00
Ludovic Fernandez 6933296e2f
easydns: fix zone detection (#2121) 2024-03-03 15:41:55 +01:00
Jess Thrysoee a7ca3d7f1c
bunny: support delegated sub-domains (#2123) 2024-02-29 21:08:03 +00:00
Ludovic Fernandez 9d0cd24533
fix: test on CI (#2119) 2024-02-24 20:49:40 +01:00
Ludovic Fernandez fac40196c5
Add DNS provider for Mail-in-a-Box (#2110) 2024-02-24 01:31:16 +01:00
Ludovic Fernandez 1106ad382f
ns1: fix record creation (#2115) 2024-02-16 21:24:58 +01:00
Fernandez Ludovic fd6047a1b8 chore: use org variables inside the CI 2024-02-15 02:22:55 +01:00
Ludovic Fernandez ba67a265c0
feat: improve errors and logs related to DNS call (#2109) 2024-02-11 14:37:09 +01:00
Ludovic Fernandez 7fe1796157
chore: minor changes (#2108) 2024-02-09 21:55:43 +01:00
Ikko Eltociear Ashimine b9b0412f7c
chore: fix typo (#2106) 2024-02-08 16:14:25 +00:00
Ludovic Fernandez 23824af555
chore: update to go1.21 (#2103) 2024-02-08 03:16:48 +01:00
Ludovic Fernandez c5a95c4cd0
cpanel: remove custom DNS call (#2102) 2024-02-06 17:17:59 +01:00
Ludovic Fernandez 83ff393131
Add DNS provider for CPanel and WHM (#1977) 2024-02-04 19:43:54 +01:00
Mike Dalrymple 719adc3964
route53: fix example (#2100) 2024-02-03 02:36:43 +00:00
Fernandez Ludovic 6decaed8a3 chore: update CI 2024-02-02 02:21:15 +01:00
Ludovic Fernandez ac6c0a5f63
chore: replace mitchellh/mapstructure by go-viper/mapstructure/v2 (#2095) 2024-01-30 17:55:09 +00:00
Rahil Bhimjiani 1a3de70183
docs: add installation for Gentoo (#2094)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-30 13:06:03 +00:00
Fernandez Ludovic ec8306f4b5 Detach v4.15.0 2024-01-28 18:02:59 +01:00
Fernandez Ludovic 46fe435c2c Prepare release v4.15.0 2024-01-28 18:02:59 +01:00
Ludovic Fernandez 755d479e62
chore: update dependencies (#2092) 2024-01-28 14:04:31 +01:00
pschou 4eab81a9eb
feat(cli): add format option for PFX encoding (#2063)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-27 22:44:14 +00:00
m0t1x 9c1a856b73
pdns: optional custom API version (#2019)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-27 22:23:10 +00:00
Martin Weindel d263a28c64
feat: support simplified issuance for very long domain names at Let's Encrypt (#2054)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-26 23:16:47 +00:00
Ludovic Fernandez ad6e38e7db
desec: increase default propagation interval (#2089) 2024-01-25 14:21:11 +01:00
Samantha 3c73f624ba
feat: update CertID format as per draft-ietf-acme-ari-02 (#2066) 2024-01-24 21:02:50 +00:00
Ludovic Fernandez bb830677d1
constellix: follow rate limiting headers (#2085) 2024-01-23 14:18:54 +00:00
Ludovic Fernandez 7fd1704282
inwx: improve sleep calculation (#2086) 2024-01-19 13:55:57 +01:00
Phil Pennock 143aa4f3ee
gcloud: support GCE_ZONE_ID to bypass zone list (#2081)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-18 21:30:20 +00:00
Günther Noack 9d4c60e67a
inwx: wait before generating new TOTP TANs (#2084)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-18 20:51:57 +00:00
Ludovic Fernandez c17f659c5d
ionos: fix DNS record removal (#2083) 2024-01-17 22:18:17 +01:00
pschou c847ac4a4c
feat(cli): add environment variable for specifying alternate directory URL (#2061) 2024-01-12 21:11:42 +00:00
George Kusayko 3ba40ff7da
Add DNS provider for Webnames (#2077)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-12 21:23:03 +01:00
Danil Solovyov 68dc83ae0a
cloudru: change default URLs (#2076) 2024-01-10 00:31:04 +00:00
Patrik e98dea02de
scaleway: add cname support (#2075) 2024-01-06 13:02:10 +01:00
Fernandez Ludovic 6da922047a chore: change test ports (related to Windows and CI) 2024-01-06 12:43:26 +01:00
Alexis Savin 7186ebb6f1
efficientip: add insecure skip verify option (#2052)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-12 21:29:57 +01:00
Alexander Kazarin 5af3c6c042
regru: HTTP method changed to POST (#2051)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-12 13:38:13 +01:00
Alexander Kazarin cab8e1f556
regru: client certificate support (#2050)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-10 02:15:33 +01:00
Ludovic Fernandez d51b5e408b
chore: update linter (#2046) 2023-10-31 14:08:50 +01:00
pchanvallon 4f242c93e6
azuredns: allow oidc authentication (#2036)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-30 18:26:35 +01:00
Ludovic Fernandez c9ff534e39
nifcloud: fix API requests (#2039) 2023-10-18 09:53:04 +00:00
Ludovic Fernandez 52990b3c9e
liquidweb: add LWAPI_ prefix for env vars (#2034) 2023-10-15 01:12:55 +02:00
Jack Hayhurst 8afdc9d01c
liquidweb: detect zone automatically (#2031)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-14 17:08:29 +02:00
Christoph 2140e6befe
gandiv5: Add "Bearer" prefix to the auth header (#2029) 2023-10-07 00:22:02 +00:00
pchanvallon bf8c7abf6d
azuredns: provide the ability to select authentication methods (#2026)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-05 11:25:44 +00:00
volayvaz c2fd4498e5
otc: sequential challenge (#2023) 2023-09-21 14:04:52 +00:00
Ludovic Fernandez b523518108
ipv64: remove unused option (#2022) 2023-09-20 21:30:21 +00:00
Lars Wiegman d0aa6b3c0d
doc: update Designate documentation (#1465) 2023-09-20 19:47:00 +00:00
Ludovic Fernandez 113648a368
gandiv5: add Personal Access Token support (#2007) 2023-09-20 03:42:25 +00:00
Ludovic Fernandez 766e581f8d
Add DNS provider for http.net (#2014) 2023-09-20 01:40:26 +00:00
Fernandez Ludovic a05e3832a1 Detach v4.14.2 2023-09-20 01:32:03 +02:00
Fernandez Ludovic a6ddcac65a Prepare release v4.14.2 2023-09-20 01:31:52 +02:00
Fernandez Ludovic 5ef996ee05 chore: temporary workaround 2023-09-20 01:30:33 +02:00
Fernandez Ludovic 8a7fd67c58 Detach v4.14.1 2023-09-20 00:53:13 +02:00
338 changed files with 9106 additions and 2224 deletions

1
.github/FUNDING.yml vendored
View file

@ -1 +0,0 @@
github: ldez

View file

@ -42,6 +42,7 @@ body:
- Through Caddy
- Through Terraform ACME provider
- Through Bitnami
- Through Zoraxy
- Other
validations:
required: true

View file

@ -21,6 +21,7 @@ body:
- Through Caddy
- Through Terraform ACME provider
- Through Bitnami
- Through Zoraxy
- Other
validations:
required: true

View file

@ -12,23 +12,23 @@ jobs:
runs-on: ubuntu-latest
env:
GO_VERSION: stable
HUGO_VERSION: '0.117.0'
HUGO_VERSION: 0.117.0
CGO_ENABLED: 0
steps:
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
# https://github.com/marketplace/actions/checkout
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
# 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: Generate DNS docs
run: make generate-dns
@ -42,7 +42,7 @@ jobs:
# https://github.com/marketplace/actions/github-pages
- name: Deploy to GitHub Pages
uses: crazy-max/ghaction-github-pages@v3
uses: crazy-max/ghaction-github-pages@v4
with:
target_branch: gh-pages
build_dir: docs/public

View file

@ -16,17 +16,17 @@ jobs:
strategy:
matrix:
go-version: [ stable, oldstable ]
go-version: [ stable ]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

View file

@ -13,8 +13,8 @@ jobs:
runs-on: ubuntu-latest
env:
GO_VERSION: stable
GOLANGCI_LINT_VERSION: v1.54.1
HUGO_VERSION: '0.117.0'
GOLANGCI_LINT_VERSION: v1.59.0
HUGO_VERSION: 0.117.0
CGO_ENABLED: 0
LEGO_E2E_TESTS: CI
MEMCACHED_HOSTS: localhost:11211
@ -23,13 +23,13 @@ jobs:
# https://github.com/marketplace/actions/checkout
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
@ -46,10 +46,10 @@ jobs:
golangci-lint --version
- name: Install Pebble
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@main
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@3fe019bbc0a41ed16e2fee31592bb91751acaa47
- name: Install challtestsrv
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@main
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@3fe019bbc0a41ed16e2fee31592bb91751acaa47
- name: Set up a Memcached server
uses: niden/actions-memcached@v7

View file

@ -15,6 +15,12 @@ jobs:
CGO_ENABLED: 0
steps:
# temporary workaround for an error in free disk space action
# https://github.com/jlumbroso/free-disk-space/issues/14
- name: Update Package List and Remove Dotnet
run: |
sudo apt-get update
sudo apt-get remove -y '^dotnet-.*'
# https://github.com/marketplace/actions/free-disk-space-ubuntu
- name: Free Disk Space
@ -32,12 +38,12 @@ jobs:
swap-storage: false
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
@ -47,17 +53,21 @@ jobs:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- name: Install snapcraft
run: sudo snap install snapcraft --classic
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# https://goreleaser.com/ci/actions/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release -p 1 --clean --timeout=90m
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}

View file

@ -1,14 +1,12 @@
run:
timeout: 10m
skip-files: []
linters-settings:
govet:
check-shadowing: true
enable:
- shadow
gocyclo:
min-complexity: 12
maligned:
suggest-new: true
goconst:
min-len: 3
min-occurrences: 3
@ -88,20 +86,20 @@ linters-settings:
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
testifylint:
disable:
- require-error
- go-require
perfsprint:
err-error: true
errorf: true
sprintf1: true
strconcat: false
linters:
enable-all: true
disable:
- deadcode # deprecated
- exhaustivestruct # deprecated
- golint # deprecated
- ifshort # deprecated
- interfacer # deprecated
- maligned # deprecated
- nosnakecase # deprecated
- scopelint # deprecated
- structcheck # deprecated
- varcheck # deprecated
- gomnd # deprecated
- cyclop # duplicate of gocyclo
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
@ -111,13 +109,13 @@ linters:
- dupl # not relevant
- prealloc # too many false-positive
- bodyclose # too many false-positive
- gomnd
- mnd
- testpackage # not relevant
- tparallel # not relevant
- paralleltest # not relevant
- nestif # too many false-positive
- wrapcheck
- goerr113 # not relevant
- err113 # not relevant
- nlreturn # not relevant
- wsl # not relevant
- exhaustive # not relevant
@ -137,10 +135,12 @@ linters:
- nonamedreturns
- musttag # false-positive https://github.com/junk1tm/musttag/issues/17
- gosmopolitan # not relevant
- exportloopref # Useless with go1.22
- canonicalheader # Can create side effects in the context of API clients
issues:
exclude-use-default: false
max-per-linter: 0
max-issues-per-linter: 0
max-same-issues: 0
exclude:
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
@ -152,6 +152,10 @@ issues:
- funlen
- goconst
- maintidx
- path: (.+)_test.go
text: 'Error return value of `fmt.Fprintln` is not checked'
linters:
- errcheck
- path: providers/dns/dns_providers.go
linters:
- gocyclo
@ -223,4 +227,12 @@ issues:
- path: providers/dns/hosttech/internal/client_test.go
text: 'Duplicate words \(0\) found'
- path: cmd/cmd_renew.go
text: 'cyclomatic complexity 16 of func `renewForDomains` is high'
text: 'cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high'
- path: providers/dns/cpanel/cpanel.go
text: 'cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high'
output:
sort-results: true
sort-order:
- linter
- file

View file

@ -17,7 +17,7 @@ builds:
- linux
- freebsd
- openbsd
- solaris
# - solaris # https://github.com/gofrs/flock/issues/60
goarch:
- amd64
- 386
@ -41,6 +41,14 @@ builds:
- goos: openbsd
goarch: arm
changelog:
sort: asc
filters:
exclude:
- '(?i)^chore:'
- '(?i)^Detach v[\d|.]+'
- '(?i)^Prepare release v[\d|.]+'
archives:
- id: lego
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}'
@ -133,3 +141,33 @@ dockers:
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/arm/v7'
# Disabled because https://github.com/go-acme/lego/pull/2134#issuecomment-2135293270
snapcrafts:
- name: lego
disable: true
grade: stable
confinement: strict
license: MIT
base: core22
publish: true
summary: Lego is a Let's Encrypt/ACME client.
description: |
Lego is a Let's Encrypt/ACME client written in Go.
The lego snap makes it easy to install and use Lego on any Linux distribution that supports snaps.
Usage:
* `sudo snap install lego`
* `sudo lego --email="you@example.com" --domains="example.com" --server=https://acme-staging-v02.api.letsencrypt.org/directory --http --http.port :8080 run
channel_templates:
- edge
apps:
lego:
command: bin/lego
environment:
LEGO_PATH: /var/snap/lego/common/.lego
plugs:
- network-bind

View file

@ -1,42 +1,165 @@
# Changelog
## [v4.14.1] - 2023-09-19
## [v4.17.3] - 2024-05-28
### Added
- **[dnsprovider]** Add DNS provider for Selectel v2
- **[dnsprovider]** route53: adds option to not wait for changes
- **[dnsprovider]** ovh: add OAuth2 authentication
- **[dnsprovider]** azuredns: use TenantID also for cli authentication
- **[dnsprovider]** godaddy: documentation about new API limitations
- **[cli]** feat: add LEGO_ISSUER_CERT_PATH to hook
### Changed
- **[dnsprovider]** dode: update API URL
- **[dnsprovider]** exec: stream command output
- **[dnsprovider]** oracle: update API client
- **[dnsprovider]** azuredns: servicediscovery for zones
- **[dnsprovider]** scaleway: add alternative env var names
- **[dnsprovider]** exoscale: simplify record creation
- **[dnsprovider]** httpnet: add provider to NewDNSChallengeProviderByName
- **[cli]** feat: fills LEGO_CERT_PFX_PATH and LEGO_CERT_PEM_PATH only when needed
- **[lib,ari]** feat: renewal retry after value
### Fixed
- **[dnsprovider]** pdns: reconstruct zone URLs to enable non-root folder API endpoints
- **[dnsprovider]** alidns: fix link to API documentation
## [v4.17.2] - 2024-05-28
Canceled due to a release failure related to Snapcraft.
The Snapcraft release are disabled for now.
## [v4.17.1] - 2024-05-28
Canceled due to a release failure related to oci-go-sdk.
The module `github.com/oracle/oci-go-sdk/v65` uses `github.com/gofrs/flock` but flock doesn't support some platform (like Solaris):
- https://github.com/gofrs/flock/issues/60
Due to that we will remove the Solaris build.
## [v4.17.0] - 2024-05-28
Canceled due to a release failure related to Snapcraft.
## [v4.16.1] - 2024-03-10
### Fixed
- **[cli,ari]** fix: don't generate ARI cert ID if ARI is not enable
## [v4.16.0] - 2024-03-09
### Added
- **[dnsprovider]** Add DNS provider for Shellrent
- **[dnsprovider]** Add DNS provider for Mail-in-a-Box
- **[dnsprovider]** Add DNS provider for CPanel and WHM
### Changed
- **[lib,ari]** Implement 'replaces' field in newOrder and draft-ietf-acme-ari-03 CertID changes
- **[log]** feat: improve errors and logs related to DNS call
- **[lib]** update to go-jose/go-jose/v4 v4.0.1
### Fixed
- **[dnsprovider]** nifcloud: fix bug in case of same auth zone
- **[dnsprovider]** bunny: Support delegated subdomains
- **[dnsprovider]** easydns: fix zone detection
- **[dnsprovider]** ns1: fix record creation
## [v4.15.0] - 2024-01-28
### Added
- **[dnsprovider]** Add DNS provider for http.net
- **[dnsprovider]** Add DNS provider for Webnames
### Changed
- **[cli]** Add environment variable for specifying alternate directory URL
- **[cli]** Add format option for PFX encoding
- **[lib]** Support simplified issuance for very long domain names at Let's Encrypt
- **[lib]** Update CertID format as per draft-ietf-acme-ari-02
- **[dnsprovider]** azuredns: allow OIDC authentication
- **[dnsprovider]** azuredns: provide the ability to select authentication methods
- **[dnsprovider]** efficientip: add insecure skip verify option
- **[dnsprovider]** gandiv5: add Personal Access Token support
- **[dnsprovider]** gcloud: support GCE_ZONE_ID to bypass zone list
- **[dnsprovider]** liquidweb: add LWAPI_ prefix for env vars
- **[dnsprovider]** liquidweb: detect zone automatically
- **[dnsprovider]** pdns: optional custom API version
- **[dnsprovider]** regru: client certificate support
- **[dnsprovider]** regru: HTTP method changed to POST
- **[dnsprovider]** scaleway: add cname support
### Fixed
- **[dnsprovider]** cloudru: change default URLs
- **[dnsprovider]** constellix: follow rate limiting headers
- **[dnsprovider]** desec: increase default propagation interval
- **[dnsprovider]** gandiv5: Add "Bearer" prefix to the auth header
- **[dnsprovider]** inwx: improve sleep calculation
- **[dnsprovider]** inwx: wait before generating new TOTP TANs
- **[dnsprovider]** ionos: fix DNS record removal
- **[dnsprovider]** ipv64: remove unused option
- **[dnsprovider]** nifcloud: fix API requests
- **[dnsprovider]** otc: sequential challenge
## [v4.14.1] - 2023-09-20
### Fixed
### Fixed:
- **[dnsprovider]** bunny: fix zone detection
- **[dnsprovider]** bunny: use NRDCG fork
- **[dnsprovider]** ovh: update client to v1.4.2
## [v4.14.1] - 2023-09-19
Cancelled due to CI failure.
## [v4.14.0] - 2023-08-20
### Added:
### Added
- **[dnsprovider]** Add DNS provider for Yandex 360
- **[dnsprovider]** Add DNS provider for cloud.ru
- **[httpprovider]** Adding S3 support for HTTP domain validation
### Changed:
### Changed
- **[cli]** Allow to set EAB kid and hmac via environment variables
- **[dnsprovider]** Migrate to aws-sdk-go-v2 (lightsail, route53)
### Fixed:
### Fixed
- **[dnsprovider]** nearlyfreespeech: fix authentication
- **[dnsprovider]** pdns: fix notify
- **[dnsprovider]** route53: avoid unexpected records deletion
## [v4.13.3] - 2023-07-25
### Fixed:
### Fixed
- **[dnsprovider]** azuredns: fix configuration from env vars
- **[dnsprovider]** gcore: change API domain
## [v4.13.2] - 2023-07-21
### Fixed:
### Fixed
- **[dnsprovider]** servercow: fix regression
## [v4.13.1] - 2023-07-20
### Added:
### Added
- **[dnsprovider]** Add DNS provider for IPv64
- **[dnsprovider]** Add DNS provider for Metaname
- **[dnsprovider]** Add DNS provider for RcodeZero
@ -44,10 +167,12 @@
- **[dnsprovider]** azure: new implementation based on the new API client
- **[lib]** Experimental option to force DNS queries to use TCP
### Changed:
### Changed
- **[dnsprovider]** cloudflare: update api client to v0.70.0
### Fixed:
### Fixed
- **[dnsprovider,cname]** fix: ensure case-insensitive comparison of CNAME records
- **[cli]** fix: list command
- **[lib]** fix: ARI explanationURL
@ -58,33 +183,39 @@ Cancelled due to a CI issue (no space left on device).
## [v4.12.2] - 2023-06-19
### Fixed:
### Fixed
- **[dnsprovider]** dnsmadeeasy: fix DeleteRecord
- **[lib]** fix: read status code from response
## [v4.12.1] - 2023-06-06
### Fixed:
### Fixed
- **[dnsprovider]** pdns: fix record value
## [v4.12.0] - 2023-05-28
### Added:
### Added
- **[lib,cli]** Initial ACME Renewal Info (ARI) Implementation
- **[dnsprovider]** Add DNS provider for Derak Cloud
- **[dnsprovider]** route53: pass ExternalID property to STS:AssumeRole API operation
- **[lib,cli]** Support custom duration for certificate
### Changed:
### Changed
- **[dnsprovider]** Refactor DNS provider and client implementations
### Fixed:
### Fixed
- **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used
- **[cli]** fix: archive only domain-related files on revoke
## [v4.11.0] - 2023-05-02
### Added:
### Added
- **[lib]** Support for certificate with raw IP SAN (RFC8738)
- **[dnsprovider]** Add Brandit.com as DNS provider
- **[dnsprovider]** Add DNS provider for Bunny
@ -92,13 +223,15 @@ Cancelled due to a CI issue (no space left on device).
- **[dnsprovider]** Add Google Domains as DNS provider
- **[dnsprovider]** Add DNS provider for Plesk
### Changed:
### Changed
- **[cli]** feat: add LEGO_CERT_PEM_PATH and LEGO_CERT_PFX_PATH to run hook
- **[lib,cli]** feat: add RSA 3072
- **[dnsprovider]** gcloud: update google APIs to latest version
- **[lib,dnsprovider,cname]** chore: replace GetRecord by GetChallengeInfo
### Fixed:
### Fixed
- **[dnsprovider]** rimuhosting: fix API base URL
## [v4.10.2] - 2023-02-26
@ -107,26 +240,30 @@ Fix Docker image builds.
## [v4.10.1] - 2023-02-25
### Fixed:
### Fixed
- **[dnsprovider,cname]** acmedns: fix CNAME support
- **[dnsprovider]** dynu: fix subdomain support
## [v4.10.0] - 2023-02-10
### Added:
### Added
- **[dnsprovider]** Add DNS provider for dnsHome.de
- **[dnsprovider]** Add DNS provider for Liara
- **[dnsprovider]** Add DNS provider for UltraDNS
- **[dnsprovider]** Add DNS provider for Websupport
### Changed:
### Changed
- **[dnsprovider]** ibmcloud: add support for subdomains
- **[dnsprovider]** infomaniak: CNAME support
- **[dnsprovider]** namesilo: add cleanup before add a DNS record
- **[dnsprovider]** route53: Allow static credentials to be supplied
- **[dnsprovider]** tencentcloud: support punycode domain
### Fixed:
### Fixed
- **[dnsprovider]** alidns: filter on record type
- **[dnsprovider]** arvancloud: replace arvancloud.com by arvancloud.ir
- **[dnsprovider]** hetzner: improve zone ID detection
@ -136,12 +273,12 @@ Fix Docker image builds.
## [v4.9.1] - 2022-11-25
### Changed:
### Changed
-
- **[lib,cname]** cname: add log about CNAME entries
- **[dnsprovider]** regru: improve error handling
### Fixed:
### Fixed
-
- **[dnsprovider,cname]** fix CNAME support for multiple DNS providers
- **[dnsprovider,cname]** duckdns: fix CNAME support
@ -151,7 +288,7 @@ Fix Docker image builds.
## [v4.9.0] - 2022-10-03
### Added:
### Added
- **[dnsprovider]** Add DNS provider for CIVO
- **[dnsprovider]** Add DNS provider for VK Cloud
@ -160,7 +297,7 @@ Fix Docker image builds.
- **[dnsprovider]** loopia: add configurable API endpoint
- **[dnsprovider]** pdns: notify secondary servers after updates
### Changed:
### Changed
- **[dnsprovider]** allinkl: removed deprecated sha1 hashing
- **[dnsprovider]** auroradns: update authentification
@ -173,7 +310,8 @@ Fix Docker image builds.
- **[lib,cname]** add recursive CNAME lookup support
- **[lib]** Remove embedded issuer certificates from issued certificate if bundle is false
### Fixed:
### Fixed
- **[dnsprovider]** luadns: fix cname support
- **[dnsprovider]** njalla: fix record id unmarshal error
- **[dnsprovider]** tencentcloud: fix subdomain error
@ -538,7 +676,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
- **[dnsprovider]** Add DNS provider for Constellix
- **[dnsprovider]** Add DNS provider for Servercow.
- **[dnsprovider]** Add DNS provider for Scaleway
- **[cli]** Add &#34;LEGO_PATH&#34; environment variable
- **[cli]** Add "LEGO_PATH" environment variable
### Changed:
@ -551,7 +689,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
### Fixed:
- **[dnsprovider]** zoneee: fix subdomains.
- **[dnsprovider]** designate: Don&#39;t clean up managed records like SOA and NS
- **[dnsprovider]** designate: Don't clean up managed records like SOA and NS
- **[dnsprovider]** dnspod: update lib.
- **[lib]** crypto: Treat CommonName as optional
- **[lib]** chore: update cenkalti/backoff to v4.
@ -576,7 +714,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
### Changed:
- **[dnsprovider]** httpreq: Allow use environment vars from a `_FILE` file
- **[lib]** Don&#39;t deactivate valid authorizations
- **[lib]** Don't deactivate valid authorizations
- **[lib]** Expose more SOA fields found by dns01.FindZoneByFqdn
### Fixed:
@ -749,7 +887,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated.
- **[lib]** Adds `Remove` for challenges
- **[lib]** Add version to xenolf-acme in User-Agent.
- **[dnsprovider]** The ability for a DNS provider to solve the challenge sequentially
- **[dnsprovider]** Add DNS provider for &#34;HTTP request&#34;.
- **[dnsprovider]** Add DNS provider for "HTTP request".
- **[dnsprovider]** Add DNS Provider for Vscale
- **[dnsprovider]** Add DNS Provider for TransIP
- **[dnsprovider]** Add DNS Provider for inwx
@ -879,7 +1017,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated.
### Added:
- lib: A new DNS provider for OTC.
- lib: The `AWS_HOSTED_ZONE_ID` environment variable for the Route53 DNS provider to directly specify the zone.
- lib: The `RFC2136_TIMEOUT` enviroment variable to make the timeout for the RFC2136 provider configurable.
- lib: The `RFC2136_TIMEOUT` environment variable to make the timeout for the RFC2136 provider configurable.
- lib: The `GCE_SERVICE_ACCOUNT_FILE` environment variable to specify a service account file for the Google Cloud DNS provider.
### Fixed:

View file

@ -16,7 +16,7 @@ Let's Encrypt client and ACME library written in Go.
- ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html)
- Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS ApplicationLayer Protocol Negotiation (ALPN) Challenge Extension
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses
- Support [draft-ietf-acme-ari-01](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
- Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
- Register with CA
- Obtain certificates, both from scratch or with an existing CSR
- Renew certificates
@ -58,33 +58,34 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [Azure (deprecated)](https://go-acme.github.io/lego/dns/azure/) | [Azure DNS](https://go-acme.github.io/lego/dns/azuredns/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) |
| [Brandit](https://go-acme.github.io/lego/dns/brandit/) | [Bunny](https://go-acme.github.io/lego/dns/bunny/) | [Checkdomain](https://go-acme.github.io/lego/dns/checkdomain/) | [Civo](https://go-acme.github.io/lego/dns/civo/) |
| [Cloud.ru](https://go-acme.github.io/lego/dns/cloudru/) | [CloudDNS](https://go-acme.github.io/lego/dns/clouddns/) | [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) |
| [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Constellix](https://go-acme.github.io/lego/dns/constellix/) | [Derak Cloud](https://go-acme.github.io/lego/dns/derak/) |
| [deSEC.io](https://go-acme.github.io/lego/dns/desec/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) |
| [dnsHome.de](https://go-acme.github.io/lego/dns/dnshomede/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod (deprecated)](https://go-acme.github.io/lego/dns/dnspod/) | [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) |
| [Domeneshop](https://go-acme.github.io/lego/dns/domeneshop/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) |
| [Dynu](https://go-acme.github.io/lego/dns/dynu/) | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Efficient IP](https://go-acme.github.io/lego/dns/efficientip/) | [Epik](https://go-acme.github.io/lego/dns/epik/) |
| [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | [freemyip.com](https://go-acme.github.io/lego/dns/freemyip/) | [G-Core](https://go-acme.github.io/lego/dns/gcore/) |
| [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) |
| [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Google Domains](https://go-acme.github.io/lego/dns/googledomains/) | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) |
| [Hosttech](https://go-acme.github.io/lego/dns/hosttech/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) |
| [IBM Cloud (SoftLayer)](https://go-acme.github.io/lego/dns/ibmcloud/) | [IIJ DNS Platform Service](https://go-acme.github.io/lego/dns/iijdpf/) | [Infoblox](https://go-acme.github.io/lego/dns/infoblox/) | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) |
| [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Ionos](https://go-acme.github.io/lego/dns/ionos/) |
| [IPv64](https://go-acme.github.io/lego/dns/ipv64/) | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) |
| [Liara](https://go-acme.github.io/lego/dns/liara/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Loopia](https://go-acme.github.io/lego/dns/loopia/) |
| [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [Metaname](https://go-acme.github.io/lego/dns/metaname/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) |
| [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) |
| [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/) |
| [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [Njalla](https://go-acme.github.io/lego/dns/njalla/) | [Nodion](https://go-acme.github.io/lego/dns/nodion/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) |
| [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [plesk.com](https://go-acme.github.io/lego/dns/plesk/) |
| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/) |
| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) |
| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) |
| [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) |
| [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/) | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) |
| [Vercel](https://go-acme.github.io/lego/dns/vercel/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/) |
| [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
| [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/) | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/) | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) |
| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | |
| [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Constellix](https://go-acme.github.io/lego/dns/constellix/) | [CPanel/WHM](https://go-acme.github.io/lego/dns/cpanel/) |
| [Derak Cloud](https://go-acme.github.io/lego/dns/derak/) | [deSEC.io](https://go-acme.github.io/lego/dns/desec/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) |
| [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [dnsHome.de](https://go-acme.github.io/lego/dns/dnshomede/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod (deprecated)](https://go-acme.github.io/lego/dns/dnspod/) |
| [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | [Domeneshop](https://go-acme.github.io/lego/dns/domeneshop/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) |
| [Dyn](https://go-acme.github.io/lego/dns/dyn/) | [Dynu](https://go-acme.github.io/lego/dns/dynu/) | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Efficient IP](https://go-acme.github.io/lego/dns/efficientip/) |
| [Epik](https://go-acme.github.io/lego/dns/epik/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | [freemyip.com](https://go-acme.github.io/lego/dns/freemyip/) |
| [G-Core](https://go-acme.github.io/lego/dns/gcore/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) |
| [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Google Domains](https://go-acme.github.io/lego/dns/googledomains/) | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) |
| [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [Hosttech](https://go-acme.github.io/lego/dns/hosttech/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [http.net](https://go-acme.github.io/lego/dns/httpnet/) |
| [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [IBM Cloud (SoftLayer)](https://go-acme.github.io/lego/dns/ibmcloud/) | [IIJ DNS Platform Service](https://go-acme.github.io/lego/dns/iijdpf/) |
| [Infoblox](https://go-acme.github.io/lego/dns/infoblox/) | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/) |
| [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Ionos](https://go-acme.github.io/lego/dns/ionos/) | [IPv64](https://go-acme.github.io/lego/dns/ipv64/) | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/) |
| [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Liara](https://go-acme.github.io/lego/dns/liara/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) |
| [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Loopia](https://go-acme.github.io/lego/dns/loopia/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Mail-in-a-Box](https://go-acme.github.io/lego/dns/mailinabox/) |
| [Manual](https://go-acme.github.io/lego/dns/manual/) | [Metaname](https://go-acme.github.io/lego/dns/metaname/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) |
| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [NearlyFreeSpeech.NET](https://go-acme.github.io/lego/dns/nearlyfreespeech/) |
| [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) |
| [Njalla](https://go-acme.github.io/lego/dns/njalla/) | [Nodion](https://go-acme.github.io/lego/dns/nodion/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) |
| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [plesk.com](https://go-acme.github.io/lego/dns/plesk/) | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) |
| [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [RcodeZero](https://go-acme.github.io/lego/dns/rcodezero/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) |
| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) |
| [Selectel v2](https://go-acme.github.io/lego/dns/selectelv2/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Shellrent](https://go-acme.github.io/lego/dns/shellrent/) |
| [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) |
| [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/) | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/) |
| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vercel](https://go-acme.github.io/lego/dns/vercel/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) |
| [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Webnames](https://go-acme.github.io/lego/dns/webnames/) |
| [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/) | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/) |
| [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | |
<!-- END DNS PROVIDERS LIST -->

View file

@ -16,7 +16,7 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
location := getLocation(resp)
if len(location) > 0 {
if location != "" {
a.core.jws.SetKid(location)
}

View file

@ -9,7 +9,7 @@ import (
"fmt"
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
jose "github.com/go-jose/go-jose/v3"
jose "github.com/go-jose/go-jose/v4"
)
// JWS Represents a JWS.

View file

@ -5,10 +5,10 @@ package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/4.14.1"
ourUserAgent = "xenolf-acme/4.17.3"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "release"
ourUserAgentComment = "detach"
)

View file

@ -13,6 +13,10 @@ import (
type OrderOptions struct {
NotBefore time.Time
NotAfter time.Time
// A string uniquely identifying a previously-issued certificate which this
// order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
ReplacesCertID string
}
type OrderService service
@ -45,6 +49,10 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm
if !opts.NotBefore.IsZero() {
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
}
if o.core.GetDirectory().RenewalInfo != "" {
orderReq.Replaces = opts.ReplacesCertID
}
}
var order acme.Order

View file

@ -11,7 +11,7 @@ import (
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -94,7 +94,6 @@ func TestOrderService_NewWithOptions(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -112,7 +111,8 @@ func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error)
return nil, err
}
jws, err := jose.ParseSigned(string(reqBody))
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
if err != nil {
return nil, err
}

View file

@ -3,8 +3,6 @@ package api
import (
"errors"
"net/http"
"github.com/go-acme/lego/v4/acme"
)
// ErrNoARI is returned when the server does not advertise a renewal info endpoint.
@ -28,26 +26,3 @@ func (c *CertificateService) GetRenewalInfo(certID string) (*http.Response, erro
return c.core.HTTPClient.Get(c.core.GetDirectory().RenewalInfo + "/" + certID)
}
// UpdateRenewalInfo POSTs updated renewal information for a certificate to the renewalInfo endpoint.
// This is used to indicate that a certificate has been replaced.
//
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
//
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
func (c *CertificateService) UpdateRenewalInfo(req acme.RenewalInfoUpdateRequest) (*http.Response, error) {
if c.core.GetDirectory().RenewalInfo == "" {
return nil, ErrNoARI
}
if req.CertID == "" {
return nil, errors.New("renewalInfo[post]: 'certID' cannot be empty")
}
if !req.Replaced {
return nil, errors.New("renewalInfo[post]: 'replaced' cannot be false")
}
return c.core.post(c.core.GetDirectory().RenewalInfo, req, nil)
}

View file

@ -44,7 +44,6 @@ func Test_getLink(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -181,6 +181,12 @@ type Order struct {
// certificate (optional, string):
// A URL for the certificate that has been issued in response to this order
Certificate string `json:"certificate,omitempty"`
// replaces (optional, string):
// replaces (string, optional): A string uniquely identifying a
// previously-issued certificate which this order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
Replaces string `json:"replaces,omitempty"`
}
// Authorization the ACME authorization object.
@ -329,9 +335,11 @@ type RenewalInfoResponse struct {
}
// RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint.
// - (4.2. Updating Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
// - (4.2. RenewalInfo Objects) https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
type RenewalInfoUpdateRequest struct {
// CertID is the base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960] with any trailing '=' characters stripped.
// CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the
// certificate's authority key identifier and Serial is the certificate's serial number. For details, see:
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1
CertID string `json:"certID"`
// Replaced is required and indicates whether or not the client considers the certificate to have been replaced.
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,

View file

@ -15,6 +15,7 @@ import (
"fmt"
"math/big"
"net"
"slices"
"strings"
"time"
@ -88,7 +89,7 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlockDER, _ := pem.Decode(key)
if keyBlockDER == nil {
return nil, fmt.Errorf("invalid PEM block")
return nil, errors.New("invalid PEM block")
}
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
@ -216,6 +217,26 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
return x509.ParseCertificate(pemBlock.Bytes)
}
func GetCertificateMainDomain(cert *x509.Certificate) (string, error) {
return getMainDomain(cert.Subject, cert.DNSNames)
}
func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) {
return getMainDomain(cert.Subject, cert.DNSNames)
}
func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) {
if subject.CommonName == "" && len(dnsNames) == 0 {
return "", errors.New("missing domain")
}
if subject.CommonName != "" {
return subject.CommonName, nil
}
return dnsNames[0], nil
}
func ExtractDomains(cert *x509.Certificate) []string {
var domains []string
if cert.Subject.CommonName != "" {
@ -248,7 +269,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
// loop over the SubjectAltName DNS names
for _, sanName := range csr.DNSNames {
if containsSAN(domains, sanName) {
if slices.Contains(domains, sanName) {
// Duplicate; skip this name
continue
}
@ -267,15 +288,6 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
return domains
}
func containsSAN(domains []string, sanName string) bool {
for _, existingName := range domains {
if existingName == sanName {
return true
}
}
return false
}
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
if err != nil {

View file

@ -39,14 +39,14 @@ func TestGenerateCSR(t *testing.T) {
expected expected
}{
{
desc: "without SAN",
desc: "without SAN (nil)",
privateKey: privateKey,
domain: "lego.acme",
mustStaple: true,
expected: expected{len: 245},
},
{
desc: "without SAN",
desc: "without SAN (empty)",
privateKey: privateKey,
domain: "lego.acme",
san: []string{},
@ -86,7 +86,6 @@ func TestGenerateCSR(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -12,6 +12,7 @@ const (
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
// From the documentation the limitation is 20 requests per second,
// but using 20 as value doesn't work but 18 do.
// https://letsencrypt.org/docs/rate-limits/
overallRequestLimit = 18
)
@ -37,7 +38,7 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
var responses []acme.Authorization
failures := newObtainError()
for i := 0; i < len(order.Authorizations); i++ {
for range len(order.Authorizations) {
select {
case res := <-resc:
responses = append(responses, res)

View file

@ -63,6 +63,10 @@ type ObtainRequest struct {
Bundle bool
PreferredChain string
AlwaysDeactivateAuthorizations bool
// A string uniquely identifying a previously-issued certificate which this
// order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
ReplacesCertID string
}
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
@ -79,6 +83,10 @@ type ObtainForCSRRequest struct {
Bundle bool
PreferredChain string
AlwaysDeactivateAuthorizations bool
// A string uniquely identifying a previously-issued certificate which this
// order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
ReplacesCertID string
}
type resolver interface {
@ -124,8 +132,9 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
}
orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
}
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -189,8 +198,9 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
}
orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
}
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -243,8 +253,10 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
}
}
// Determine certificate name(s) based on the authorization resources
commonName := domains[0]
commonName := ""
if len(domains[0]) <= 64 {
commonName = domains[0]
}
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
@ -252,7 +264,12 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
// object.
san := []string{commonName}
var san []string
if commonName != "" {
san = append(san, commonName)
}
for _, auth := range order.Identifiers {
if auth.Value != commonName {
san = append(san, auth.Value)
@ -274,9 +291,8 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
return nil, err
}
commonName := domains[0]
certRes := &Resource{
Domain: commonName,
Domain: domains[0],
CertURL: respOrder.Certificate,
PrivateKey: privateKeyPem,
}
@ -598,8 +614,13 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
return nil, err
}
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
if err != nil {
return nil, err
}
return &Resource{
Domain: x509Certs[0].Subject.CommonName,
Domain: domain,
Certificate: cert,
IssuerCertificate: issuer,
CertURL: url,

View file

@ -4,7 +4,6 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -28,7 +27,7 @@ func Test_obtainError_Join(t *testing.T) {
err := failures.Join()
to := &TomatoError{}
assert.ErrorAs(t, err, &to)
require.ErrorAs(t, err, &to)
}
func Test_obtainError_Join_multiple_domains(t *testing.T) {
@ -40,16 +39,16 @@ func Test_obtainError_Join_multiple_domains(t *testing.T) {
err := failures.Join()
to := &TomatoError{}
assert.ErrorAs(t, err, &to)
require.ErrorAs(t, err, &to)
ca := &CarrotError{}
assert.ErrorAs(t, err, &ca)
require.ErrorAs(t, err, &ca)
}
func Test_obtainError_Join_no_error(t *testing.T) {
failures := newObtainError()
assert.NoError(t, failures.Join())
require.NoError(t, failures.Join())
}
func Test_obtainError_Join_same_domain(t *testing.T) {
@ -66,5 +65,5 @@ func Test_obtainError_Join_same_domain(t *testing.T) {
}
ca := &CarrotError{}
assert.ErrorAs(t, err, &ca)
require.ErrorAs(t, err, &ca)
}

View file

@ -1,16 +1,13 @@
package certificate
import (
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/big"
"math/rand"
"strings"
"time"
"github.com/go-acme/lego/v4/acme"
@ -18,16 +15,18 @@ import (
// RenewalInfoRequest contains the necessary renewal information.
type RenewalInfoRequest struct {
Cert *x509.Certificate
Issuer *x509.Certificate
// HashName must be the string representation of a crypto.Hash constant in the golang.org/x/crypto package (e.g. "SHA-256").
// The correct value depends on the algorithm expected by the ACME server's ARI implementation.
HashName string
Cert *x509.Certificate
}
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
type RenewalInfoResponse struct {
acme.RenewalInfoResponse
// RetryAfter header indicating the polling interval that the ACME server recommends.
// Conforming clients SHOULD query the renewalInfo URL again after the RetryAfter period has passed,
// as the server may provide a different suggestedWindow.
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
RetryAfter time.Duration
}
// ShouldRenewAt determines the optimal renewal time based on the current time (UTC),renewal window suggest by ARI, and the client's willingness to sleep.
@ -72,7 +71,7 @@ func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.D
//
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) {
certID, err := makeCertID(req.Cert, req.Issuer, req.HashName)
certID, err := MakeARICertID(req.Cert)
if err != nil {
return nil, fmt.Errorf("error making certID: %w", err)
}
@ -88,117 +87,43 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse
if err != nil {
return nil, err
}
if retry := resp.Header.Get("Retry-After"); retry != "" {
info.RetryAfter, err = time.ParseDuration(retry + "s")
if err != nil {
return nil, err
}
}
return &info, nil
}
// UpdateRenewalInfo sends an update to the ACME server's renewal info endpoint to indicate that the client has successfully replaced a certificate.
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
// for instance because it has been renewed and the new certificate is in use, or because it is no longer in use.
//
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
//
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
func (c *Certifier) UpdateRenewalInfo(req RenewalInfoRequest) error {
certID, err := makeCertID(req.Cert, req.Issuer, req.HashName)
if err != nil {
return fmt.Errorf("error making certID: %w", err)
}
_, err = c.core.Certificates.UpdateRenewalInfo(acme.RenewalInfoUpdateRequest{
CertID: certID,
Replaced: true,
})
if err != nil {
return err
}
return nil
}
// makeCertID returns a base64url-encoded string that uniquely identifies a certificate to endpoints
// that implement the draft-ietf-acme-ari specification: https://datatracker.ietf.org/doc/draft-ietf-acme-ari.
// hashName must be the string representation of a crypto.Hash constant in the golang.org/x/crypto package.
// Supported hash functions are SHA-1, SHA-256, SHA-384, and SHA-512.
func makeCertID(leaf, issuer *x509.Certificate, hashName string) (string, error) {
// MakeARICertID constructs a certificate identifier as described in draft-ietf-acme-ari-03, section 4.1.
func MakeARICertID(leaf *x509.Certificate) (string, error) {
if leaf == nil {
return "", fmt.Errorf("leaf certificate is nil")
}
if issuer == nil {
return "", fmt.Errorf("issuer certificate is nil")
return "", errors.New("leaf certificate is nil")
}
var hashFunc crypto.Hash
var oid asn1.ObjectIdentifier
switch hashName {
// The following correlation of hashFunc to OID is copied from a private mapping in golang.org/x/crypto/ocsp:
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.8.0:ocsp/ocsp.go;l=156
case crypto.SHA1.String():
hashFunc = crypto.SHA1
oid = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
case crypto.SHA256.String():
hashFunc = crypto.SHA256
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
case crypto.SHA384.String():
hashFunc = crypto.SHA384
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2})
case crypto.SHA512.String():
hashFunc = crypto.SHA512
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3})
default:
return "", fmt.Errorf("hashName %q is not supported by this package", hashName)
}
if !hashFunc.Available() {
// This should never happen.
return "", fmt.Errorf("hash function %q is not available on your platform", hashFunc)
}
var spki struct {
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
_, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &spki)
if err != nil {
return "", err
}
h := hashFunc.New()
h.Write(spki.PublicKey.RightAlign())
issuerKeyHash := h.Sum(nil)
h.Reset()
h.Write(issuer.RawSubject)
issuerNameHash := h.Sum(nil)
type certID struct {
HashAlgorithm pkix.AlgorithmIdentifier
IssuerNameHash []byte
IssuerKeyHash []byte
SerialNumber *big.Int
}
// DER-encode the CertID ASN.1 sequence [RFC6960].
certIDBytes, err := asn1.Marshal(certID{
HashAlgorithm: pkix.AlgorithmIdentifier{
Algorithm: oid,
},
IssuerNameHash: issuerNameHash,
IssuerKeyHash: issuerKeyHash,
SerialNumber: leaf.SerialNumber,
})
// Marshal the Serial Number into DER.
der, err := asn1.Marshal(leaf.SerialNumber)
if err != nil {
return "", err
}
// base64url-encode [RFC4648] the bytes of the DER-encoded CertID ASN.1 sequence [RFC6960].
encodedBytes := base64.URLEncoding.EncodeToString(certIDBytes)
// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
// length, and value).
if len(der) < 3 {
return "", errors.New("invalid DER encoding of serial number")
}
// Any trailing '=' characters MUST be stripped.
return strings.TrimRight(encodedBytes, "="), nil
// Extract only the integer bytes from the DER encoded Serial Number
// Skipping the first 2 bytes (tag and length).
serial := base64.RawURLEncoding.EncodeToString(der[2:])
// Convert the Authority Key Identifier to base64url encoding without
// padding.
aki := base64.RawURLEncoding.EncodeToString(leaf.AuthorityKeyId)
// Construct the final identifier by concatenating AKI and Serial Number.
return fmt.Sprintf("%s.%s", aki, serial), nil
}

View file

@ -1,11 +1,8 @@
package certificate
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"io"
"net/http"
"testing"
"time"
@ -14,62 +11,28 @@ 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-jose/go-jose/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
ariLeafPEM = `-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgIIPqNFaGVEHxwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MB4XDTIyMDMxNzE3NTEwOVoXDTI0MDQx
NjE3NTEwOVowFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCgm9K/c+il2Pf0f8qhgxn9SKqXq88cOm9ov9AVRbPA
OWAAewqX2yUAwI4LZBGEgzGzTATkiXfoJ3cN3k39cH6tBbb3iSPuEn7OZpIk9D+e
3Q9/hX+N/jlWkaTB/FNA+7aE5IVWhmdczYilXa10V9r+RcvACJt0gsipBZVJ4jfJ
HnWJJGRZzzxqG/xkQmpXxZO7nOPFc8SxYKWdfcgp+rjR2ogYhSz7BfKoVakGPbpX
vZOuT9z4kkHra/WjwlkQhtHoTXdAxH3qC2UjMzO57Tx+otj0CxAv9O7CTJXISywB
vEVcmTSZkHS3eZtvvIwPx7I30ITRkYk/tLl1MbyB3SiZAgMBAAGjeDB2MA4GA1Ud
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTAWBgNVHREE
DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAx0aYvmCk7JYGNEXe
+hrOfKawkHYzWvA92cI/Oi6h+oSdHZ2UKzwFNf37cVKZ37FCrrv5pFP/xhhHvrNV
EnOx4IaF7OrnaTu5miZiUWuvRQP7ZGmGNFYbLTEF6/dj+WqyYdVaWzxRqHFu1ptC
TXysJCeyiGnR+KOOjOOQ9ZlO5JUK3OE4hagPLfaIpDDy6RXQt3ss0iNLuB1+IOtp
1URpvffLZQ8xPsEgOZyPWOcabTwJrtqBwily+lwPFn2mChUx846LwQfxtsXU/lJg
HX2RteNJx7YYNeX3Uf960mgo5an6vE8QNAsIoNHYrGyEmXDhTRe9mCHyiW2S7fZq
o9q12g==
MIIBQzCB66ADAgECAgUAh2VDITAKBggqhkjOPQQDAjAVMRMwEQYDVQQDEwpFeGFt
cGxlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBYxFDAS
BgNVBAMTC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeBZu
7cbpAYNXZLbbh8rNIzuOoqOOtmxA1v7cRm//AwyMwWxyHz4zfwmBhcSrf47NUAFf
qzLQ2PPQxdTXREYEnKMjMCEwHwYDVR0jBBgwFoAUaYhba4dGQEHhs3uEe6CuLN4B
yNQwCgYIKoZIzj0EAwIDRwAwRAIge09+S5TZAlw5tgtiVvuERV6cT4mfutXIlwTb
+FYN/8oCIClDsqBklhB9KAelFiYt9+6FDj3z4KGVelYM5MdsO3pK
-----END CERTIFICATE-----`
ariIssuerPEM = `-----BEGIN CERTIFICATE-----
MIIDSzCCAjOgAwIBAgIIOhNWtJ7Igr0wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MCAXDTIyMDMxNzE3NTEwOVoYDzIxMjIw
MzE3MTc1MTA5WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAzYTEzNTYwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDc3P6cxcCZ7FQOQrYuigReSa8T
IOPNKmlmX9OrTkPwjThiMNEETYKO1ea99yXPK36LUHC6OLmZ9jVQW2Ny1qwQCOy6
TrquhnwKgtkBMDAZBLySSEXYdKL3r0jA4sflW130/OLwhstU/yv0J8+pj7eSVOR3
zJBnYd1AqnXHRSwQm299KXgqema7uwsa8cgjrXsBzAhrwrvYlVhpWFSv3lQRDFQg
c5Z/ZDV9i26qiaJsCCmdisJZWN7N2luUgxdRqzZ4Cr2Xoilg3T+hkb2y/d6ttsPA
kaSA+pq3q6Qa7/qfGdT5WuUkcHpvKNRWqnwT9rCYlmG00r3hGgc42D/z1VvfAgMB
AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ4zzDRUaXHVKql
STWkULGU4zGZpTAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTANBgkq
hkiG9w0BAQsFAAOCAQEArbDHhEjGedjb/YjU80aFTPWOMRjgyfQaPPgyxwX6Dsid
1i2H1x4ud4ntz3sTZZxdQIrOqtlIWTWVCjpStwGxaC+38SdreiTTwy/nikXGa/6W
ZyQRppR3agh/pl5LHVO6GsJz3YHa7wQhEhj3xsRwa9VrRXgHbLGbPOFVRTHPjaPg
Gtsv2PN3f67DsPHF47ASqyOIRpLZPQmZIw6D3isJwfl+8CzvlB1veO0Q3uh08IJc
fspYQXvFBzYa64uKxNAJMi4Pby8cf4r36Wnb7cL4ho3fOHgAltxdW8jgibRzqZpQ
QKyxn2jX7kxeUDt0hFDJE8lOrhP73m66eBNzxe//FQ==
-----END CERTIFICATE-----`
ariLeafCertID = "MFswCwYJYIZIAWUDBAIBBCCeWLRusNLb--vmWOkxm34qDjTMWkc3utIhOMoMwKDqbgQg2iiKWySZrD-6c88HMZ6vhIHZPamChLlzGHeZ7pTS8jYCCD6jRWhlRB8c"
ariLeafCertID = "aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE"
)
func Test_makeCertID(t *testing.T) {
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
require.NoError(t, err)
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
require.NoError(t, err)
actual, err := makeCertID(leaf, issuer, crypto.SHA256.String())
actual, err := MakeARICertID(leaf)
require.NoError(t, err)
assert.Equal(t, ariLeafCertID, actual)
}
@ -77,8 +40,6 @@ func Test_makeCertID(t *testing.T) {
func TestCertifier_GetRenewalInfo(t *testing.T) {
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
require.NoError(t, err)
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
require.NoError(t, err)
// Test with a fake API.
mux, apiURL := tester.SetupFakeAPI(t)
@ -89,6 +50,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Retry-After", "21600")
w.WriteHeader(http.StatusOK)
_, wErr := w.Write([]byte(`{
"suggestedWindow": {
@ -109,19 +71,18 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()})
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/docs/renewal-advice/", ri.ExplanationURL)
assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter)
}
func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
require.NoError(t, err)
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err, "Could not generate test key")
@ -135,7 +96,7 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
{
desc: "API timeout",
httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms.
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
request: RenewalInfoRequest{leaf},
handler: func(w http.ResponseWriter, r *http.Request) {
// API that takes 2ms to respond.
time.Sleep(2 * time.Millisecond)
@ -144,24 +105,15 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
{
desc: "API error",
httpClient: http.DefaultClient,
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
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)
},
},
{
desc: "Issuer certificate is nil",
httpClient: http.DefaultClient,
request: RenewalInfoRequest{leaf, nil, crypto.SHA256.String()},
handler: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -180,105 +132,19 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
}
}
func TestCertifier_UpdateRenewalInfo(t *testing.T) {
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
require.NoError(t, err)
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err, "Could not generate test key")
// Test with a fake API.
mux, apiURL := tester.SetupFakeAPI(t)
mux.HandleFunc("/renewalInfo", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
body, rsbErr := readSignedBody(r, key)
if rsbErr != nil {
http.Error(w, rsbErr.Error(), http.StatusBadRequest)
return
}
var req acme.RenewalInfoUpdateRequest
err = json.Unmarshal(body, &req)
assert.NoError(t, err)
assert.True(t, req.Replaced)
assert.Equal(t, ariLeafCertID, req.CertID)
w.WriteHeader(http.StatusOK)
})
core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key)
require.NoError(t, err)
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
err = certifier.UpdateRenewalInfo(RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()})
require.NoError(t, err)
}
func TestCertifier_UpdateRenewalInfo_errors(t *testing.T) {
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
require.NoError(t, err)
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err, "Could not generate test key")
testCases := []struct {
desc string
request RenewalInfoRequest
}{
{
desc: "API error",
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
},
{
desc: "Certificate is nil",
request: RenewalInfoRequest{nil, issuer, crypto.SHA256.String()},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
mux, apiURL := tester.SetupFakeAPI(t)
// Always returns an error.
mux.HandleFunc("/renewalInfo", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
})
core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key)
require.NoError(t, err)
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
err = certifier.UpdateRenewalInfo(test.request)
require.Error(t, err)
})
}
}
func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
now := time.Now().UTC()
t.Run("Window is in the past", func(t *testing.T) {
ri := RenewalInfoResponse{
acme.RenewalInfoResponse{
RenewalInfoResponse: acme.RenewalInfoResponse{
SuggestedWindow: acme.Window{
Start: now.Add(-2 * time.Hour),
End: now.Add(-1 * time.Hour),
},
ExplanationURL: "",
},
RetryAfter: 0,
}
rt := ri.ShouldRenewAt(now, 0)
@ -288,13 +154,14 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
t.Run("Window is in the future", func(t *testing.T) {
ri := RenewalInfoResponse{
acme.RenewalInfoResponse{
RenewalInfoResponse: acme.RenewalInfoResponse{
SuggestedWindow: acme.Window{
Start: now.Add(1 * time.Hour),
End: now.Add(2 * time.Hour),
},
ExplanationURL: "",
},
RetryAfter: 0,
}
rt := ri.ShouldRenewAt(now, 0)
@ -303,13 +170,14 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
t.Run("Window is in the future, but caller is willing to sleep", func(t *testing.T) {
ri := RenewalInfoResponse{
acme.RenewalInfoResponse{
RenewalInfoResponse: acme.RenewalInfoResponse{
SuggestedWindow: acme.Window{
Start: now.Add(1 * time.Hour),
End: now.Add(2 * time.Hour),
},
ExplanationURL: "",
},
RetryAfter: 0,
}
rt := ri.ShouldRenewAt(now, 2*time.Hour)
@ -319,38 +187,17 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
t.Run("Window is in the future, but caller isn't willing to sleep long enough", func(t *testing.T) {
ri := RenewalInfoResponse{
acme.RenewalInfoResponse{
RenewalInfoResponse: acme.RenewalInfoResponse{
SuggestedWindow: acme.Window{
Start: now.Add(1 * time.Hour),
End: now.Add(2 * time.Hour),
},
ExplanationURL: "",
},
RetryAfter: 0,
}
rt := ri.ShouldRenewAt(now, 59*time.Minute)
assert.Nil(t, rt)
})
}
func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
jws, err := jose.ParseSigned(string(reqBody))
if err != nil {
return nil, err
}
body, err := jws.Verify(&jose.JSONWebKey{
Key: privateKey.Public(),
Algorithm: "RSA",
})
if err != nil {
return nil, err
}
return body, nil
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/acme"
@ -124,7 +125,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
}
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
log.Infof("[%s] acme: Checking DNS record propagation. [nameservers=%s]", domain, strings.Join(recursiveNameservers, ","))
time.Sleep(interval)
@ -214,7 +215,7 @@ func getChallengeFQDN(domain string, followCNAME bool) string {
}
// recursion counter so it doesn't spin out of control
for limit := 0; limit < 50; limit++ {
for range 50 {
// Keep following CNAMEs
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)

View file

@ -25,7 +25,7 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return err
return fmt.Errorf("manual: could not find zone: %w", err)
}
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
@ -33,8 +33,11 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
fmt.Printf("lego: Press 'Enter' when you are done\n")
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
if err != nil {
return fmt.Errorf("manual: %w", err)
}
return err
return nil
}
// CleanUp prints instructions for manually removing the TXT record.
@ -43,7 +46,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return err
return fmt.Errorf("manual: could not find zone: %w", err)
}
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)

View file

@ -5,7 +5,6 @@ import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -32,14 +31,14 @@ func TestDNSProviderManual(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
file, err := os.CreateTemp("", "lego_test")
assert.NoError(t, err)
require.NoError(t, err)
defer func() { _ = os.Remove(file.Name()) }()
_, err = file.WriteString(test.input)
assert.NoError(t, err)
require.NoError(t, err)
_, err = file.Seek(0, io.SeekStart)
assert.NoError(t, err)
require.NoError(t, err)
os.Stdin = file

View file

@ -47,7 +47,6 @@ func TestExtractSubDomain(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -93,7 +92,6 @@ func TestExtractSubDomain_errors(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -14,18 +14,17 @@ func TestToFqdn(t *testing.T) {
}{
{
desc: "simple",
domain: "foo.bar.com",
expected: "foo.bar.com.",
domain: "foo.example.com",
expected: "foo.example.com.",
},
{
desc: "already FQDN",
domain: "foo.bar.com.",
expected: "foo.bar.com.",
domain: "foo.example.com.",
expected: "foo.example.com.",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -43,18 +42,17 @@ func TestUnFqdn(t *testing.T) {
}{
{
desc: "simple",
fqdn: "foo.bar.com.",
expected: "foo.bar.com",
fqdn: "foo.example.",
expected: "foo.example",
},
{
desc: "already domain",
fqdn: "foo.bar.com",
expected: "foo.bar.com",
fqdn: "foo.example",
expected: "foo.example",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net"
"os"
"slices"
"strconv"
"strings"
"sync"
@ -98,12 +99,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
zone, err := FindZoneByFqdn(fqdn)
if err != nil {
return nil, fmt.Errorf("could not determine the zone: %w", err)
return nil, fmt.Errorf("could not find zone: %w", err)
}
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
if err != nil {
return nil, err
return nil, fmt.Errorf("NS call failed: %w", err)
}
for _, rr := range r.Answer {
@ -115,7 +116,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
if len(authoritativeNss) > 0 {
return authoritativeNss, nil
}
return nil, errors.New("could not determine authoritative nameservers")
return nil, fmt.Errorf("[zone=%s] could not determine authoritative nameservers", zone)
}
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
@ -129,7 +131,7 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", err
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
}
return soa.primaryNs, nil
}
@ -145,7 +147,7 @@ func FindZoneByFqdn(fqdn string) (string, error) {
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", err
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
}
return soa.zone, nil
}
@ -170,35 +172,35 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
var err error
var in *dns.Msg
var r *dns.Msg
labelIndexes := dns.Split(fqdn)
for _, index := range labelIndexes {
domain := fqdn[index:]
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
if err != nil {
continue
}
if in == nil {
if r == nil {
continue
}
switch in.Rcode {
switch r.Rcode {
case dns.RcodeSuccess:
// Check if we got a SOA RR in the answer section
if len(in.Answer) == 0 {
if len(r.Answer) == 0 {
continue
}
// CNAME records cannot/should not exist at the root of a zone.
// So we skip a domain when a CNAME is found.
if dnsMsgContainsCNAME(in) {
if dnsMsgContainsCNAME(r) {
continue
}
for _, ans := range in.Answer {
for _, ans := range r.Answer {
if soa, ok := ans.(*dns.SOA); ok {
return newSoaCacheEntry(soa), nil
}
@ -207,36 +209,46 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
// NXDOMAIN
default:
// Any response code other than NOERROR and NXDOMAIN is treated as error
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
return nil, &DNSError{Message: fmt.Sprintf("unexpected response for '%s'", domain), MsgOut: r}
}
}
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
return nil, &DNSError{Message: fmt.Sprintf("could not find the start of authority for '%s'", fqdn), MsgOut: r, Err: err}
}
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
for _, ans := range msg.Answer {
if _, ok := ans.(*dns.CNAME); ok {
return true
}
}
return false
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
_, ok := rr.(*dns.CNAME)
return ok
})
}
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
m := createDNSMsg(fqdn, rtype, recursive)
var in *dns.Msg
if len(nameservers) == 0 {
return nil, &DNSError{Message: "empty list of nameservers"}
}
var r *dns.Msg
var err error
var errAll error
for _, ns := range nameservers {
in, err = sendDNSQuery(m, ns)
if err == nil && len(in.Answer) > 0 {
r, err = sendDNSQuery(m, ns)
if err == nil && len(r.Answer) > 0 {
break
}
errAll = errors.Join(errAll, err)
}
return in, err
if err != nil {
return r, errAll
}
return r, nil
}
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
@ -254,37 +266,82 @@ 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}
in, _, err := tcp.Exchange(m, ns)
r, _, err := tcp.Exchange(m, ns)
if err != nil {
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
}
return in, err
return r, nil
}
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
in, _, err := udp.Exchange(m, ns)
r, _, err := udp.Exchange(m, ns)
if in != nil && in.Truncated {
if r != nil && r.Truncated {
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
// If the TCP request succeeds, the "err" will reset to nil
in, _, err = tcp.Exchange(m, ns)
}
return in, err
}
func formatDNSError(msg *dns.Msg, err error) string {
var parts []string
if msg != nil {
parts = append(parts, dns.RcodeToString[msg.Rcode])
r, _, err = tcp.Exchange(m, ns)
}
if err != nil {
parts = append(parts, err.Error())
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
}
if len(parts) > 0 {
return ": " + strings.Join(parts, " ")
}
return ""
return r, nil
}
// DNSError error related to DNS calls.
type DNSError struct {
Message string
NS string
MsgIn *dns.Msg
MsgOut *dns.Msg
Err error
}
func (d *DNSError) Error() string {
var details []string
if d.NS != "" {
details = append(details, "ns="+d.NS)
}
if d.MsgIn != nil && len(d.MsgIn.Question) > 0 {
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgIn.Question)))
}
if d.MsgOut != nil {
if d.MsgIn == nil || len(d.MsgIn.Question) == 0 {
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgOut.Question)))
}
details = append(details, "code="+dns.RcodeToString[d.MsgOut.Rcode])
}
msg := "DNS error"
if d.Message != "" {
msg = d.Message
}
if d.Err != nil {
msg += ": " + d.Err.Error()
}
if len(details) > 0 {
msg += " [" + strings.Join(details, ", ") + "]"
}
return msg
}
func (d *DNSError) Unwrap() error {
return d.Err
}
func formatQuestions(questions []dns.Question) string {
var parts []string
for _, question := range questions {
parts = append(parts, strings.ReplaceAll(strings.TrimPrefix(question.String(), ";"), "\t", " "))
}
return strings.Join(parts, ";")
}

View file

@ -1,9 +1,11 @@
package dns01
import (
"errors"
"sort"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -28,7 +30,6 @@ func TestLookupNameserversOK(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.fqdn, func(t *testing.T) {
t.Parallel()
@ -52,12 +53,11 @@ func TestLookupNameserversErr(t *testing.T) {
{
desc: "invalid tld",
fqdn: "_null.n0n0.",
error: "could not determine the zone",
error: "could not find zone",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -109,7 +109,7 @@ var findXByFqdnTestCases = []struct {
fqdn: "test.lego.zz.",
zone: "lego.zz.",
nameservers: []string{"8.8.8.8:53"},
expectedError: "could not find the start of authority for test.lego.zz.: NXDOMAIN",
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",
@ -119,18 +119,19 @@ var findXByFqdnTestCases = []struct {
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"},
expectedError: "could not find the start of authority for mail.google.com.: read udp",
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: "could not find the start of authority for test.ldez.com.",
expectedError: "[fqdn=test.ldez.com.] could not find the start of authority for 'test.ldez.com.': empty list of nameservers",
},
}
@ -142,7 +143,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) {
zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers)
if test.expectedError != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), test.expectedError)
assert.ErrorContains(t, err, test.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, test.zone, zone)
@ -159,7 +160,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) {
ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers)
if test.expectedError != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), test.expectedError)
assert.ErrorContains(t, err, test.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, test.primaryNs, ns)
@ -197,3 +198,69 @@ func TestResolveConfServers(t *testing.T) {
})
}
}
func TestDNSError_Error(t *testing.T) {
msgIn := createDNSMsg("example.com.", dns.TypeTXT, true)
msgOut := createDNSMsg("example.org.", dns.TypeSOA, true)
msgOut.Rcode = dns.RcodeNameError
testCases := []struct {
desc string
err *DNSError
expected string
}{
{
desc: "empty error",
err: &DNSError{},
expected: "DNS error",
},
{
desc: "all fields",
err: &DNSError{
Message: "Oops",
NS: "example.com.",
MsgIn: msgIn,
MsgOut: msgOut,
Err: errors.New("I did it again"),
},
expected: "Oops: I did it again [ns=example.com., question='example.com. IN TXT', code=NXDOMAIN]",
},
{
desc: "only NS",
err: &DNSError{
NS: "example.com.",
},
expected: "DNS error [ns=example.com.]",
},
{
desc: "only MsgIn",
err: &DNSError{
MsgIn: msgIn,
},
expected: "DNS error [question='example.com. IN TXT']",
},
{
desc: "only MsgOut",
err: &DNSError{
MsgOut: msgOut,
},
expected: "DNS error [question='example.org. IN SOA', code=NXDOMAIN]",
},
{
desc: "only Err",
err: &DNSError{
Err: errors.New("I did it again"),
},
expected: "DNS error: I did it again",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
assert.EqualError(t, test.err, test.expected)
})
}
}

View file

@ -28,7 +28,6 @@ func TestCheckDNSPropagation(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()
@ -69,7 +68,6 @@ func TestCheckAuthoritativeNss(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()
@ -104,7 +102,6 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()

View file

@ -69,7 +69,6 @@ func TestParseForwardedHeader(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()

View file

@ -57,7 +57,6 @@ func TestProviderServer_GetAddress(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -99,7 +99,6 @@ func TestProber_Solve(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -12,7 +12,7 @@ import (
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -158,7 +158,8 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error {
return err
}
jws, err := jose.ParseSigned(string(reqBody))
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
if err != nil {
return err
}

View file

@ -24,7 +24,7 @@ func TestChallenge(t *testing.T) {
_, apiURL := tester.SetupFakeAPI(t)
domain := "localhost"
port := "23457"
port := "24457"
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
conn, err := tls.Dial("tcp", net.JoinHostPort(domain, port), &tls.Config{
@ -75,7 +75,7 @@ func TestChallenge(t *testing.T) {
solver := NewChallenge(
core,
mockValidate,
&ProviderServer{port: "23457"},
&ProviderServer{port: port},
)
authz := acme.Authorization{
@ -126,7 +126,7 @@ func TestChallengeIPaddress(t *testing.T) {
_, apiURL := tester.SetupFakeAPI(t)
domain := "127.0.0.1"
port := "23457"
port := "24457"
rd, _ := dns.ReverseAddr(domain)
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
@ -141,7 +141,7 @@ func TestChallengeIPaddress(t *testing.T) {
assert.Len(t, connState.PeerCertificates, 1, "Expected the challenge server to return exactly one certificate")
remoteCert := connState.PeerCertificates[0]
assert.Len(t, remoteCert.DNSNames, 0, "Expected the challenge certificate to have no DNSNames entry in context of challenge for IP")
assert.Empty(t, remoteCert.DNSNames, "Expected the challenge certificate to have no DNSNames entry in context of challenge for IP")
assert.Len(t, remoteCert.IPAddresses, 1, "Expected the challenge certificate to have exactly one IPAddresses entry")
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")
@ -176,7 +176,7 @@ func TestChallengeIPaddress(t *testing.T) {
solver := NewChallenge(
core,
mockValidate,
&ProviderServer{port: "23457"},
&ProviderServer{port: port},
)
authz := acme.Authorization{

View file

@ -3,10 +3,10 @@ package cmd
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
@ -55,17 +55,27 @@ type CertificatesStorage struct {
pem bool
pfx bool
pfxPassword string
pfxFormat string
filename string // Deprecated
}
// NewCertificatesStorage create a new certificates storage.
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
pfxFormat := ctx.String("pfx.format")
switch pfxFormat {
case "DES", "RC2", "SHA256":
default:
log.Fatalf("Invalid PFX format: %s", pfxFormat)
}
return &CertificatesStorage{
rootPath: filepath.Join(ctx.String("path"), baseCertificatesFolderName),
archivePath: filepath.Join(ctx.String("path"), baseArchivesFolderName),
pem: ctx.Bool("pem"),
pfx: ctx.Bool("pfx"),
pfxPassword: ctx.String("pfx.pass"),
pfxFormat: pfxFormat,
filename: ctx.String("filename"),
}
}
@ -218,14 +228,9 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
return fmt.Errorf("unable to load Certificate for domain %s: %w", domain, err)
}
issuerCertPemBlock, _ := pem.Decode(certRes.IssuerCertificate)
if issuerCertPemBlock == nil {
return fmt.Errorf("unable to parse Issuer Certificate for domain %s", domain)
}
issuerCert, err := x509.ParseCertificate(issuerCertPemBlock.Bytes)
certChain, err := getCertificateChain(certRes)
if err != nil {
return fmt.Errorf("unable to load Issuer Certificate for domain %s: %w", domain, err)
return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err)
}
keyPemBlock, _ := pem.Decode(certRes.PrivateKey)
@ -251,7 +256,12 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain)
}
pfxBytes, err := pkcs12.Encode(rand.Reader, privateKey, cert, []*x509.Certificate{issuerCert}, s.pfxPassword)
encoder, err := getPFXEncoder(s.pfxFormat)
if err != nil {
return fmt.Errorf("PFX encoder: %w", err)
}
pfxBytes, err := encoder.Encode(privateKey, cert, certChain, s.pfxPassword)
if err != nil {
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
}
@ -285,6 +295,42 @@ func (s *CertificatesStorage) MoveToArchive(domain string) error {
return nil
}
func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, error) {
chainCertPemBlock, rest := pem.Decode(certRes.IssuerCertificate)
if chainCertPemBlock == nil {
return nil, errors.New("unable to parse Issuer Certificate")
}
var certChain []*x509.Certificate
for chainCertPemBlock != nil {
chainCert, err := x509.ParseCertificate(chainCertPemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to parse Chain Certificate: %w", err)
}
certChain = append(certChain, chainCert)
chainCertPemBlock, rest = pem.Decode(rest) // Try decoding the next pem block
}
return certChain, nil
}
func getPFXEncoder(pfxFormat string) (*pkcs12.Encoder, error) {
var encoder *pkcs12.Encoder
switch pfxFormat {
case "SHA256":
encoder = pkcs12.Modern2023
case "DES":
encoder = pkcs12.LegacyDES
case "RC2":
encoder = pkcs12.LegacyRC2
default:
return nil, fmt.Errorf("invalid PFX format: %s", pfxFormat)
}
return encoder, nil
}
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
func sanitizedDomain(domain string) string {
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))

View file

@ -84,10 +84,15 @@ func listCertificates(ctx *cli.Context) error {
return err
}
name, err := certcrypto.GetCertificateMainDomain(pCert)
if err != nil {
return err
}
if names {
fmt.Println(pCert.Subject.CommonName)
fmt.Println(name)
} else {
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
fmt.Println(" Certificate Name:", name)
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
fmt.Println(" Expiry Date:", pCert.NotAfter)
fmt.Println(" Certificate Path:", filename)

View file

@ -18,12 +18,13 @@ import (
)
const (
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
renewEnvCertPath = "LEGO_CERT_PATH"
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
renewEnvCertPath = "LEGO_CERT_PATH"
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
renewEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH"
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
)
func createRenew() *cli.Command {
@ -34,7 +35,7 @@ func createRenew() *cli.Command {
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice("domains")) > 0
hasCsr := len(ctx.String("csr")) > 0
hasCsr := ctx.String("csr") != ""
if hasDomains && hasCsr {
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
}
@ -53,11 +54,6 @@ func createRenew() *cli.Command {
Name: "ari-enable",
Usage: "Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed.",
},
&cli.StringFlag{
Name: "ari-hash-name",
Value: crypto.SHA256.String(),
Usage: "The string representation of the hash expected by the renewalInfo endpoint (e.g. \"SHA-256\").",
},
&cli.DurationFlag{
Name: "ari-wait-to-renew-duration",
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
@ -146,11 +142,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
var ariRenewalTime *time.Time
if ctx.Bool("ari-enable") {
if len(certificates) < 2 {
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
} else {
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
}
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
// Figure out if we need to sleep before renewing.
@ -207,6 +199,13 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
if ctx.Bool("ari-enable") {
request.ReplacesCertID, err = certificate.MakeARICertID(cert)
if err != nil {
log.Fatalf("Error while construction the ARI CertID for domain %s\n\t%v", domain, err)
}
}
certRes, err := client.Certificate.Obtain(request)
if err != nil {
log.Fatal(err)
@ -214,23 +213,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
certsStorage.SaveResource(certRes)
if ariRenewalTime != nil {
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
Cert: certificates[0],
Issuer: certificates[1],
HashName: ctx.String("ari-hash-name"),
})
if err != nil {
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
}
}
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, ".pem")
meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, ".pfx")
addPathToMetadata(meta, domain, certRes, certsStorage)
return launchHook(ctx.String("renew-hook"), meta)
}
@ -241,7 +224,10 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
log.Fatal(err)
}
domain := csr.Subject.CommonName
domain, err := certcrypto.GetCSRMainDomain(csr)
if err != nil {
log.Fatalf("Error: %v", err)
}
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
@ -255,11 +241,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
var ariRenewalTime *time.Time
if ctx.Bool("ari-enable") {
if len(certificates) < 2 {
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
} else {
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
}
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
// Figure out if we need to sleep before renewing.
@ -287,6 +269,13 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
if ctx.Bool("ari-enable") {
request.ReplacesCertID, err = certificate.MakeARICertID(cert)
if err != nil {
log.Fatalf("Error while construction the ARI CertID for domain %s\n\t%v", domain, err)
}
}
certRes, err := client.Certificate.ObtainForCSR(request)
if err != nil {
log.Fatal(err)
@ -294,21 +283,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
certsStorage.SaveResource(certRes)
if ariRenewalTime != nil {
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
Cert: certificates[0],
Issuer: certificates[1],
HashName: ctx.String("ari-hash-name"),
})
if err != nil {
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
}
}
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
addPathToMetadata(meta, domain, certRes, certsStorage)
return launchHook(ctx.String("renew-hook"), meta)
}
@ -331,23 +306,19 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
}
// getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint.
func getARIRenewalTime(ctx *cli.Context, cert, issuer *x509.Certificate, domain string, client *lego.Client) *time.Time {
func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
if cert.IsCA {
log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain)
}
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{
Cert: cert,
Issuer: issuer,
HashName: ctx.String("ari-hash-name"),
})
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{Cert: cert})
if err != nil {
if errors.Is(err, api.ErrNoARI) {
// The server does not advertise a renewal info endpoint.
log.Warnf("[%s] acme: %w", domain, err)
log.Warnf("[%s] acme: %v", domain, err)
return nil
}
log.Warnf("[%s] acme: calling renewal info endpoint: %w", domain, err)
log.Warnf("[%s] acme: calling renewal info endpoint: %v", domain, err)
return nil
}
@ -366,6 +337,24 @@ func getARIRenewalTime(ctx *cli.Context, cert, issuer *x509.Certificate, domain
return renewalTime
}
func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) {
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, certExt)
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, keyExt)
if certRes.IssuerCertificate != nil {
meta[renewEnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, issuerExt)
}
if certsStorage.pem {
meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, pemExt)
}
if certsStorage.pfx {
meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, pfxExt)
}
}
func merge(prevDomains, nextDomains []string) []string {
for _, next := range nextDomains {
var found bool

View file

@ -48,7 +48,6 @@ func Test_merge(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -108,7 +107,6 @@ func Test_needRenewal(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
actual := needRenewal(test.x509Cert, "foo.com", test.days)

View file

@ -21,7 +21,7 @@ func createRun() *cli.Command {
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice("domains")) > 0
hasCsr := len(ctx.String("csr")) > 0
hasCsr := ctx.String("csr") != ""
if hasDomains && hasCsr {
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
}

View file

@ -18,6 +18,7 @@ func CreateFlags(defaultPath string) []cli.Flag {
&cli.StringFlag{
Name: "server",
Aliases: []string{"s"},
EnvVars: []string{"LEGO_SERVER"},
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
Value: lego.LEDirectoryProduction,
},
@ -132,13 +133,21 @@ func CreateFlags(defaultPath string) []cli.Flag {
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
},
&cli.BoolFlag{
Name: "pfx",
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
Name: "pfx",
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
EnvVars: []string{"LEGO_PFX"},
},
&cli.StringFlag{
Name: "pfx.pass",
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
Value: pkcs12.DefaultPassword,
Name: "pfx.pass",
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
Value: pkcs12.DefaultPassword,
EnvVars: []string{"LEGO_PFX_PASSWORD"},
},
&cli.StringFlag{
Name: "pfx.format",
Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.",
Value: "RC2",
EnvVars: []string{"LEGO_PFX_FORMAT"},
},
&cli.IntFlag{
Name: "cert.timeout",

View file

@ -35,6 +35,7 @@ func allDNSCodes() string {
"cloudxns",
"conoha",
"constellix",
"cpanel",
"derak",
"desec",
"designate",
@ -66,6 +67,7 @@ func allDNSCodes() string {
"hetzner",
"hostingde",
"hosttech",
"httpnet",
"httpreq",
"hurricane",
"hyperone",
@ -86,6 +88,7 @@ func allDNSCodes() string {
"liquidweb",
"loopia",
"luadns",
"mailinabox",
"metaname",
"mydnsjp",
"mythicbeasts",
@ -116,7 +119,9 @@ func allDNSCodes() string {
"sakuracloud",
"scaleway",
"selectel",
"selectelv2",
"servercow",
"shellrent",
"simply",
"sonic",
"stackpath",
@ -131,6 +136,7 @@ func allDNSCodes() string {
"vkcloud",
"vscale",
"vultr",
"webnames",
"websupport",
"wedos",
"yandex",
@ -306,18 +312,22 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "AZURE_CLIENT_CERTIFICATE_PATH": Client certificate path`)
ew.writeln(` - "AZURE_CLIENT_ID": Client ID`)
ew.writeln(` - "AZURE_CLIENT_SECRET": Client secret`)
ew.writeln(` - "AZURE_RESOURCE_GROUP": DNS zone resource group`)
ew.writeln(` - "AZURE_SUBSCRIPTION_ID": DNS zone subscription ID`)
ew.writeln(` - "AZURE_TENANT_ID": Tenant ID`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "AZURE_AUTH_METHOD": Specify which authentication method to use`)
ew.writeln(` - "AZURE_AUTH_MSI_TIMEOUT": Managed Identity timeout duration`)
ew.writeln(` - "AZURE_ENVIRONMENT": Azure environment, one of: public, usgovernment, and china`)
ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "AZURE_PRIVATE_ZONE": Set to true to use Azure Private DNS Zones and not public`)
ew.writeln(` - "AZURE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "AZURE_RESOURCE_GROUP": DNS zone resource group`)
ew.writeln(` - "AZURE_SERVICEDISCOVERY_FILTER": Advanced ServiceDiscovery filter using Kusto query condition`)
ew.writeln(` - "AZURE_SUBSCRIPTION_ID": DNS zone subscription ID`)
ew.writeln(` - "AZURE_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "AZURE_ZONE_NAME": Zone name to use inside Azure DNS service to add the TXT record in`)
@ -606,6 +616,30 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/constellix`)
case "cpanel":
// generated from: providers/dns/cpanel/cpanel.toml
ew.writeln(`Configuration for CPanel/WHM.`)
ew.writeln(`Code: 'cpanel'`)
ew.writeln(`Since: 'v4.16.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "CPANEL_BASE_URL": API server URL`)
ew.writeln(` - "CPANEL_TOKEN": API token`)
ew.writeln(` - "CPANEL_USERNAME": username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "CPANEL_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "CPANEL_MODE": use cpanel API or WHM API (Default: cpanel)`)
ew.writeln(` - "CPANEL_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "CPANEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "CPANEL_REGION": The region`)
ew.writeln(` - "CPANEL_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cpanel`)
case "derak":
// generated from: providers/dns/derak/derak.toml
ew.writeln(`Configuration for Derak Cloud.`)
@ -960,6 +994,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "EFFICIENTIP_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "EFFICIENTIP_INSECURE_SKIP_VERIFY": Whether or not to verify EfficientIP API certificate`)
ew.writeln(` - "EFFICIENTIP_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "EFFICIENTIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "EFFICIENTIP_TTL": The TTL of the TXT record used for the DNS challenge`)
@ -1070,7 +1105,8 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "GANDIV5_API_KEY": API key`)
ew.writeln(` - "GANDIV5_API_KEY": API key (Deprecated)`)
ew.writeln(` - "GANDIV5_PERSONAL_ACCESS_TOKEN": Personal Access Token`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
@ -1101,6 +1137,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "GCE_ZONE_ID": Allows to skip the automatic detection of the zone`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)
@ -1248,6 +1285,27 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hosttech`)
case "httpnet":
// generated from: providers/dns/httpnet/httpnet.toml
ew.writeln(`Configuration for http.net.`)
ew.writeln(`Code: 'httpnet'`)
ew.writeln(`Since: 'v4.15.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "HTTPNET_API_KEY": API key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "HTTPNET_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "HTTPNET_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "HTTPNET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "HTTPNET_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "HTTPNET_ZONE_NAME": Zone name in ACE format`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/httpnet`)
case "httpreq":
// generated from: providers/dns/httpreq/httpreq.toml
ew.writeln(`Configuration for HTTP request.`)
@ -1490,7 +1548,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "IPV64_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "IPV64_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "IPV64_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "IPV64_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
@ -1610,17 +1667,17 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "LIQUID_WEB_PASSWORD": Storm API Password`)
ew.writeln(` - "LIQUID_WEB_USERNAME": Storm API Username`)
ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`)
ew.writeln(` - "LWAPI_PASSWORD": Liquid Web API Password`)
ew.writeln(` - "LWAPI_USERNAME": Liquid Web API Username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "LIQUID_WEB_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
ew.writeln(` - "LIQUID_WEB_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "LIQUID_WEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "LIQUID_WEB_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "LIQUID_WEB_URL": Storm API endpoint`)
ew.writeln(` - "LWAPI_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
ew.writeln(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "LWAPI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "LWAPI_URL": Liquid Web API endpoint`)
ew.writeln(` - "LWAPI_ZONE": DNS Zone`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`)
@ -1668,6 +1725,26 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/luadns`)
case "mailinabox":
// generated from: providers/dns/mailinabox/mailinabox.toml
ew.writeln(`Configuration for Mail-in-a-Box.`)
ew.writeln(`Code: 'mailinabox'`)
ew.writeln(`Since: 'v4.16.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "MAILINABOX_BASE_URL": Base API URL (ex: https://box.example.com)`)
ew.writeln(` - "MAILINABOX_EMAIL": User email`)
ew.writeln(` - "MAILINABOX_PASSWORD": User password`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/mailinabox`)
case "metaname":
// generated from: providers/dns/metaname/metaname.toml
ew.writeln(`Configuration for Metaname.`)
@ -2008,6 +2085,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
@ -2021,9 +2099,11 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "OVH_APPLICATION_KEY": Application key`)
ew.writeln(` - "OVH_APPLICATION_SECRET": Application secret`)
ew.writeln(` - "OVH_CONSUMER_KEY": Consumer key`)
ew.writeln(` - "OVH_APPLICATION_KEY": Application key (Application Key authentication)`)
ew.writeln(` - "OVH_APPLICATION_SECRET": Application secret (Application Key authentication)`)
ew.writeln(` - "OVH_CLIENT_ID": Client ID (OAuth2)`)
ew.writeln(` - "OVH_CLIENT_SECRET": Client secret (OAuth2)`)
ew.writeln(` - "OVH_CONSUMER_KEY": Consumer key (Application Key authentication)`)
ew.writeln(` - "OVH_ENDPOINT": Endpoint URL (ovh-eu or ovh-ca)`)
ew.writeln()
@ -2049,6 +2129,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "PDNS_API_VERSION": Skip API version autodetection and use the provided version number.`)
ew.writeln(` - "PDNS_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "PDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
@ -2158,6 +2239,8 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "REGRU_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "REGRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "REGRU_TLS_CERT": authentication certificate`)
ew.writeln(` - "REGRU_TLS_KEY": authentication private key`)
ew.writeln(` - "REGRU_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
@ -2223,6 +2306,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "AWS_REGION": Managed by the AWS client ('AWS_REGION_FILE' is not supported)`)
ew.writeln(` - "AWS_SDK_LOAD_CONFIG": Managed by the AWS client. Retrieve the region from the CLI config file ('AWS_SDK_LOAD_CONFIG_FILE' is not supported)`)
ew.writeln(` - "AWS_SECRET_ACCESS_KEY": Managed by the AWS client. Secret access key ('AWS_SECRET_ACCESS_KEY_FILE' is not supported, use 'AWS_SHARED_CREDENTIALS_FILE' instead)`)
ew.writeln(` - "AWS_WAIT_FOR_RECORD_SETS_CHANGED": Wait for changes to be INSYNC (it can be unstable)`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
@ -2284,14 +2368,15 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "SCALEWAY_API_TOKEN": API token`)
ew.writeln(` - "SCALEWAY_PROJECT_ID": Project to use (optional)`)
ew.writeln(` - "SCW_PROJECT_ID": Project to use (optional)`)
ew.writeln(` - "SCW_SECRET_KEY": Secret key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SCALEWAY_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SCALEWAY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SCALEWAY_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "SCW_ACCESS_KEY": Access key`)
ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/scaleway`)
@ -2317,6 +2402,30 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectel`)
case "selectelv2":
// generated from: providers/dns/selectelv2/selectelv2.toml
ew.writeln(`Configuration for Selectel v2.`)
ew.writeln(`Code: 'selectelv2'`)
ew.writeln(`Since: 'v4.17.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "SELECTELV2_ACCOUNT_ID": Selectel account ID (INT)`)
ew.writeln(` - "SELECTELV2_PASSWORD": Openstack username's password`)
ew.writeln(` - "SELECTELV2_PROJECT_ID": Cloud project ID (UUID)`)
ew.writeln(` - "SELECTELV2_USERNAME": Openstack username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SELECTELV2_BASE_URL": API endpoint URL`)
ew.writeln(` - "SELECTELV2_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "SELECTELV2_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SELECTELV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SELECTELV2_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectelv2`)
case "servercow":
// generated from: providers/dns/servercow/servercow.toml
ew.writeln(`Configuration for Servercow.`)
@ -2338,6 +2447,27 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/servercow`)
case "shellrent":
// generated from: providers/dns/shellrent/shellrent.toml
ew.writeln(`Configuration for Shellrent.`)
ew.writeln(`Code: 'shellrent'`)
ew.writeln(`Since: 'v4.16.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "SHELLRENT_TOKEN": Token`)
ew.writeln(` - "SHELLRENT_USERNAME": Username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SHELLRENT_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "SHELLRENT_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SHELLRENT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SHELLRENT_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/shellrent`)
case "simply":
// generated from: providers/dns/simply/simply.toml
ew.writeln(`Configuration for Simply.com.`)
@ -2638,6 +2768,26 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`)
case "webnames":
// generated from: providers/dns/webnames/webnames.toml
ew.writeln(`Configuration for Webnames.`)
ew.writeln(`Code: 'webnames'`)
ew.writeln(`Since: 'v4.15.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "WEBNAMES_API_KEY": Domain API key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "WEBNAMES_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`)
case "websupport":
// generated from: providers/dns/websupport/websupport.toml
ew.writeln(`Configuration for Websupport.`)

View file

@ -1,6 +1,6 @@
.PHONY: default clean hugo hugo-build
default: hugo
default: clean hugo
clean:
rm -rf public/

View file

@ -70,7 +70,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## More information
- [API documentation](https://www.alibabacloud.com/help/doc-detail/42875.htm)
- [API documentation](https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records)
- [Go client](https://github.com/aliyun/alibaba-cloud-sdk-go)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -48,15 +48,12 @@ lego --domains example.com --email your_example@email.com --dns azuredns run
### Using Managed Identity (Azure VM)
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_SUBSCRIPTION_ID=<your target zone subscription ID> \
AZURE_RESOURCE_GROUP=<your target zone resource group name> \
lego --domains example.com --email your_example@email.com --dns azuredns run
### Using Managed Identity (Azure Arc)
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_SUBSCRIPTION_ID=<your target zone subscription ID> \
AZURE_RESOURCE_GROUP=<your target zone resource group name> \
IMDS_ENDPOINT=http://localhost:40342 \
IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \
lego --domains example.com --email your_example@email.com --dns azuredns run
@ -70,10 +67,9 @@ lego --domains example.com --email your_example@email.com --dns azuredns run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `AZURE_CLIENT_CERTIFICATE_PATH` | Client certificate path |
| `AZURE_CLIENT_ID` | Client ID |
| `AZURE_CLIENT_SECRET` | Client secret |
| `AZURE_RESOURCE_GROUP` | DNS zone resource group |
| `AZURE_SUBSCRIPTION_ID` | DNS zone subscription ID |
| `AZURE_TENANT_ID` | Tenant ID |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
@ -84,10 +80,15 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `AZURE_AUTH_METHOD` | Specify which authentication method to use |
| `AZURE_AUTH_MSI_TIMEOUT` | Managed Identity timeout duration |
| `AZURE_ENVIRONMENT` | Azure environment, one of: public, usgovernment, and china |
| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check |
| `AZURE_PRIVATE_ZONE` | Set to true to use Azure Private DNS Zones and not public |
| `AZURE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `AZURE_RESOURCE_GROUP` | DNS zone resource group |
| `AZURE_SERVICEDISCOVERY_FILTER` | Advanced ServiceDiscovery filter using Kusto query condition |
| `AZURE_SUBSCRIPTION_ID` | DNS zone subscription ID |
| `AZURE_TTL` | The TTL of the TXT record used for the DNS challenge |
| `AZURE_ZONE_NAME` | Zone name to use inside Azure DNS service to add the TXT record in |
@ -96,19 +97,75 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Description
Azure Credentials are automatically detected in the following locations and prioritized in the following order:
Several authentication methods can be used to authenticate against Azure DNS API.
### Default Azure Credentials (default option)
Default Azure Credentials automatically detects in the following locations and prioritized in the following order:
1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`
2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH`
3. Workload identity for resources hosted in Azure environment (see below)
4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI
4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI
Link:
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
### Environment variables
#### Service Discovery
Lego automatically finds all visible Azure (private) DNS zones using [Azure ResourceGraph query](https://learn.microsoft.com/en-us/azure/governance/resource-graph/).
This can be limited by specifying environment variable `AZURE_SUBSCRIPTION_ID` and/or `AZURE_RESOURCE_GROUP` which limits the
DNS zones to only a subscription or to one resourceGroup.
Additionally environment variable `AZURE_SERVICEDISCOVERY_FILTER` can be used to filter DNS zones with an addition Kusto filter eg:
```
resources
| where type =~ "microsoft.network/dnszones"
| ${AZURE_SERVICEDISCOVERY_FILTER}
| project subscriptionId, resourceGroup, name
```
#### Client secret
The Azure Credentials can be configured using the following environment variables:
* AZURE_CLIENT_ID = "Client ID"
* AZURE_CLIENT_SECRET = "Client secret"
* AZURE_TENANT_ID = "Tenant ID"
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
#### Client certificate
The Azure Credentials can be configured using the following environment variables:
* AZURE_CLIENT_ID = "Client ID"
* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
* AZURE_TENANT_ID = "Tenant ID"
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
### Workload identity
#### Azure Managed Identity
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
Here is a summary of the steps to follow to use it :
* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`.
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
* create a federated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account.
Link :
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`.
### Azure Managed Identity
#### Azure Managed Identity (with Azure workload)
The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets.
@ -138,6 +195,11 @@ az role assignment create \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}/TXT/${AZ_RECORD_SET}"
```
A timeout wrapper is configured for this authentication method.
The duration can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
The default timeout is 2 seconds.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
#### Azure Managed Identity (with Azure Arc)
The Azure Arc agent provides the ability to use a Managed Identity on resources hosted outside of Azure
@ -146,22 +208,26 @@ The Azure Arc agent provides the ability to use a Managed Identity on resources
While the upstream `azidentity` SDK will try to automatically identify and use the Azure Arc metadata service,
if you get `azuredns: DefaultAzureCredential: failed to acquire a token.` error messages,
you may need to set the environment variables:
* `IMDS_ENDPOINT=http://localhost:40342`
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
* `IMDS_ENDPOINT=http://localhost:40342`
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
#### Workload identity for AKS
A timeout wrapper is configured for this authentication method.
The duration can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
The default timeout is 2 seconds.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
### Azure CLI
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
The Azure CLI is a command-line tool provided by Microsoft to interact with Azure resources.
It provides an easy way to authenticate by simply running `az login` command.
The generated token will be cached by default in the `~/.azure` folder.
Here is a summary of the steps to follow to use it :
* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`.
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`.
Link :
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
### Open ID Connect
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`.

View file

@ -0,0 +1,83 @@
---
title: "CPanel/WHM"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: cpanel
dnsprovider:
since: "v4.16.0"
code: "cpanel"
url: "https://cpanel.net/"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/cpanel/cpanel.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [CPanel/WHM](https://cpanel.net/).
<!--more-->
- Code: `cpanel`
- Since: v4.16.0
Here is an example bash command using the CPanel/WHM provider:
```bash
### CPANEL (default)
CPANEL_USERNAME = "yyyy"
CPANEL_TOKEN = "xxxx"
CPANEL_BASE_URL = "https://example.com:2083" \
lego --email you@example.com --dns cpanel --domains my.example.org run
## WHM
CPANEL_MODE = whm
CPANEL_USERNAME = "yyyy"
CPANEL_TOKEN = "xxxx"
CPANEL_BASE_URL = "https://example.com:2087" \
lego --email you@example.com --dns cpanel --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `CPANEL_BASE_URL` | API server URL |
| `CPANEL_TOKEN` | API token |
| `CPANEL_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 |
|--------------------------------|-------------|
| `CPANEL_HTTP_TIMEOUT` | API request timeout |
| `CPANEL_MODE` | use cpanel API or WHM API (Default: cpanel) |
| `CPANEL_POLLING_INTERVAL` | Time between DNS propagation check |
| `CPANEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `CPANEL_REGION` | The region |
| `CPANEL_TTL` | The TTL of the TXT record used for the DNS challenge |
The 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
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/cpanel/cpanel.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -98,6 +98,10 @@ For more information, you can read about the different methods of authentication
- [Keystone username/password](https://docs.openstack.org/keystone/latest/user/supported_clients.html)
- [Keystone application credentials](https://docs.openstack.org/keystone/latest/user/application_credentials.html)
Public cloud providers with support for Designate:
- [Fuga Cloud](https://fuga.cloud/)
## More information

View file

@ -61,7 +61,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## More information
- [API documentation](https://www.do.de/wiki/LetsEncrypt_-_Entwickler)
- [API documentation](https://www.do.de/wiki/freie-ssl-tls-zertifikate-ueber-acme/)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/dode/dode.toml -->

View file

@ -54,6 +54,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `EFFICIENTIP_HTTP_TIMEOUT` | API request timeout |
| `EFFICIENTIP_INSECURE_SKIP_VERIFY` | Whether or not to verify EfficientIP API certificate |
| `EFFICIENTIP_POLLING_INTERVAL` | Time between DNS propagation check |
| `EFFICIENTIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `EFFICIENTIP_TTL` | The TTL of the TXT record used for the DNS challenge |

View file

@ -26,7 +26,7 @@ Configuration for [Gandi Live DNS (v5)](https://www.gandi.net).
Here is an example bash command using the Gandi Live DNS (v5) provider:
```bash
GANDIV5_API_KEY=abcdefghijklmnopqrstuvwx \
GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \
lego --email you@example.com --dns gandiv5 --domains my.example.org run
```
@ -37,7 +37,8 @@ lego --email you@example.com --dns gandiv5 --domains my.example.org run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `GANDIV5_API_KEY` | API key |
| `GANDIV5_API_KEY` | API key (Deprecated) |
| `GANDIV5_PERSONAL_ACCESS_TOKEN` | Personal Access 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" >}}).

View file

@ -58,6 +58,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `GCE_POLLING_INTERVAL` | Time between DNS propagation check |
| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge |
| `GCE_ZONE_ID` | Allows to skip the automatic detection of the zone |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -57,6 +57,12 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
GoDaddy has recently (2024-04) updated the account requirements to access parts of their production Domains API:
- Availability API: Limited to accounts with 50 or more domains.
- Management and DNS APIs: Limited to accounts with 10 or more domains and/or an active Discount Domain Club plan.
https://community.letsencrypt.org/t/getting-unauthorized-url-error-while-trying-to-get-cert-for-subdomains/217329/12

View file

@ -0,0 +1,68 @@
---
title: "http.net"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: httpnet
dnsprovider:
since: "v4.15.0"
code: "httpnet"
url: "https://www.http.net/"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/httpnet/httpnet.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [http.net](https://www.http.net/).
<!--more-->
- Code: `httpnet`
- Since: v4.15.0
Here is an example bash command using the http.net provider:
```bash
HTTPNET_API_KEY=xxxxxxxx \
lego --email you@example.com --dns httpnet --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `HTTPNET_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 |
|--------------------------------|-------------|
| `HTTPNET_HTTP_TIMEOUT` | API request timeout |
| `HTTPNET_POLLING_INTERVAL` | Time between DNS propagation check |
| `HTTPNET_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `HTTPNET_TTL` | The TTL of the TXT record used for the DNS challenge |
| `HTTPNET_ZONE_NAME` | Zone name in ACE format |
The 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.http.net/docs/api/#dns)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/httpnet/httpnet.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -50,7 +50,6 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `IPV64_HTTP_TIMEOUT` | API request timeout |
| `IPV64_POLLING_INTERVAL` | Time between DNS propagation check |
| `IPV64_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `IPV64_SEQUENCE_INTERVAL` | Time between sequential requests |
| `IPV64_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.

View file

@ -26,9 +26,8 @@ Configuration for [Liquid Web](https://liquidweb.com).
Here is an example bash command using the Liquid Web provider:
```bash
LIQUID_WEB_USERNAME=someuser \
LIQUID_WEB_PASSWORD=somepass \
LIQUID_WEB_ZONE=tacoman.com.net \
LWAPI_USERNAME=someuser \
LWAPI_PASSWORD=somepass \
lego --email you@example.com --dns liquidweb --domains my.example.org run
```
@ -39,9 +38,8 @@ lego --email you@example.com --dns liquidweb --domains my.example.org run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `LIQUID_WEB_PASSWORD` | Storm API Password |
| `LIQUID_WEB_USERNAME` | Storm API Username |
| `LIQUID_WEB_ZONE` | DNS Zone |
| `LWAPI_PASSWORD` | Liquid Web API Password |
| `LWAPI_USERNAME` | Liquid Web 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" >}}).
@ -51,11 +49,12 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `LIQUID_WEB_HTTP_TIMEOUT` | Maximum waiting time for the DNS records to be created (not verified) |
| `LIQUID_WEB_POLLING_INTERVAL` | Time between DNS propagation check |
| `LIQUID_WEB_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `LIQUID_WEB_TTL` | The TTL of the TXT record used for the DNS challenge |
| `LIQUID_WEB_URL` | Storm API endpoint |
| `LWAPI_HTTP_TIMEOUT` | Maximum waiting time for the DNS records to be created (not verified) |
| `LWAPI_POLLING_INTERVAL` | Time between DNS propagation check |
| `LWAPI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `LWAPI_TTL` | The TTL of the TXT record used for the DNS challenge |
| `LWAPI_URL` | Liquid Web API endpoint |
| `LWAPI_ZONE` | DNS Zone |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
@ -65,7 +64,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## More information
- [API documentation](https://cart.liquidweb.com/storm/api/docs/v1/)
- [API documentation](https://api.liquidweb.com/docs/)
- [Go client](https://github.com/liquidweb/liquidweb-go)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -0,0 +1,69 @@
---
title: "Mail-in-a-Box"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: mailinabox
dnsprovider:
since: "v4.16.0"
code: "mailinabox"
url: "https://mailinabox.email"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/mailinabox/mailinabox.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [Mail-in-a-Box](https://mailinabox.email).
<!--more-->
- Code: `mailinabox`
- Since: v4.16.0
Here is an example bash command using the Mail-in-a-Box provider:
```bash
MAILINABOX_EMAIL=user@example.com \
MAILINABOX_PASSWORD=yyyy \
MAILINABOX_BASE_URL=https://box.example.com \
lego --email you@example.com --dns mailinabox --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `MAILINABOX_BASE_URL` | Base API URL (ex: https://box.example.com) |
| `MAILINABOX_EMAIL` | User email |
| `MAILINABOX_PASSWORD` | User password |
The 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 |
|--------------------------------|-------------|
| `MAILINABOX_POLLING_INTERVAL` | Time between DNS propagation check |
| `MAILINABOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
The 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://mailinabox.email/api-docs.html)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/mailinabox/mailinabox.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -51,6 +51,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `OTC_HTTP_TIMEOUT` | API request timeout |
| `OTC_POLLING_INTERVAL` | Time between DNS propagation check |
| `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `OTC_SEQUENCE_INTERVAL` | Time between sequential requests |
| `OTC_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.

View file

@ -26,11 +26,20 @@ Configuration for [OVH](https://www.ovh.com/).
Here is an example bash command using the OVH provider:
```bash
# Application Key authentication:
OVH_APPLICATION_KEY=1234567898765432 \
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
OVH_CONSUMER_KEY=256vfsd347245sdfg \
OVH_ENDPOINT=ovh-eu \
lego --email you@example.com --dns ovh --domains my.example.org run
# Or OAuth2:
OVH_CLIENT_ID=yyy \
OVH_CLIENT_SECRET=xxx \
OVH_ENDPOINT=ovh-eu \
lego --email you@example.com --dns ovh --domains my.example.org run
```
@ -40,9 +49,11 @@ lego --email you@example.com --dns ovh --domains my.example.org run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `OVH_APPLICATION_KEY` | Application key |
| `OVH_APPLICATION_SECRET` | Application secret |
| `OVH_CONSUMER_KEY` | Consumer key |
| `OVH_APPLICATION_KEY` | Application key (Application Key authentication) |
| `OVH_APPLICATION_SECRET` | Application secret (Application Key authentication) |
| `OVH_CLIENT_ID` | Client ID (OAuth2) |
| `OVH_CLIENT_SECRET` | Client secret (OAuth2) |
| `OVH_CONSUMER_KEY` | Consumer key (Application Key authentication) |
| `OVH_ENDPOINT` | Endpoint URL (ovh-eu or ovh-ca) |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
@ -82,6 +93,22 @@ When requesting the consumer key, the following configuration can be used to def
}
```
## OAuth2 Client Credentials
Another method for authentication is by using OAuth2 client credentials.
An IAM policy and service account can be created by following the [OVH guide](https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343).
Following IAM policies need to be authorized for the affected domain:
* dnsZone:apiovh:record/create
* dnsZone:apiovh:record/delete
* dnsZone:apiovh:refresh
## Important Note
Both authentication methods cannot be used at the same time.
## More information

View file

@ -49,6 +49,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `PDNS_API_VERSION` | Skip API version autodetection and use the provided version number. |
| `PDNS_HTTP_TIMEOUT` | API request timeout |
| `PDNS_POLLING_INTERVAL` | Time between DNS propagation check |
| `PDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
@ -65,6 +66,7 @@ Tested and confirmed to work with PowerDNS authoritative server 3.4.8 and 4.0.1.
PowerDNS Notes:
- PowerDNS API does not currently support SSL, therefore you should take care to ensure that traffic between lego and the PowerDNS API is over a trusted network, VPN etc.
- In order to have the SOA serial automatically increment each time the `_acme-challenge` record is added/modified via the API, set `SOA-EDIT-API` to `INCEPTION-INCREMENT` for the zone in the `domainmetadata` table
- Some PowerDNS servers doesn't have root API endpoints enabled and API version autodetection will not work. In that case version number can be defined using `PDNS_API_VERSION`.

View file

@ -52,6 +52,8 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `REGRU_HTTP_TIMEOUT` | API request timeout |
| `REGRU_POLLING_INTERVAL` | Time between DNS propagation check |
| `REGRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `REGRU_TLS_CERT` | authentication certificate |
| `REGRU_TLS_KEY` | authentication private key |
| `REGRU_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.

View file

@ -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 \
--domains example.com --email your_example@email.com --dns route53 --accept-tos=true run
lego --domains example.com --email your_example@email.com --dns route53 --accept-tos=true run
```
@ -48,6 +48,7 @@ AWS_HOSTED_ZONE_ID=your_hosted_zone_id \
| `AWS_REGION` | Managed by the AWS client (`AWS_REGION_FILE` is not supported) |
| `AWS_SDK_LOAD_CONFIG` | Managed by the AWS client. Retrieve the region from the CLI config file (`AWS_SDK_LOAD_CONFIG_FILE` is not supported) |
| `AWS_SECRET_ACCESS_KEY` | Managed by the AWS client. Secret access key (`AWS_SECRET_ACCESS_KEY_FILE` is not supported, use `AWS_SHARED_CREDENTIALS_FILE` instead) |
| `AWS_WAIT_FOR_RECORD_SETS_CHANGED` | Wait for changes to be INSYNC (it can be unstable) |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -26,7 +26,7 @@ Configuration for [Scaleway](https://developers.scaleway.com/).
Here is an example bash command using the Scaleway provider:
```bash
SCALEWAY_API_TOKEN=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \
SCW_SECRET_KEY=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \
lego --email you@example.com --dns scaleway --domains my.example.org run
```
@ -37,8 +37,8 @@ lego --email you@example.com --dns scaleway --domains my.example.org run
| Environment Variable Name | Description |
|-----------------------|-------------|
| `SCALEWAY_API_TOKEN` | API token |
| `SCALEWAY_PROJECT_ID` | Project to use (optional) |
| `SCW_PROJECT_ID` | Project to use (optional) |
| `SCW_SECRET_KEY` | Secret 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" >}}).
@ -48,9 +48,10 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `SCALEWAY_POLLING_INTERVAL` | Time between DNS propagation check |
| `SCALEWAY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SCALEWAY_TTL` | The TTL of the TXT record used for the DNS challenge |
| `SCW_ACCESS_KEY` | Access key |
| `SCW_POLLING_INTERVAL` | Time between DNS propagation check |
| `SCW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SCW_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -0,0 +1,75 @@
---
title: "Selectel v2"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: selectelv2
dnsprovider:
since: "v4.17.0"
code: "selectelv2"
url: "https://selectel.ru"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/selectelv2/selectelv2.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [Selectel v2](https://selectel.ru).
<!--more-->
- Code: `selectelv2`
- Since: v4.17.0
Here is an example bash command using the Selectel v2 provider:
```bash
SELECTEL_USERNAME=trex \
SELECTEL_PASSWORD=xxxxx \
SELECTEL_ACCOUNT_ID=1234567 \
SELECTEL_PROJECT_ID=111a11111aaa11aa1a11aaa11111aa1a \
lego --email you@example.com --dns selectelv2 --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `SELECTELV2_ACCOUNT_ID` | Selectel account ID (INT) |
| `SELECTELV2_PASSWORD` | Openstack username's password |
| `SELECTELV2_PROJECT_ID` | Cloud project ID (UUID) |
| `SELECTELV2_USERNAME` | Openstack 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 |
|--------------------------------|-------------|
| `SELECTELV2_BASE_URL` | API endpoint URL |
| `SELECTELV2_HTTP_TIMEOUT` | API request timeout |
| `SELECTELV2_POLLING_INTERVAL` | Time between DNS propagation check |
| `SELECTELV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SELECTELV2_TTL` | The TTL of the TXT record used for the DNS challenge |
The 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.selectel.ru/docs/cloud-services/dns_api/dns_api_actual/)
- [Go client](https://github.com/selectel/domains-go)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/selectelv2/selectelv2.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -0,0 +1,69 @@
---
title: "Shellrent"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: shellrent
dnsprovider:
since: "v4.16.0"
code: "shellrent"
url: "https://www.shellrent.com/"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/shellrent/shellrent.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [Shellrent](https://www.shellrent.com/).
<!--more-->
- Code: `shellrent`
- Since: v4.16.0
Here is an example bash command using the Shellrent provider:
```bash
SHELLRENT_USERNAME=xxxx \
SHELLRENT_TOKEN=yyyy \
lego --email you@example.com --dns shellrent --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `SHELLRENT_TOKEN` | Token |
| `SHELLRENT_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 |
|--------------------------------|-------------|
| `SHELLRENT_HTTP_TIMEOUT` | API request timeout |
| `SHELLRENT_POLLING_INTERVAL` | Time between DNS propagation check |
| `SHELLRENT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `SHELLRENT_TTL` | The TTL of the TXT record used for the DNS challenge |
The 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.shellrent.com/section/api2)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/shellrent/shellrent.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -0,0 +1,72 @@
---
title: "Webnames"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: webnames
dnsprovider:
since: "v4.15.0"
code: "webnames"
url: "https://www.webnames.ru/"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/webnames/webnames.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [Webnames](https://www.webnames.ru/).
<!--more-->
- Code: `webnames`
- Since: v4.15.0
Here is an example bash command using the Webnames provider:
```bash
WEBNAMES_API_KEY=xxxxxx \
lego --email you@example.com --dns webnames --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `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" >}}).
## Additional Configuration
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `WEBNAMES_HTTP_TIMEOUT` | API request timeout |
| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check |
| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `WEBNAMES_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## API Key
To obtain the key, you need to change the DNS server to `*.nameself.com`: Personal account / My domains and services / Select the required domain / DNS servers
The API key can be found: Personal account / My domains and services / Select the required domain / Zone management / acme.sh or certbot settings
## More information
- [API documentation](https://github.com/regtime-ltd/certbot-dns-webnames)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/webnames/webnames.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -30,13 +30,27 @@ docker run goacme/lego -h
yay -S lego-bin
```
- [Snap](https://snapcraft.io/lego) (official):
```bash
sudo snap install lego
```
- [FreeBSD (Ports)](https://www.freshports.org/security/lego) (unofficial):
```bash
cd /usr/ports/security/lego && make install clean
```
[Homebrew](https://brew.sh/) user can install [lego](https://formulae.brew.sh/formula/lego) with:
- [Gentoo](https://gitweb.gentoo.org/repo/proj/guru.git/tree/app-crypt/lego) (unofficial):
You can [enable GURU](https://wiki.gentoo.org/wiki/Project:GURU/Information_for_End_Users) repository and then:
```bash
emerge app-crypt/lego
```
- [Homebrew](https://formulae.brew.sh/formula/lego) (unofficial):
```bash
brew install lego
@ -52,7 +66,7 @@ docker run goacme/lego -h
Requirements:
- go1.17+
- go1.22+.
- environment variable: `GO111MODULE=on`
To install the latest version from sources, just run:

View file

@ -104,6 +104,8 @@ Some information is provided through environment variables:
- `LEGO_CERT_DOMAIN`: the main domain of the certificate.
- `LEGO_CERT_PATH`: the path of the certificate.
- `LEGO_CERT_KEY_PATH`: the path of the certificate key.
- `LEGO_CERT_PEM_PATH`: (only with `--pem`) the path to the PEM certificate.
- `LEGO_CERT_PFX_PATH`: (only with `--pfx`) the path to the PFX certificate.
### Use case

View file

@ -61,6 +61,8 @@ Some information is provided through environment variables:
- `LEGO_CERT_DOMAIN`: the main domain of the certificate.
- `LEGO_CERT_PATH`: the path of the certificate.
- `LEGO_CERT_KEY_PATH`: the path of the certificate key.
- `LEGO_CERT_PEM_PATH`: (only with `--pem`) the path to the PEM certificate.
- `LEGO_CERT_PFX_PATH`: (only with `--pfx`) the path to the PFX certificate.
See [Obtain a Certificate → Use case]({{< ref "usage/cli/Obtain-a-Certificate#use-case" >}}) for an example script.

View file

@ -8,7 +8,7 @@ NAME:
lego - Let's Encrypt client written in Go
USAGE:
lego [global options] command [command options] [arguments...]
lego [global options] command [command options]
COMMANDS:
run Register an account, then create and install a certificate
@ -20,7 +20,7 @@ COMMANDS:
GLOBAL OPTIONS:
--domains value, -d value [ --domains value, -d value ] Add a domain to the process. Can be specified multiple times.
--server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory")
--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.
--csr value, -c value Certificate signing request filename, if an external CSR is to be used.
@ -44,8 +44,9 @@ GLOBAL OPTIONS:
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together. (default: false)
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. (default: false)
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit")
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. (default: false) [$LEGO_PFX]
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
--pfx.format value The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--user-agent value Add to the user-agent sent to the CA to identify an application embedding lego-cli
--help, -h show help
@ -81,9 +82,8 @@ USAGE:
lego renew [command options] [arguments...]
OPTIONS:
--days value The number of days left on a certificate to renew it. (default: 0)
--days value The number of days left on a certificate to renew it. (default: 30)
--ari-enable Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed. (default: false)
--ari-hash-name value The string representation of the hash expected by the renewalInfo endpoint (e.g. "SHA-256").
--ari-wait-to-renew-duration value The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
@ -137,7 +137,7 @@ To display the documentation for a specific DNS provider, run:
$ lego dnshelp -c code
Supported DNS providers:
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, derak, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, metaname, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, constellix, cpanel, derak, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hosttech, httpnet, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, mailinabox, manual, metaname, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rcodezero, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, servercow, shellrent, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, webnames, websupport, wedos, yandex, yandex360, yandexcloud, zoneee, zonomi
More information: https://go-acme.github.io/lego/dns
"""

206
go.mod
View file

@ -1,168 +1,190 @@
module github.com/go-acme/lego/v4
go 1.20
go 1.22
// github.com/exoscale/egoscale v1.19.0 => It is an error, please don't use it.
require (
cloud.google.com/go/compute/metadata v0.2.3
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0
github.com/Azure/go-autorest/autorest v0.11.24
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/BurntSushi/toml v1.3.2
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755
github.com/aws/aws-sdk-go-v2 v1.19.0
github.com/aws/aws-sdk-go-v2/config v1.18.28
github.com/aws/aws-sdk-go-v2/credentials v1.13.27
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3
github.com/cenkalti/backoff/v4 v4.2.1
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/aws/aws-sdk-go-v2/service/lightsail v1.37.0
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6
github.com/cenkalti/backoff/v4 v4.3.0
github.com/civo/civogo v0.3.11
github.com/cloudflare/cloudflare-go v0.70.0
github.com/cloudflare/cloudflare-go v0.93.0
github.com/cpu/goacmedns v0.1.1
github.com/dnsimple/dnsimple-go v1.2.0
github.com/exoscale/egoscale v0.100.1
github.com/go-jose/go-jose/v3 v3.0.0
github.com/dnsimple/dnsimple-go v1.7.0
github.com/exoscale/egoscale v0.102.3
github.com/go-jose/go-jose/v4 v4.0.1
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1
github.com/google/go-querystring v1.1.0
github.com/gophercloud/gophercloud v1.0.0
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae
github.com/hashicorp/go-retryablehttp v0.7.4
github.com/gophercloud/gophercloud v1.11.0
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56
github.com/hashicorp/go-retryablehttp v0.7.5
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df
github.com/infobloxopen/infoblox-go-client v1.1.1
github.com/labbsr0x/bindman-dns-webhook v1.0.2
github.com/linode/linodego v1.17.2
github.com/liquidweb/liquidweb-go v1.6.3
github.com/mattn/go-isatty v0.0.19
github.com/miekg/dns v1.1.55
github.com/linode/linodego v1.28.0
github.com/liquidweb/liquidweb-go v1.6.4
github.com/mattn/go-isatty v0.0.20
github.com/miekg/dns v1.1.58
github.com/mimuret/golang-iij-dpf v0.9.1
github.com/mitchellh/mapstructure v1.5.0
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
github.com/nrdcg/auroradns v1.1.0
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9
github.com/nrdcg/desec v0.7.0
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3
github.com/nrdcg/desec v0.8.0
github.com/nrdcg/dnspod-go v0.4.0
github.com/nrdcg/freemyip v0.2.0
github.com/nrdcg/goinwx v0.8.2
github.com/nrdcg/goinwx v0.10.0
github.com/nrdcg/mailinabox v0.2.0
github.com/nrdcg/namesilo v0.2.1
github.com/nrdcg/nodion v0.1.0
github.com/nrdcg/porkbun v0.2.0
github.com/nrdcg/porkbun v0.3.0
github.com/nzdjb/go-metaname v1.0.0
github.com/oracle/oci-go-sdk v24.3.0+incompatible
github.com/ovh/go-ovh v1.4.2
github.com/oracle/oci-go-sdk/v65 v65.63.1
github.com/ovh/go-ovh v1.5.1
github.com/pquerna/otp v1.4.0
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2
github.com/sacloud/api-client-go v0.2.8
github.com/sacloud/iaas-api-go v1.11.1
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
github.com/softlayer/softlayer-go v1.1.2
github.com/stretchr/testify v1.8.4
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490
github.com/transip/gotransip/v6 v6.20.0
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c
github.com/urfave/cli/v2 v2.25.7
github.com/sacloud/api-client-go v0.2.10
github.com/sacloud/iaas-api-go v1.12.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.25
github.com/selectel/domains-go v1.0.2
github.com/selectel/go-selvpcclient/v3 v3.1.1
github.com/softlayer/softlayer-go v1.1.3
github.com/stretchr/testify v1.9.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898
github.com/transip/gotransip/v6 v6.23.0
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a
github.com/urfave/cli/v2 v2.27.1
github.com/vinyldns/go-vinyldns v0.9.16
github.com/vultr/govultr/v2 v2.17.2
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997
golang.org/x/crypto v0.10.0
golang.org/x/net v0.11.0
golang.org/x/oauth2 v0.9.0
golang.org/x/time v0.3.0
google.golang.org/api v0.111.0
gopkg.in/ns1/ns1-go.v2 v2.7.6
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.19.0
golang.org/x/time v0.5.0
google.golang.org/api v0.172.0
gopkg.in/ns1/ns1-go.v2 v2.9.1
gopkg.in/yaml.v2 v2.4.0
software.sslmate.com/src/go-pkcs12 v0.2.0
software.sslmate.com/src/go-pkcs12 v0.4.0
)
require (
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute v1.24.0 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // 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.0.0 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-resty/resty/v2 v2.11.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect
github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // 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/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sacloud/go-http v0.1.6 // indirect
github.com/sacloud/packages-go v0.0.9 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/assertions v1.0.1 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/ratelimit v0.3.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.20.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.63.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

497
go.sum
View file

@ -5,15 +5,13 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY=
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -21,99 +19,108 @@ 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.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
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/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 h1:9Eih8XcEeQnFD0ntMlUDleKMzfeCeUfa+VbnDCI4AZs=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0/go.mod h1:wGPyTi+aURdqPAGMZDQqnNs9IrShADF8w2WZb6bKeq0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
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/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 h1:lM7JnA9dEdDFH9XOgRNQMDTQnOjlLkDTNA7c0aWTQ30=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 h1:cZG7psLfqpkB6H+fIrgUDWmlzM474St1LP0jcz272yI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27/go.mod h1:ZdjYvJpDlefgh8/hWelJhqgqJeodxu4SmbVsSdBlL7E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 h1:Bje8Xkh2OWpjBdNfXLrnn8eZg569dUQmhgtydxAYyP0=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30/go.mod h1:qQtIBl5OVMfmeQkz8HaVyh5DzFmmFXyvK27UgIgOr4c=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 h1:hx4WksB0NRQ9utR+2c3gEGzl6uKj3eM6PMQ6tN3lgXs=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4/go.mod h1:JniVpqvw90sVjNqanGLufrVapWySL28fhBlYgl96Q/w=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0=
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k=
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 h1:PalLOEGZ/4XfQxpGZFTLaoJSmPoybnqJYotaIZEf/Rg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0/go.mod h1:PwyKKVL0cNkC37QwLcrhyeCrAk+5bY8O2ou7USyAS2A=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA=
github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.37.0 h1:lfVUMJEGXzi5l8jam/WXLNSn+vM/fpe2dmMYOdRiQ+k=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.37.0/go.mod h1:GSVUed6FJivX6v7Pgrk9iXuRa2NuCtT+nMWGxQHSAXQ=
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4 h1:ZZKiHm4cN8IDDZ2kh8DTk+YnYBjVsiFdwf5FwVs//IQ=
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.4/go.mod h1:RTfjFUctf+Zyq8e4rgLXmz43+0kIoIXbENvrFtilumI=
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
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=
@ -123,8 +130,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -134,8 +141,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM=
github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.70.0 h1:4opGbUygM8DjirUuaz23jn3akuAcnOCEx+0nQtQEcFo=
github.com/cloudflare/cloudflare-go v0.70.0/go.mod h1:VW6GuazkaZ4xEDkFt24lkXQUsE8q7BiGqDniC2s8WEM=
github.com/cloudflare/cloudflare-go v0.93.0 h1:rV0eHb42NUewfK5qa2+LKAD4v4oFA0QGDABn/lMyF78=
github.com/cloudflare/cloudflare-go v0.93.0/go.mod h1:N1u1cLZ4lG6NeezGOWi7P6aq1DK2iVYg9ze7GZbUmZE=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@ -164,8 +171,9 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -173,17 +181,21 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exoscale/egoscale v0.100.1 h1:iXsV1Ei7daqe/6FYSCSDyrFs1iUG1l1X9qNh2uMw6z0=
github.com/exoscale/egoscale v0.100.1/go.mod h1:BAb9p4rmyU+Wl400CJZO5270H2sXtdsZjLcm5xMKkz4=
github.com/exoscale/egoscale v0.102.3 h1:DYqN2ipoLKpiFoprRGQkp2av/Ze7sUYYlGhi1N62tfY=
github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0VQas/UEGU5c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -194,11 +206,16 @@ github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW
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-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/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-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@ -208,13 +225,21 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
@ -224,10 +249,14 @@ github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
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=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -244,8 +273,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -256,10 +286,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-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=
@ -270,21 +301,24 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
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.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
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.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae h1:Hi3IgB9RQDE15Kfovd8MTZrcana+UlQqNbOif8dLpA0=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM=
github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE=
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@ -304,19 +338,21 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
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.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=
@ -333,14 +369,13 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -349,6 +384,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@ -383,15 +419,13 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/linode/linodego v1.17.2 h1:b32dj4662PGG5P9qVa6nBezccWdqgukndlMIuPGq1CQ=
github.com/linode/linodego v1.17.2/go.mod h1:C2iyT3Vg2O2sPxkWka4XAQ5WSUtm5LmTZ3Adw43Ra7Q=
github.com/linode/linodego v1.28.0 h1:lzxxJebsYg5cCWRNDLyL2StW3sfMyAwf/FYfxFjFrlk=
github.com/linode/linodego v1.28.0/go.mod h1:5oAsx+uinHtVo6U77nXXXtox7MWzUW6aEkTOKXxA9uo=
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA=
github.com/liquidweb/go-lwApi v0.0.5/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=
github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4=
github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc=
github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -403,22 +437,24 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -448,22 +484,24 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU=
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA=
github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
github.com/nrdcg/desec v0.8.0 h1:FJbRWUAluTCUi9nHFnhqPhLSIHiNnB9elZVWYgFtIqA=
github.com/nrdcg/desec v0.8.0/go.mod h1:BsnYPtSlBttJL3Gyzv0kDH7zkk60obwThlnqiiKzn+o=
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8=
github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4=
github.com/nrdcg/goinwx v0.8.2 h1:RmjiHlEA+lzi3toXyPSaE6hWnBQ0+G+1u7w8C6Fpp4g=
github.com/nrdcg/goinwx v0.8.2/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
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/porkbun v0.2.0 h1:ghaqPtIKcffba99epWFkK3VWf6TKJT9WMXMgaTqv95Y=
github.com/nrdcg/porkbun v0.2.0/go.mod h1:i0uLMn9ItFsLsSQIAeEu1wQ9/+6EvX1eQw15hulMMRw=
github.com/nrdcg/porkbun v0.3.0 h1:jnRV7j2zd3hmh+tSDOGetJyy3+WklaMxbs7HtTTmWMs=
github.com/nrdcg/porkbun v0.3.0/go.mod h1:jh1DKz96jGHW+NCdG3AmTbbnQeBlNUz1KeSgeN/cBVw=
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=
@ -482,17 +520,19 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/ovh/go-ovh v1.4.2 h1:ub4jVK6ERbiBTo4y5wbLCjeKCjGY+K36e7BviW+MaAU=
github.com/ovh/go-ovh v1.4.2/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/oracle/oci-go-sdk/v65 v65.63.1 h1:dYL7sk9L1+C9LCmoq+zjPMNteuJJfk54YExq/4pV9xQ=
github.com/oracle/oci-go-sdk/v65 v65.63.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/ovh/go-ovh v1.5.1 h1:P8O+7H+NQuFK9P/j4sFW5C0fvSS2DnHYGPwdVCp45wI=
github.com/ovh/go-ovh v1.5.1/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/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/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -529,26 +569,33 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
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.2.8 h1:tIY6PZNBX900K66TqEPa4d6UIbedUczfCBnPJkzi8kw=
github.com/sacloud/api-client-go v0.2.8/go.mod h1:0CV/kWNYlS1hCNdnk6Wx7Wdg8DPFCnv0zOIzdXjeAeY=
github.com/sacloud/go-http v0.1.6 h1:lJGXDt9xrxJiDszRPaN9NIP8MVj10YKMzmnyzdSfI8w=
github.com/sacloud/go-http v0.1.6/go.mod h1:oLAHoDJRkptf8sq4fE8oERLkdCh0kJWfWu+paoJY7I0=
github.com/sacloud/iaas-api-go v1.11.1 h1:2MsFZ4H1uRdRVx2nVXuERWQ3swoFc3XreIV5hJ3Nsws=
github.com/sacloud/iaas-api-go v1.11.1/go.mod h1:uBDSa06F/V0OnoR66jGdbH0PVnCJw+NeE9RVbVgMfss=
github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7Afeks=
github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c=
github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc=
github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw=
github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8=
github.com/sacloud/iaas-api-go v1.12.0 h1:kqXFn3HzCiawlX6hVJb1GVqcSJqcmiGHB4Zp14sxiI8=
github.com/sacloud/iaas-api-go v1.12.0/go.mod h1:SZLXeWOdXk3WReIS557sbU1gkOgrE4rseIBQV1B3b7o=
github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck=
github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.25 h1:/8rfZAdFfafRXOgz+ZpMZZWZ5pYggCY9t7e/BvjaBHM=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.25/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/selectel/domains-go v1.0.2 h1:Si6iGaMnTFJxwiJVI50DOdZnwcxc87kqaWrVQYW0a4U=
github.com/selectel/domains-go v1.0.2/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM=
github.com/selectel/go-selvpcclient/v3 v3.1.1/go.mod h1:NM7IXhh1IzqZ88DOw1Qc5Ez3tULLViXo95l5+rKPuyQ=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@ -556,11 +603,14 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CY
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
github.com/softlayer/softlayer-go v1.1.2 h1:rUSSGCyaxymvTOsaFjwr+cGxA8muw3xg2LSrIMNcN/c=
github.com/softlayer/softlayer-go v1.1.2/go.mod h1:hvAbzGH4LRXA6yXY8BNx99yoqZ7urfDdtl9mvBf0G+g=
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
github.com/softlayer/softlayer-go v1.1.3 h1:dfFzt5eOKIAyB/b78fHMyDu5ICx0ZtxL9NRhBlf831A=
github.com/softlayer/softlayer-go v1.1.3/go.mod h1:Pc7F57OgUKaAam7TtpqkUeqL7QyKknfiUI4R49h41/U=
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=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
@ -577,8 +627,9 @@ github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -588,24 +639,29 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 h1:ERwcXqhc94L9cFxtiI0pvt7IJtlHl/p/Jayl3mLw+ms=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 h1:LoYv5u+gUoFpU/AmIuTRG/2KiEkdm9gCC0dTvk8WITQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898/go.mod h1:c1j6YQ+vCbeA8kJ59Im4UnMd1GxovlpPBDhGZoewfn8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY=
github.com/transip/gotransip/v6 v6.20.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c=
github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA=
github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c h1:mKnW6IGLw7uXu6DL6RitufZWcXS6hCnauXRUFof7rKM=
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c/go.mod h1:F4UyVEmq4/m5lAmx+GccrxyRCXmnBjzUL09JLTQFp94=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a h1:w4PK5/N9kq8PfNxBv8a5t1bqlYRrVT7XzT7iTPTtiPk=
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a/go.mod h1:Xwz7o+ExFtxR/i0aJDnTXuiccQJlOxDgNe6FsZC4TzQ=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
@ -618,24 +674,33 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0=
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E=
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA=
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e h1:jLIqA7M9qY31g/Nw/5htVD0DFbxmLnlFZcHKJiG3osI=
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 h1:wtzLQJmghkSUb1YkeFphIh7ST7NNVDaVOJZSAJcjMdw=
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4/go.mod h1:9d1MV6u4lK715YXnZceKqhP4L0bKBKmv4mSLnVSjJaM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
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 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw=
go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -643,25 +708,29 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
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=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -678,8 +747,10 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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=
@ -705,17 +776,20 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
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=
@ -725,7 +799,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -762,21 +839,31 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/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-20220908164124-27713097b956/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.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=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
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=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -785,17 +872,24 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@ -816,25 +910,29 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
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.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
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=
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/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=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.111.0 h1:bwKi+z2BsdwYFRKrqwutM+axAlYLz83gt5pDSXCJT+0=
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk=
google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis=
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=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -847,8 +945,12 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds=
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
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=
@ -860,8 +962,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.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.63.1 h1:pNClQmvdlyNUiwFETOux/PYqfhmA7BrswEdGRnib1fA=
google.golang.org/grpc v1.63.1/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
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=
@ -874,8 +976,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
@ -890,11 +992,10 @@ gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdOD
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.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/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/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA=
gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
gopkg.in/ns1/ns1-go.v2 v2.9.1 h1:3/QYzUazRCSE49d3sh1Q+X7IrDp/I7OqR/M7dKA0Oks=
gopkg.in/ns1/ns1-go.v2 v2.9.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@ -909,6 +1010,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -917,5 +1019,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=
software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

View file

@ -155,7 +155,7 @@ func generateCLIHelp(models *Providers) error {
}
func generateReadMe(models *Providers) error {
max, lines := extractTableData(models)
maximum, lines := extractTableData(models)
file, err := os.Open(readmePath)
if err != nil {
@ -174,7 +174,7 @@ func generateReadMe(models *Providers) error {
if text == startLine {
_, _ = fmt.Fprintln(buffer, text)
err = writeDNSTable(buffer, lines, max)
err = writeDNSTable(buffer, lines, maximum)
if err != nil {
return err
}
@ -208,14 +208,14 @@ func extractTableData(models *Providers) (int, [][]string) {
items := []string{fmt.Sprintf(readmePattern, "Manual", "manual")}
var max int
var maximum int
for _, pvd := range models.Providers {
item := fmt.Sprintf(readmePattern, strings.ReplaceAll(pvd.Name, "|", "/"), pvd.Code)
items = append(items, item)
if max < len(item) {
max = len(item)
if maximum < len(item) {
maximum = len(item)
}
}
@ -253,7 +253,7 @@ func extractTableData(models *Providers) (int, [][]string) {
lines = append(lines, line)
}
return max, lines
return maximum, lines
}
func writeDNSTable(w io.Writer, lines [][]string, size int) error {

View file

@ -78,15 +78,26 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) {
return values, nil
}
func GetOneWithFallback[T any](main string, defaultValue T, fn func(string) (T, error), names ...string) T {
v, _ := getOneWithFallback(main, names...)
value, err := fn(v)
if err != nil {
return defaultValue
}
return value
}
func getOneWithFallback(main string, names ...string) (string, string) {
value := GetOrFile(main)
if len(value) > 0 {
if value != "" {
return value, main
}
for _, name := range names {
value := GetOrFile(name)
if len(value) > 0 {
if value != "" {
return value, main
}
}
@ -94,43 +105,32 @@ func getOneWithFallback(main string, names ...string) (string, string) {
return "", main
}
// GetOrDefaultInt returns the given environment variable value as an integer.
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultInt(envVar string, defaultValue int) int {
v, err := strconv.Atoi(GetOrFile(envVar))
if err != nil {
return defaultValue
}
return v
}
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
v := GetOrDefaultInt(envVar, -1)
if v < 0 {
return defaultValue
}
return time.Duration(v) * time.Second
}
// GetOrDefaultString returns the given environment variable value as a string.
// Returns the default if the env var cannot be found.
func GetOrDefaultString(envVar, defaultValue string) string {
v := GetOrFile(envVar)
if v == "" {
return defaultValue
}
return v
func GetOrDefaultString(envVar string, defaultValue string) string {
return getOrDefault(envVar, defaultValue, ParseString)
}
// GetOrDefaultBool returns the given environment variable value as a boolean.
// Returns the default if the env var cannot be coopered to a boolean, or is not found.
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
v, err := strconv.ParseBool(GetOrFile(envVar))
return getOrDefault(envVar, defaultValue, strconv.ParseBool)
}
// GetOrDefaultInt returns the given environment variable value as an integer.
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultInt(envVar string, defaultValue int) int {
return getOrDefault(envVar, defaultValue, strconv.Atoi)
}
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
// Returns the default if the env var cannot be coopered to an int, or is not found.
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
return getOrDefault(envVar, defaultValue, ParseSecond)
}
func getOrDefault[T any](envVar string, defaultValue T, fn func(string) (T, error)) T {
v, err := fn(GetOrFile(envVar))
if err != nil {
return defaultValue
}
@ -161,3 +161,26 @@ func GetOrFile(envVar string) string {
return strings.TrimSuffix(string(fileContents), "\n")
}
// ParseSecond parses env var value (string) to a second (time.Duration).
func ParseSecond(s string) (time.Duration, error) {
v, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if v < 0 {
return 0, fmt.Errorf("unsupported value: %d", v)
}
return time.Duration(v) * time.Second, nil
}
// ParseString parses env var value (string) to a string but throws an error when the string is empty.
func ParseString(s string) (string, error) {
if s == "" {
return "", errors.New("empty string")
}
return s, nil
}

View file

@ -15,12 +15,12 @@ func TestGetWithFallback(t *testing.T) {
var1Missing := os.Getenv("TEST_LEGO_VAR_MISSING_1")
var2Missing := os.Getenv("TEST_LEGO_VAR_MISSING_2")
defer func() {
t.Cleanup(func() {
_ = os.Setenv("TEST_LEGO_VAR_EXIST_1", var1Exist)
_ = os.Setenv("TEST_LEGO_VAR_EXIST_2", var2Exist)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_1", var1Missing)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_2", var2Missing)
}()
})
err := os.Setenv("TEST_LEGO_VAR_EXIST_1", "VAR1")
require.NoError(t, err)
@ -94,8 +94,10 @@ func TestGetWithFallback(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
value, err := GetWithFallback(test.groups...)
if len(test.expected.error) > 0 {
if test.expected.error != "" {
assert.EqualError(t, err, test.expected.error)
} else {
require.NoError(t, err)
@ -105,6 +107,73 @@ func TestGetWithFallback(t *testing.T) {
}
}
func TestGetOneWithFallback(t *testing.T) {
var1Exist := os.Getenv("TEST_LEGO_VAR_EXIST_1")
var2Exist := os.Getenv("TEST_LEGO_VAR_EXIST_2")
var1Missing := os.Getenv("TEST_LEGO_VAR_MISSING_1")
var2Missing := os.Getenv("TEST_LEGO_VAR_MISSING_2")
t.Cleanup(func() {
_ = os.Setenv("TEST_LEGO_VAR_EXIST_1", var1Exist)
_ = os.Setenv("TEST_LEGO_VAR_EXIST_2", var2Exist)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_1", var1Missing)
_ = os.Setenv("TEST_LEGO_VAR_MISSING_2", var2Missing)
})
err := os.Setenv("TEST_LEGO_VAR_EXIST_1", "VAR1")
require.NoError(t, err)
err = os.Setenv("TEST_LEGO_VAR_EXIST_2", "VAR2")
require.NoError(t, err)
err = os.Unsetenv("TEST_LEGO_VAR_MISSING_1")
require.NoError(t, err)
err = os.Unsetenv("TEST_LEGO_VAR_MISSING_2")
require.NoError(t, err)
testCases := []struct {
desc string
main string
defaultValue string
alts []string
expected string
}{
{
desc: "with value and no alternative",
main: "TEST_LEGO_VAR_EXIST_1",
defaultValue: "oops",
expected: "VAR1",
},
{
desc: "with value and alternatives",
main: "TEST_LEGO_VAR_EXIST_1",
defaultValue: "oops",
alts: []string{"TEST_LEGO_VAR_MISSING_1"},
expected: "VAR1",
},
{
desc: "without value and no alternatives",
main: "TEST_LEGO_VAR_MISSING_1",
defaultValue: "oops",
expected: "oops",
},
{
desc: "without value and alternatives",
main: "TEST_LEGO_VAR_MISSING_1",
defaultValue: "oops",
alts: []string{"TEST_LEGO_VAR_EXIST_1"},
expected: "VAR1",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
value := GetOneWithFallback(test.main, test.defaultValue, ParseString, test.alts...)
assert.Equal(t, test.expected, value)
})
}
}
func TestGetOrDefaultInt(t *testing.T) {
testCases := []struct {
desc string

View file

@ -3,6 +3,7 @@ package tester
import (
"fmt"
"os"
"slices"
)
// EnvTest Environment variables manager for tests.
@ -143,10 +144,5 @@ func (e *EnvTest) Apply(envVars map[string]string) {
}
func (e *EnvTest) isManagedKey(varName string) bool {
for _, key := range e.keys {
if key == varName {
return true
}
}
return false
return slices.Contains(e.keys, varName)
}

View file

@ -28,10 +28,10 @@ const (
type acmeDNSClient interface {
// UpdateTXTRecord updates the provided account's TXT record
// to the given value or returns an error.
UpdateTXTRecord(goacmedns.Account, string) error
UpdateTXTRecord(account goacmedns.Account, value string) error
// RegisterAccount registers and returns a new account
// with the given allowFrom restriction or returns an error.
RegisterAccount([]string) (goacmedns.Account, error)
RegisterAccount(allowFrom []string) (goacmedns.Account, error)
}
// DNSProvider implements the challenge.Provider interface.

View file

@ -108,7 +108,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
case config.APIKey != "" && config.SecretKey != "":
credential = credentials.NewAccessKeyCredential(config.APIKey, config.SecretKey)
default:
return nil, fmt.Errorf("alicloud: ram role or credentials missing")
return nil, errors.New("alicloud: ram role or credentials missing")
}
conf := sdk.NewConfig().WithTimeout(config.HTTPTimeout)
@ -198,7 +198,7 @@ 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)
return "", fmt.Errorf("could not find zone: %w", err)
}
var hostedZone alidns.DomainInDescribeDomains

View file

@ -29,5 +29,5 @@ lego --email you@example.com --dns alidns --domains my.example.org run
ALICLOUD_HTTP_TIMEOUT = "API request timeout"
[Links]
API = "https://www.alibabacloud.com/help/doc-detail/42875.htm"
API = "https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records"
GoClient = "https://github.com/aliyun/alibaba-cloud-sdk-go"

View file

@ -115,7 +115,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("allinkl: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("allinkl: could not find zone for domain %q: %w", domain, err)
}
ctx := context.Background()

View file

@ -12,7 +12,7 @@ import (
"time"
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
)
const apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php"

View file

@ -111,7 +111,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("arvancloud: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("arvancloud: could not find zone for domain %q: %w", domain, err)
}
authZone = dns01.UnFqdn(authZone)
@ -152,7 +152,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("arvancloud: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("arvancloud: could not find zone for domain %q: %w", domain, err)
}
authZone = dns01.UnFqdn(authZone)
@ -166,7 +166,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
}
if err := d.client.DeleteRecord(context.Background(), authZone, recordID); err != nil {
return fmt.Errorf("arvancloud: failed to delate TXT record: id=%s: %w", recordID, err)
return fmt.Errorf("arvancloud: failed to delete TXT record: id=%s: %w", recordID, err)
}
// deletes record ID from map

View file

@ -108,7 +108,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("aurora: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("aurora: could not find zone for domain %q: %w", domain, err)
}
// 1. Aurora will happily create the TXT record when it is provided a fqdn,
@ -160,7 +160,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN))
if err != nil {
return fmt.Errorf("aurora: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("aurora: could not find zone for domain %q: %w", domain, err)
}
authZone = dns01.UnFqdn(authZone)

View file

@ -4,7 +4,6 @@ import (
"testing"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -133,10 +132,10 @@ func TestLivePresent(t *testing.T) {
envTest.RestoreEnv()
provider, err := NewDNSProvider()
assert.NoError(t, err)
require.NoError(t, err)
err = provider.Present(envTest.GetDomain(), "", "123d==")
assert.NoError(t, err)
require.NoError(t, err)
}
func TestLiveCleanUp(t *testing.T) {
@ -146,8 +145,8 @@ func TestLiveCleanUp(t *testing.T) {
envTest.RestoreEnv()
provider, err := NewDNSProvider()
assert.NoError(t, err)
require.NoError(t, err)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
assert.NoError(t, err)
require.NoError(t, err)
}

View file

@ -118,7 +118,7 @@ func (d *dnsProviderPrivate) getHostedZoneID(ctx context.Context, fqdn string) (
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return "", fmt.Errorf("could not find zone for FQDN %q: %w", fqdn, err)
return "", fmt.Errorf("could not find zone: %w", err)
}
dc := privatedns.NewPrivateZonesClientWithBaseURI(d.config.ResourceManagerEndpoint, d.config.SubscriptionID)

View file

@ -118,7 +118,7 @@ func (d *dnsProviderPublic) getHostedZoneID(ctx context.Context, fqdn string) (s
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return "", fmt.Errorf("could not find zone for FQDN %q: %w", fqdn, err)
return "", fmt.Errorf("could not find zone: %w", err)
}
dc := dns.NewZonesClientWithBaseURI(d.config.ResourceManagerEndpoint, d.config.SubscriptionID)

View file

@ -3,14 +3,19 @@
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"
)
@ -28,9 +33,22 @@ const (
EnvClientID = envNamespace + "CLIENT_ID"
EnvClientSecret = envNamespace + "CLIENT_SECRET"
EnvOIDCToken = envNamespace + "OIDC_TOKEN"
EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH"
EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL"
EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN"
EnvAuthMethod = envNamespace + "AUTH_METHOD"
EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT"
EnvServiceDiscoveryFilter = envNamespace + "SERVICEDISCOVERY_FILTER"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL"
EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
)
// Config is used to configure the creation of the DNSProvider.
@ -46,9 +64,20 @@ type Config struct {
ClientSecret string
TenantID string
OIDCToken string
OIDCTokenFilePath string
OIDCRequestURL string
OIDCRequestToken string
AuthMethod string
AuthMSITimeout time.Duration
PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPClient *http.Client
ServiceDiscoveryFilter string
}
// NewDefaultConfig returns a default configuration for the DNSProvider.
@ -94,6 +123,22 @@ func NewDNSProvider() (*DNSProvider, error) {
config.ClientSecret = env.GetOrFile(EnvClientSecret)
config.TenantID = env.GetOrFile(EnvTenantID)
config.OIDCToken = env.GetOrFile(EnvOIDCToken)
config.OIDCTokenFilePath = env.GetOrFile(EnvOIDCTokenFilePath)
config.ServiceDiscoveryFilter = env.GetOrFile(EnvServiceDiscoveryFilter)
oidcValues, _ := env.GetWithFallback(
[]string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL},
[]string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken},
)
config.OIDCRequestURL = oidcValues[EnvOIDCRequestURL]
config.OIDCRequestToken = oidcValues[EnvOIDCRequestToken]
config.AuthMethod = env.GetOrFile(EnvAuthMethod)
config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second)
return NewDNSProviderConfig(config)
}
@ -103,38 +148,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("azuredns: the configuration of the DNS provider is nil")
}
var err error
var credentials azcore.TokenCredential
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
options := azidentity.ClientSecretCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: config.Environment,
},
}
credentials, err = azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, &options)
if err != nil {
return nil, fmt.Errorf("azuredns: %w", err)
}
} else {
options := azidentity.DefaultAzureCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: config.Environment,
},
}
credentials, err = azidentity.NewDefaultAzureCredential(&options)
if err != nil {
return nil, fmt.Errorf("azuredns: %w", err)
}
if config.HTTPClient == nil {
config.HTTPClient = &http.Client{Timeout: 5 * time.Second}
}
if config.SubscriptionID == "" {
return nil, errors.New("azuredns: SubscriptionID is missing")
}
if config.ResourceGroup == "" {
return nil, errors.New("azuredns: ResourceGroup is missing")
credentials, err := getCredentials(config)
if err != nil {
return nil, fmt.Errorf("azuredns: Unable to retrieve valid credentials: %w", err)
}
var dnsProvider challenge.ProviderTimeout
@ -169,7 +189,89 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return d.provider.CleanUp(domain, token, keyAuth)
}
func deref[T string | int | int32 | int64](v *T) T {
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 getAuthZone(fqdn string) (string, error) {
authZone := env.GetOrFile(EnvZoneName)
if authZone != "" {
return authZone, nil
}
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return "", fmt.Errorf("could not find zone: %w", err)
}
return authZone, nil
}
func deref[T any](v *T) T {
if v == nil {
var zero T
return zero

View file

@ -27,15 +27,12 @@ lego --domains example.com --email your_example@email.com --dns azuredns run
### Using Managed Identity (Azure VM)
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_SUBSCRIPTION_ID=<your target zone subscription ID> \
AZURE_RESOURCE_GROUP=<your target zone resource group name> \
lego --domains example.com --email your_example@email.com --dns azuredns run
### Using Managed Identity (Azure Arc)
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_SUBSCRIPTION_ID=<your target zone subscription ID> \
AZURE_RESOURCE_GROUP=<your target zone resource group name> \
IMDS_ENDPOINT=http://localhost:40342 \
IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \
lego --domains example.com --email your_example@email.com --dns azuredns run
@ -45,19 +42,75 @@ lego --domains example.com --email your_example@email.com --dns azuredns run
Additional = '''
## Description
Azure Credentials are automatically detected in the following locations and prioritized in the following order:
Several authentication methods can be used to authenticate against Azure DNS API.
### Default Azure Credentials (default option)
Default Azure Credentials automatically detects in the following locations and prioritized in the following order:
1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`
2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH`
3. Workload identity for resources hosted in Azure environment (see below)
4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI
4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI
Link:
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
### Environment variables
#### Service Discovery
Lego automatically finds all visible Azure (private) DNS zones using [Azure ResourceGraph query](https://learn.microsoft.com/en-us/azure/governance/resource-graph/).
This can be limited by specifying environment variable `AZURE_SUBSCRIPTION_ID` and/or `AZURE_RESOURCE_GROUP` which limits the
DNS zones to only a subscription or to one resourceGroup.
Additionally environment variable `AZURE_SERVICEDISCOVERY_FILTER` can be used to filter DNS zones with an addition Kusto filter eg:
```
resources
| where type =~ "microsoft.network/dnszones"
| ${AZURE_SERVICEDISCOVERY_FILTER}
| project subscriptionId, resourceGroup, name
```
#### Client secret
The Azure Credentials can be configured using the following environment variables:
* AZURE_CLIENT_ID = "Client ID"
* AZURE_CLIENT_SECRET = "Client secret"
* AZURE_TENANT_ID = "Tenant ID"
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
#### Client certificate
The Azure Credentials can be configured using the following environment variables:
* AZURE_CLIENT_ID = "Client ID"
* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
* AZURE_TENANT_ID = "Tenant ID"
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
### Workload identity
#### Azure Managed Identity
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
Here is a summary of the steps to follow to use it :
* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`.
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
* create a federated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account.
Link :
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`.
### Azure Managed Identity
#### Azure Managed Identity (with Azure workload)
The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets.
@ -87,6 +140,11 @@ az role assignment create \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}/TXT/${AZ_RECORD_SET}"
```
A timeout wrapper is configured for this authentication method.
The duration can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
The default timeout is 2 seconds.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
#### Azure Managed Identity (with Azure Arc)
The Azure Arc agent provides the ability to use a Managed Identity on resources hosted outside of Azure
@ -95,22 +153,26 @@ The Azure Arc agent provides the ability to use a Managed Identity on resources
While the upstream `azidentity` SDK will try to automatically identify and use the Azure Arc metadata service,
if you get `azuredns: DefaultAzureCredential: failed to acquire a token.` error messages,
you may need to set the environment variables:
* `IMDS_ENDPOINT=http://localhost:40342`
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
* `IMDS_ENDPOINT=http://localhost:40342`
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
#### Workload identity for AKS
A timeout wrapper is configured for this authentication method.
The duration can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
The default timeout is 2 seconds.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
### Azure CLI
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
The Azure CLI is a command-line tool provided by Microsoft to interact with Azure resources.
It provides an easy way to authenticate by simply running `az login` command.
The generated token will be cached by default in the `~/.azure` folder.
Here is a summary of the steps to follow to use it :
* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`.
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`.
Link :
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
### Open ID Connect
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`.
'''
@ -119,12 +181,16 @@ Link :
AZURE_CLIENT_ID = "Client ID"
AZURE_CLIENT_SECRET = "Client secret"
AZURE_TENANT_ID = "Tenant ID"
AZURE_SUBSCRIPTION_ID = "DNS zone subscription ID"
AZURE_RESOURCE_GROUP = "DNS zone resource group"
AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
[Configuration.Additional]
AZURE_ENVIRONMENT = "Azure environment, one of: public, usgovernment, and china"
AZURE_SUBSCRIPTION_ID = "DNS zone subscription ID"
AZURE_RESOURCE_GROUP = "DNS zone resource group"
AZURE_SERVICEDISCOVERY_FILTER = "Advanced ServiceDiscovery filter using Kusto query condition"
AZURE_PRIVATE_ZONE = "Set to true to use Azure Private DNS Zones and not public"
AZURE_ZONE_NAME = "Zone name to use inside Azure DNS service to add the TXT record in"
AZURE_AUTH_METHOD = "Specify which authentication method to use"
AZURE_AUTH_MSI_TIMEOUT = "Managed Identity timeout duration"
AZURE_TTL = "The TTL of the TXT record used for the DNS challenge"
AZURE_POLLING_INTERVAL = "Time between DNS propagation check"
AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"

View file

@ -1,8 +1,6 @@
package azuredns
import (
"net/http"
"net/http/httptest"
"testing"
"time"
@ -25,20 +23,10 @@ func TestNewDNSProvider(t *testing.T) {
envVars map[string]string
expected string
}{
{
desc: "success",
envVars: map[string]string{
EnvEnvironment: "",
EnvSubscriptionID: "A",
EnvResourceGroup: "B",
},
},
{
desc: "unknown environment",
envVars: map[string]string{
EnvEnvironment: "test",
EnvSubscriptionID: "A",
EnvResourceGroup: "B",
EnvEnvironment: "test",
},
expected: "azuredns: unknown environment test",
},
@ -67,78 +55,6 @@ func TestNewDNSProvider(t *testing.T) {
}
}
func TestNewDNSProviderConfig(t *testing.T) {
testCases := []struct {
desc string
subscriptionID string
resourceGroup string
privateZone bool
handler func(w http.ResponseWriter, r *http.Request)
expected string
}{
{
desc: "success (public)",
subscriptionID: "A",
resourceGroup: "B",
privateZone: false,
},
{
desc: "success (private)",
subscriptionID: "A",
resourceGroup: "B",
privateZone: true,
},
{
desc: "SubscriptionID missing",
subscriptionID: "",
resourceGroup: "",
expected: "azuredns: SubscriptionID is missing",
},
{
desc: "ResourceGroup missing",
subscriptionID: "A",
resourceGroup: "",
expected: "azuredns: ResourceGroup is missing",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.SubscriptionID = test.subscriptionID
config.ResourceGroup = test.resourceGroup
config.PrivateZone = test.privateZone
mux := http.NewServeMux()
server := httptest.NewServer(mux)
t.Cleanup(server.Close)
if test.handler == nil {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
} else {
mux.HandleFunc("/", test.handler)
}
p, err := NewDNSProviderConfig(config)
if test.expected != "" {
require.EqualError(t, err, test.expected)
return
}
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.provider)
if test.privateZone {
assert.IsType(t, p.provider, new(DNSProviderPrivate))
} else {
assert.IsType(t, p.provider, new(DNSProviderPublic))
}
})
}
}
func TestLivePresent(t *testing.T) {
if !envTest.IsLiveTest() {
t.Skip("skipping live test")

View file

@ -0,0 +1,105 @@
package azuredns
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
func checkOIDCConfig(config *Config) error {
if config.TenantID == "" {
return errors.New("azuredns: TenantID is missing")
}
if config.ClientID == "" {
return errors.New("azuredns: ClientID is missing")
}
if config.OIDCToken == "" && config.OIDCTokenFilePath == "" && (config.OIDCRequestURL == "" || config.OIDCRequestToken == "") {
return errors.New("azuredns: OIDCToken, OIDCTokenFilePath or OIDCRequestURL and OIDCRequestToken must be set")
}
return nil
}
func getOIDCAssertion(config *Config) func(ctx context.Context) (string, error) {
return func(ctx context.Context) (string, error) {
var token string
if config.OIDCToken != "" {
token = strings.TrimSpace(config.OIDCToken)
}
if config.OIDCTokenFilePath != "" {
fileTokenRaw, err := os.ReadFile(config.OIDCTokenFilePath)
if err != nil {
return "", fmt.Errorf("azuredns: error retrieving token file with path %s: %w", config.OIDCTokenFilePath, err)
}
fileToken := strings.TrimSpace(string(fileTokenRaw))
if config.OIDCToken != fileToken {
return "", fmt.Errorf("azuredns: token file with path %s does not match token from environment variable", config.OIDCTokenFilePath)
}
token = fileToken
}
if token == "" && config.OIDCRequestURL != "" && config.OIDCRequestToken != "" {
return getOIDCToken(config)
}
return token, nil
}
}
func getOIDCToken(config *Config) (string, error) {
req, err := http.NewRequest(http.MethodGet, config.OIDCRequestURL, http.NoBody)
if err != nil {
return "", fmt.Errorf("azuredns: failed to build OIDC request: %w", err)
}
query, err := url.ParseQuery(req.URL.RawQuery)
if err != nil {
return "", errors.New("azuredns: cannot parse OIDC request URL query")
}
if query.Get("audience") == "" {
query.Set("audience", "api://AzureADTokenExchange")
req.URL.RawQuery = query.Encode()
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.OIDCRequestToken))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := config.HTTPClient.Do(req)
if err != nil {
return "", fmt.Errorf("azuredns: cannot request OIDC token: %w", err)
}
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return "", fmt.Errorf("azuredns: cannot parse OIDC token response: %w", err)
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusNoContent {
return "", fmt.Errorf("azuredns: OIDC token request received HTTP status %d with response: %s", resp.StatusCode, body)
}
var returnedToken struct {
Count int `json:"count"`
Value string `json:"value"`
}
if err := json.Unmarshal(body, &returnedToken); err != nil {
return "", fmt.Errorf("azuredns: cannot unmarshal OIDC token response: %w", err)
}
return returnedToken.Value, nil
}

View file

@ -9,40 +9,30 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
)
// DNSProviderPrivate implements the challenge.Provider interface for Azure Private Zone DNS.
type DNSProviderPrivate struct {
config *Config
zoneClient *armprivatedns.PrivateZonesClient
recordClient *armprivatedns.RecordSetsClient
config *Config
credentials azcore.TokenCredential
serviceDiscoveryZones map[string]ServiceDiscoveryZone
}
// NewDNSProviderPrivate creates a DNSProviderPrivate structure with initialized Azure clients.
// NewDNSProviderPrivate creates a DNSProviderPrivate structure.
func NewDNSProviderPrivate(config *Config, credentials azcore.TokenCredential) (*DNSProviderPrivate, error) {
options := arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: config.Environment,
},
}
zoneClient, err := armprivatedns.NewPrivateZonesClient(config.SubscriptionID, credentials, &options)
zones, err := discoverDNSZones(context.Background(), config, credentials)
if err != nil {
return nil, err
}
recordClient, err := armprivatedns.NewRecordSetsClient(config.SubscriptionID, credentials, &options)
if err != nil {
return nil, err
return nil, fmt.Errorf("discover DNS zones: %w", err)
}
return &DNSProviderPrivate{
config: config,
zoneClient: zoneClient,
recordClient: recordClient,
config: config,
credentials: credentials,
serviceDiscoveryZones: zones,
}, nil
}
@ -57,18 +47,23 @@ func (d *DNSProviderPrivate) Present(domain, _, keyAuth string) error {
ctx := context.Background()
info := dns01.GetChallengeInfo(domain, keyAuth)
zone, err := d.getHostedZoneID(ctx, info.EffectiveFQDN)
zone, err := d.getHostedZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
client, err := newPrivateZoneClient(zone, d.credentials, d.config.Environment)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
// Get existing record set
rset, err := d.recordClient.Get(ctx, d.config.ResourceGroup, zone, armprivatedns.RecordTypeTXT, subDomain, nil)
resp, err := client.Get(ctx, subDomain)
if err != nil {
var respErr *azcore.ResponseError
if !errors.As(err, &respErr) || respErr.StatusCode != http.StatusNotFound {
@ -77,32 +72,22 @@ func (d *DNSProviderPrivate) Present(domain, _, keyAuth string) error {
}
// Construct unique TXT records using map
uniqRecords := map[string]struct{}{info.Value: {}}
if rset.RecordSet.Properties != nil && rset.RecordSet.Properties.TxtRecords != nil {
for _, txtRecord := range rset.RecordSet.Properties.TxtRecords {
// Assume Value doesn't contain multiple strings
if len(txtRecord.Value) > 0 {
uniqRecords[deref(txtRecord.Value[0])] = struct{}{}
}
}
}
uniqRecords := privateUniqueRecords(resp.RecordSet, info.Value)
var txtRecords []*armprivatedns.TxtRecord
for txt := range uniqRecords {
txtRecord := txt
txtRecords = append(txtRecords, &armprivatedns.TxtRecord{Value: []*string{&txtRecord}})
txtRecords = append(txtRecords, &armprivatedns.TxtRecord{Value: to.SliceOfPtrs(txt)})
}
ttlInt64 := int64(d.config.TTL)
rec := armprivatedns.RecordSet{
Name: &subDomain,
Properties: &armprivatedns.RecordSetProperties{
TTL: &ttlInt64,
TTL: to.Ptr(int64(d.config.TTL)),
TxtRecords: txtRecords,
},
}
_, err = d.recordClient.CreateOrUpdate(ctx, d.config.ResourceGroup, zone, armprivatedns.RecordTypeTXT, subDomain, rec, nil)
_, err = client.CreateOrUpdate(ctx, subDomain, rec)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
@ -115,17 +100,22 @@ func (d *DNSProviderPrivate) CleanUp(domain, _, keyAuth string) error {
ctx := context.Background()
info := dns01.GetChallengeInfo(domain, keyAuth)
zone, err := d.getHostedZoneID(ctx, info.EffectiveFQDN)
zone, err := d.getHostedZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
client, err := newPrivateZoneClient(zone, d.credentials, d.config.Environment)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
_, err = d.recordClient.Delete(ctx, d.config.ResourceGroup, zone, armprivatedns.RecordTypeTXT, subDomain, nil)
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
_, err = client.Delete(ctx, subDomain)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
@ -134,21 +124,67 @@ func (d *DNSProviderPrivate) CleanUp(domain, _, keyAuth string) error {
}
// Checks that azure has a zone for this domain name.
func (d *DNSProviderPrivate) getHostedZoneID(ctx context.Context, fqdn string) (string, error) {
if zone := env.GetOrFile(EnvZoneName); zone != "" {
return zone, nil
}
authZone, err := dns01.FindZoneByFqdn(fqdn)
func (d *DNSProviderPrivate) getHostedZone(fqdn string) (ServiceDiscoveryZone, error) {
authZone, err := getAuthZone(fqdn)
if err != nil {
return "", err
return ServiceDiscoveryZone{}, err
}
zone, err := d.zoneClient.Get(ctx, d.config.ResourceGroup, dns01.UnFqdn(authZone), nil)
if err != nil {
return "", err
azureZone, exists := d.serviceDiscoveryZones[dns01.UnFqdn(authZone)]
if !exists {
return ServiceDiscoveryZone{}, fmt.Errorf("could not find zone (from discovery): %s", authZone)
}
// zone.Name shouldn't have a trailing dot(.)
return dns01.UnFqdn(deref(zone.Name)), nil
return azureZone, nil
}
// privateZoneClient provides Azure client for one DNS zone.
type privateZoneClient struct {
zone ServiceDiscoveryZone
recordClient *armprivatedns.RecordSetsClient
}
// newPrivateZoneClient creates privateZoneClient structure with initialized Azure client.
func newPrivateZoneClient(zone ServiceDiscoveryZone, credential azcore.TokenCredential, environment cloud.Configuration) (*privateZoneClient, error) {
options := &arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: environment,
},
}
recordClient, err := armprivatedns.NewRecordSetsClient(zone.SubscriptionID, credential, options)
if err != nil {
return nil, err
}
return &privateZoneClient{
zone: zone,
recordClient: recordClient,
}, nil
}
func (c privateZoneClient) Get(ctx context.Context, subDomain string) (armprivatedns.RecordSetsClientGetResponse, error) {
return c.recordClient.Get(ctx, c.zone.ResourceGroup, c.zone.Name, armprivatedns.RecordTypeTXT, subDomain, nil)
}
func (c privateZoneClient) CreateOrUpdate(ctx context.Context, subDomain string, rec armprivatedns.RecordSet) (armprivatedns.RecordSetsClientCreateOrUpdateResponse, error) {
return c.recordClient.CreateOrUpdate(ctx, c.zone.ResourceGroup, c.zone.Name, armprivatedns.RecordTypeTXT, subDomain, rec, nil)
}
func (c privateZoneClient) Delete(ctx context.Context, subDomain string) (armprivatedns.RecordSetsClientDeleteResponse, error) {
return c.recordClient.Delete(ctx, c.zone.ResourceGroup, c.zone.Name, armprivatedns.RecordTypeTXT, subDomain, nil)
}
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
if len(txtRecord.Value) > 0 {
uniqRecords[deref(txtRecord.Value[0])] = struct{}{}
}
}
}
return uniqRecords
}

View file

@ -9,40 +9,30 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
)
// DNSProviderPublic implements the challenge.Provider interface for Azure Public Zone DNS.
type DNSProviderPublic struct {
config *Config
zoneClient *armdns.ZonesClient
recordClient *armdns.RecordSetsClient
config *Config
credentials azcore.TokenCredential
serviceDiscoveryZones map[string]ServiceDiscoveryZone
}
// NewDNSProviderPublic creates a DNSProviderPublic structure with intialised Azure clients.
// NewDNSProviderPublic creates a DNSProviderPublic structure.
func NewDNSProviderPublic(config *Config, credentials azcore.TokenCredential) (*DNSProviderPublic, error) {
options := arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: config.Environment,
},
}
zoneClient, err := armdns.NewZonesClient(config.SubscriptionID, credentials, &options)
zones, err := discoverDNSZones(context.Background(), config, credentials)
if err != nil {
return nil, err
}
recordClient, err := armdns.NewRecordSetsClient(config.SubscriptionID, credentials, &options)
if err != nil {
return nil, err
return nil, fmt.Errorf("discover DNS zones: %w", err)
}
return &DNSProviderPublic{
config: config,
zoneClient: zoneClient,
recordClient: recordClient,
config: config,
credentials: credentials,
serviceDiscoveryZones: zones,
}, nil
}
@ -57,18 +47,23 @@ func (d *DNSProviderPublic) Present(domain, _, keyAuth string) error {
ctx := context.Background()
info := dns01.GetChallengeInfo(domain, keyAuth)
zone, err := d.getHostedZoneID(ctx, info.EffectiveFQDN)
zone, err := d.getHostedZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
client, err := newPublicZoneClient(zone, d.credentials, d.config.Environment)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
// Get existing record set
rset, err := d.recordClient.Get(ctx, d.config.ResourceGroup, zone, subDomain, armdns.RecordTypeTXT, nil)
resp, err := client.Get(ctx, subDomain)
if err != nil {
var respErr *azcore.ResponseError
if !errors.As(err, &respErr) || respErr.StatusCode != http.StatusNotFound {
@ -76,33 +71,22 @@ func (d *DNSProviderPublic) Present(domain, _, keyAuth string) error {
}
}
// Construct unique TXT records using map
uniqRecords := map[string]struct{}{info.Value: {}}
if rset.RecordSet.Properties != nil && rset.RecordSet.Properties.TxtRecords != nil {
for _, txtRecord := range rset.RecordSet.Properties.TxtRecords {
// Assume Value doesn't contain multiple strings
if len(txtRecord.Value) > 0 {
uniqRecords[deref(txtRecord.Value[0])] = struct{}{}
}
}
}
uniqRecords := publicUniqueRecords(resp.RecordSet, info.Value)
var txtRecords []*armdns.TxtRecord
for txt := range uniqRecords {
txtRecord := txt
txtRecords = append(txtRecords, &armdns.TxtRecord{Value: []*string{&txtRecord}})
txtRecords = append(txtRecords, &armdns.TxtRecord{Value: to.SliceOfPtrs(txt)})
}
ttlInt64 := int64(d.config.TTL)
rec := armdns.RecordSet{
Name: &subDomain,
Properties: &armdns.RecordSetProperties{
TTL: &ttlInt64,
TTL: to.Ptr(int64(d.config.TTL)),
TxtRecords: txtRecords,
},
}
_, err = d.recordClient.CreateOrUpdate(ctx, d.config.ResourceGroup, zone, subDomain, armdns.RecordTypeTXT, rec, nil)
_, err = client.CreateOrUpdate(ctx, subDomain, rec)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
@ -115,17 +99,22 @@ func (d *DNSProviderPublic) CleanUp(domain, _, keyAuth string) error {
ctx := context.Background()
info := dns01.GetChallengeInfo(domain, keyAuth)
zone, err := d.getHostedZoneID(ctx, info.EffectiveFQDN)
zone, err := d.getHostedZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone)
client, err := newPublicZoneClient(zone, d.credentials, d.config.Environment)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
_, err = d.recordClient.Delete(ctx, d.config.ResourceGroup, zone, subDomain, armdns.RecordTypeTXT, nil)
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
_, err = client.Delete(ctx, subDomain)
if err != nil {
return fmt.Errorf("azuredns: %w", err)
}
@ -134,21 +123,66 @@ func (d *DNSProviderPublic) CleanUp(domain, _, keyAuth string) error {
}
// Checks that azure has a zone for this domain name.
func (d *DNSProviderPublic) getHostedZoneID(ctx context.Context, fqdn string) (string, error) {
if zone := env.GetOrFile(EnvZoneName); zone != "" {
return zone, nil
}
authZone, err := dns01.FindZoneByFqdn(fqdn)
func (d *DNSProviderPublic) getHostedZone(fqdn string) (ServiceDiscoveryZone, error) {
authZone, err := getAuthZone(fqdn)
if err != nil {
return "", err
return ServiceDiscoveryZone{}, err
}
zone, err := d.zoneClient.Get(ctx, d.config.ResourceGroup, dns01.UnFqdn(authZone), nil)
if err != nil {
return "", err
azureZone, exists := d.serviceDiscoveryZones[dns01.UnFqdn(authZone)]
if !exists {
return ServiceDiscoveryZone{}, fmt.Errorf("could not find zone (from discovery): %s", authZone)
}
// zone.Name shouldn't have a trailing dot(.)
return dns01.UnFqdn(deref(zone.Name)), nil
return azureZone, nil
}
type publicZoneClient struct {
zone ServiceDiscoveryZone
recordClient *armdns.RecordSetsClient
}
// newPublicZoneClient creates publicZoneClient structure with initialized Azure client.
func newPublicZoneClient(zone ServiceDiscoveryZone, credential azcore.TokenCredential, environment cloud.Configuration) (*publicZoneClient, error) {
options := &arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: environment,
},
}
recordClient, err := armdns.NewRecordSetsClient(zone.SubscriptionID, credential, options)
if err != nil {
return nil, err
}
return &publicZoneClient{
zone: zone,
recordClient: recordClient,
}, nil
}
func (c publicZoneClient) Get(ctx context.Context, subDomain string) (armdns.RecordSetsClientGetResponse, error) {
return c.recordClient.Get(ctx, c.zone.ResourceGroup, c.zone.Name, subDomain, armdns.RecordTypeTXT, nil)
}
func (c publicZoneClient) CreateOrUpdate(ctx context.Context, subDomain string, rec armdns.RecordSet) (armdns.RecordSetsClientCreateOrUpdateResponse, error) {
return c.recordClient.CreateOrUpdate(ctx, c.zone.ResourceGroup, c.zone.Name, subDomain, armdns.RecordTypeTXT, rec, nil)
}
func (c publicZoneClient) Delete(ctx context.Context, subDomain string) (armdns.RecordSetsClientDeleteResponse, error) {
return c.recordClient.Delete(ctx, c.zone.ResourceGroup, c.zone.Name, subDomain, armdns.RecordTypeTXT, nil)
}
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
if len(txtRecord.Value) > 0 {
uniqRecords[deref(txtRecord.Value[0])] = struct{}{}
}
}
}
return uniqRecords
}

View file

@ -0,0 +1,126 @@
package azuredns
import (
"bytes"
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
)
type ServiceDiscoveryZone struct {
Name string
SubscriptionID string
ResourceGroup string
}
const (
ResourceGraphTypePublicDNSZone = "microsoft.network/dnszones"
ResourceGraphTypePrivateDNSZone = "microsoft.network/privatednszones"
)
const ResourceGraphQueryOptionsTop int32 = 1000
// discoverDNSZones finds all visible Azure DNS zones based on optional subscriptionID, resourceGroup and serviceDiscovery filter using Kusto query.
func discoverDNSZones(ctx context.Context, config *Config, credentials azcore.TokenCredential) (map[string]ServiceDiscoveryZone, error) {
options := &arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: config.Environment,
},
}
client, err := armresourcegraph.NewClient(credentials, options)
if err != nil {
return nil, err
}
// Set options
requestOptions := &armresourcegraph.QueryRequestOptions{
ResultFormat: to.Ptr(armresourcegraph.ResultFormatObjectArray),
Top: to.Ptr(ResourceGraphQueryOptionsTop),
Skip: to.Ptr[int32](0),
}
zones := map[string]ServiceDiscoveryZone{}
for {
// create the query request
request := armresourcegraph.QueryRequest{
Query: to.Ptr(createGraphQuery(config)),
Options: requestOptions,
}
result, err := client.Resources(ctx, request, nil)
if err != nil {
return zones, err
}
resultList, ok := result.Data.([]any)
if !ok {
// got invalid or empty data, skipping
break
}
for _, row := range resultList {
rowData, ok := row.(map[string]any)
if !ok {
continue
}
zoneName, ok := rowData["name"].(string)
if !ok {
continue
}
if _, exists := zones[zoneName]; exists {
return zones, fmt.Errorf(`found duplicate dns zone "%s"`, zoneName)
}
zones[zoneName] = ServiceDiscoveryZone{
Name: zoneName,
ResourceGroup: rowData["resourceGroup"].(string),
SubscriptionID: rowData["subscriptionId"].(string),
}
}
*requestOptions.Skip += ResourceGraphQueryOptionsTop
if result.TotalRecords != nil {
if int64(deref(requestOptions.Skip)) >= deref(result.TotalRecords) {
break
}
}
}
return zones, nil
}
func createGraphQuery(config *Config) string {
buf := new(bytes.Buffer)
buf.WriteString("\nresources\n")
resourceType := ResourceGraphTypePublicDNSZone
if config.PrivateZone {
resourceType = ResourceGraphTypePrivateDNSZone
}
_, _ = fmt.Fprintf(buf, "| where type =~ %q\n", resourceType)
if config.SubscriptionID != "" {
_, _ = fmt.Fprintf(buf, "| where subscriptionId =~ %q\n", config.SubscriptionID)
}
if config.ResourceGroup != "" {
_, _ = fmt.Fprintf(buf, "| where resourceGroup =~ %q\n", config.ResourceGroup)
}
if config.ServiceDiscoveryFilter != "" {
_, _ = fmt.Fprintf(buf, "| %s\n", config.ServiceDiscoveryFilter)
}
buf.WriteString("| project subscriptionId, resourceGroup, name")
return buf.String()
}

View file

@ -0,0 +1,129 @@
package azuredns
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_createGraphQuery(t *testing.T) {
testCases := []struct {
desc string
cfg *Config
expected string
}{
{
desc: "empty configuration (public)",
cfg: &Config{},
expected: `
resources
| where type =~ "microsoft.network/dnszones"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "SubscriptionID (public)",
cfg: &Config{
SubscriptionID: "123",
},
expected: `
resources
| where type =~ "microsoft.network/dnszones"
| where subscriptionId =~ "123"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "ResourceGroup (public)",
cfg: &Config{
ResourceGroup: "123",
},
expected: `
resources
| where type =~ "microsoft.network/dnszones"
| where resourceGroup =~ "123"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "ServiceDiscoveryFilter (public)",
cfg: &Config{
ServiceDiscoveryFilter: "123",
},
expected: `
resources
| where type =~ "microsoft.network/dnszones"
| 123
| project subscriptionId, resourceGroup, name`,
},
{
desc: "empty configuration (private)",
cfg: &Config{
PrivateZone: true,
},
expected: `
resources
| where type =~ "microsoft.network/privatednszones"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "SubscriptionID (private)",
cfg: &Config{
SubscriptionID: "123",
PrivateZone: true,
},
expected: `
resources
| where type =~ "microsoft.network/privatednszones"
| where subscriptionId =~ "123"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "ResourceGroup (private)",
cfg: &Config{
ResourceGroup: "123",
PrivateZone: true,
},
expected: `
resources
| where type =~ "microsoft.network/privatednszones"
| where resourceGroup =~ "123"
| project subscriptionId, resourceGroup, name`,
},
{
desc: "ServiceDiscoveryFilter (private)",
cfg: &Config{
ServiceDiscoveryFilter: "123",
PrivateZone: true,
},
expected: `
resources
| where type =~ "microsoft.network/privatednszones"
| 123
| project subscriptionId, resourceGroup, name`,
},
{
desc: "all (private)",
cfg: &Config{
SubscriptionID: "123",
ResourceGroup: "456",
ServiceDiscoveryFilter: "789",
PrivateZone: true,
},
expected: `
resources
| where type =~ "microsoft.network/privatednszones"
| where subscriptionId =~ "123"
| where resourceGroup =~ "456"
| 789
| project subscriptionId, resourceGroup, name`,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
query := createGraphQuery(test.cfg)
assert.Equal(t, strings.ReplaceAll(test.expected, "\r", ""), query)
})
}
}

View file

@ -108,7 +108,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("brandit: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("brandit: could not find zone for domain %q: %w", domain, err)
}
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
@ -155,7 +155,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("brandit: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err)
return fmt.Errorf("brandit: could not find zone for domain %q: %w", domain, err)
}
// gets the record's unique ID

View file

@ -11,6 +11,7 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@ -58,7 +59,7 @@ func (c *Client) ListRecords(ctx context.Context, account, dnsZone string) (*Lis
}
for len(result.Response.RR) < result.Response.Total[0] {
query.Add("first", fmt.Sprint(result.Response.Last[0]+1))
query.Add("first", strconv.Itoa(result.Response.Last[0]+1))
tmp := &Response[*ListRecordsResponse]{}
err := c.do(ctx, query, tmp)
@ -76,7 +77,7 @@ func (c *Client) ListRecords(ctx context.Context, account, dnsZone string) (*Lis
// AddRecord adds a DNS record.
// https://portal.brandit.com/apidocv3#addDNSRR
func (c *Client) AddRecord(ctx context.Context, domainName, account, newRecordID string, record Record) (*AddRecord, error) {
value := strings.Join([]string{record.Name, fmt.Sprint(record.TTL), "IN", record.Type, record.Content}, " ")
value := strings.Join([]string{record.Name, strconv.Itoa(record.TTL), "IN", record.Type, record.Content}, " ")
query := url.Values{}
query.Add("command", "addDNSRR")

View file

@ -9,7 +9,6 @@ import (
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/miekg/dns"
"github.com/nrdcg/bunny-go"
)
@ -94,7 +93,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
authZone, err := getZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("bunny: failed to find zone: fqdn=%s: %w", info.EffectiveFQDN, err)
return fmt.Errorf("bunny: could not find zone for domain %q: %w", domain, err)
}
ctx := context.Background()
@ -129,7 +128,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
authZone, err := getZone(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("bunny: failed to find zone: fqdn=%s: %w", info.EffectiveFQDN, err)
return fmt.Errorf("bunny: could not find zone for domain %q: %w", domain, err)
}
ctx := context.Background()
@ -191,30 +190,11 @@ func getZone(fqdn string) (string, error) {
return "", err
}
zone, _, err := splitDomain(dns01.UnFqdn(authZone))
if err != nil {
return "", err
}
zone := dns01.UnFqdn(authZone)
return zone, nil
}
func splitDomain(full string) (string, string, error) {
split := dns.Split(full)
if len(split) < 2 {
return "", "", fmt.Errorf("unsupported domain: %s", full)
}
if len(split) == 2 {
return full, "", nil
}
domain := full[split[len(split)-2]:]
subDomain := full[:split[len(split)-2]-1]
return domain, subDomain, nil
}
func pointer[T string | int | int32 | int64](v T) *T { return &v }
func deref[T string | int | int32 | int64](v *T) T {

Some files were not shown because too many files have changed in this diff Show more