Compare commits

..

1 commit

Author SHA1 Message Date
79c5b83559 [#1] Add NNS Challenge support
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2023-08-07 16:15:39 +03:00
615 changed files with 4032 additions and 19439 deletions

1
.github/FUNDING.yml vendored Normal file
View file

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

View file

@ -1,4 +1,4 @@
name: 🐞 Bug Report
name: Bug Report
description: Create a report to help us improve.
labels: [bug]
body:
@ -42,7 +42,6 @@ body:
- Through Caddy
- Through Terraform ACME provider
- Through Bitnami
- Through Zoraxy
- Other
validations:
required: true

View file

@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Questions
- name: Questions
url: https://github.com/go-acme/lego/discussions
about: If you have a question, or are looking for advice, please post on our Discussions section!
- name: 📖 Documentation
- name: lego documentation
url: https://go-acme.github.io/lego/
about: Please take a look to our documentation.

View file

@ -1,4 +1,4 @@
name: 💡 Feature request
name: Feature request
description: Suggest an idea for this project.
body:
- type: checkboxes
@ -21,7 +21,6 @@ body:
- Through Caddy
- Through Terraform ACME provider
- Through Bitnami
- Through Zoraxy
- Other
validations:
required: true

View file

@ -1,4 +1,4 @@
name: 🧩 New DNS provider support
name: New DNS provider support
description: Request for the support of a new DNS provider.
title: "Support for provider: <put the name of your provider>"
labels: [enhancement, new-provider]

View file

@ -1,30 +0,0 @@
PULL REQUEST TEMPLATE FOR MAINTAINERS ONLY.
https://github.com/go-acme/lego/compare/master...ldez:branch?quick_pull=1&title=Add+DNS+provider+for+&labels=enhancement,area/dnsprovider,state/need-user-tests&template=mnp.md
?quick_pull=1&title=Add+DNS+provider+for+&labels=enhancement,area/dnsprovider,state/need-user-tests&template=mnp.md
---
- [x] adds a description to your PR
- [x] have a homogeneous design with the other providers
- [ ] add tests (units)
- [ ] add tests ("live")
- [ ] add a provider descriptor
- [ ] generate CLI help, documentation, and readme.
- [ ] be able to do: _(and put the output of this command to a comment)_
```bash
make build
rm -rf .lego
EXAMPLE_USERNAME=xxx \
./dist/lego -m your_email@example.com --dns EXAMPLE -d *.example.com -d example.com -s https://acme-staging-v02.api.letsencrypt.org/directory run
```
Note the wildcard domain is important.
- [ ] pass the linter
- [ ] do `go mod tidy`
Ping @xxx, can you run the command (with your domain, email, credentials, etc.)?
Closes #

View file

@ -11,30 +11,30 @@ jobs:
name: Build and deploy documentation
runs-on: ubuntu-latest
env:
GO_VERSION: stable
HUGO_VERSION: 0.131.0
GO_VERSION: '1.20'
HUGO_VERSION: 0.101.0
CGO_ENABLED: 0
steps:
# https://github.com/marketplace/actions/checkout
- name: Check out code
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
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
with:
fetch-depth: 0
- name: Generate DNS docs
run: make generate-dns
- name: Install Hugo
run: |
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-amd64.deb
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.deb
sudo dpkg -i /tmp/hugo.deb
- name: Build Documentation
@ -42,7 +42,7 @@ jobs:
# https://github.com/marketplace/actions/github-pages
- name: Deploy to GitHub Pages
uses: crazy-max/ghaction-github-pages@v4
uses: crazy-max/ghaction-github-pages@v3
with:
target_branch: gh-pages
build_dir: docs/public

View file

@ -16,20 +16,38 @@ jobs:
strategy:
matrix:
go-version: [ oldstable, stable ]
go-version: [ '1.19', '1.20', 1.x ]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v4
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v3
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v3
with:
# In order:
# * Module download cache
# * Build cache (Linux)
# * Build cache (Mac)
# * Build cache (Windows)
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
%LocalAppData%\go-build
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-
- name: Test
run: go test -v -cover ./...

View file

@ -12,26 +12,35 @@ jobs:
name: Main Process
runs-on: ubuntu-latest
env:
GO_VERSION: stable
GOLANGCI_LINT_VERSION: v1.60.1
HUGO_VERSION: 0.131.0
GO_VERSION: '1.20'
GOLANGCI_LINT_VERSION: v1.53.1
HUGO_VERSION: 0.54.0
CGO_ENABLED: 0
LEGO_E2E_TESTS: CI
MEMCACHED_HOSTS: localhost:11211
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@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v3
with:
go-version: ${{ env.GO_VERSION }}
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Check and get dependencies
run: |
@ -46,10 +55,10 @@ jobs:
golangci-lint --version
- name: Install Pebble
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@3fe019bbc0a41ed16e2fee31592bb91751acaa47
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@main
- name: Install challtestsrv
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@3fe019bbc0a41ed16e2fee31592bb91751acaa47
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@main
- name: Set up a Memcached server
uses: niden/actions-memcached@v7
@ -69,7 +78,7 @@ jobs:
- name: Install Hugo
run: |
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-amd64.deb
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.deb
sudo dpkg -i /tmp/hugo.deb
- name: Build Documentation

View file

@ -11,16 +11,10 @@ jobs:
name: Release version
runs-on: ubuntu-latest
env:
GO_VERSION: stable
GO_VERSION: '1.20'
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
@ -38,12 +32,12 @@ jobs:
swap-storage: false
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
@ -53,21 +47,17 @@ 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@v3
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# https://goreleaser.com/ci/actions/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v4
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,12 +1,14 @@
run:
timeout: 10m
skip-files: []
linters-settings:
govet:
enable:
- shadow
check-shadowing: true
gocyclo:
min-complexity: 12
maligned:
suggest-new: true
goconst:
min-len: 3
min-occurrences: 3
@ -86,20 +88,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:
- gomnd # deprecated
- deadcode # deprecated
- exhaustivestruct # deprecated
- golint # deprecated
- ifshort # deprecated
- interfacer # deprecated
- maligned # deprecated
- nosnakecase # deprecated
- scopelint # deprecated
- structcheck # deprecated
- varcheck # deprecated
- cyclop # duplicate of gocyclo
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
@ -109,13 +111,13 @@ linters:
- dupl # not relevant
- prealloc # too many false-positive
- bodyclose # too many false-positive
- mnd
- gomnd
- testpackage # not relevant
- tparallel # not relevant
- paralleltest # not relevant
- nestif # too many false-positive
- wrapcheck
- err113 # not relevant
- goerr113 # not relevant
- nlreturn # not relevant
- wsl # not relevant
- exhaustive # not relevant
@ -135,13 +137,10 @@ linters:
- nonamedreturns
- musttag # false-positive https://github.com/junk1tm/musttag/issues/17
- gosmopolitan # not relevant
- exportloopref # Useless with go1.22
- canonicalheader # Can create side effects in the context of API clients
- usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-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'
@ -153,10 +152,6 @@ 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
@ -186,8 +181,6 @@ issues:
text: load is a global variable
- path: 'providers/dns/([\d\w]+/)*[\d\w]+_test.go'
text: 'envTest is a global variable'
- path: 'providers/http/([\d\w]+/)*[\d\w]+_test.go'
text: 'envTest is a global variable'
- path: providers/dns/namecheap/namecheap_test.go
text: 'testCases is a global variable'
- path: providers/dns/acmedns/acmedns_test.go
@ -228,18 +221,5 @@ issues:
- path: providers/dns/hosttech/internal/client_test.go
text: 'Duplicate words \(0\) found'
- path: cmd/cmd_renew.go
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'
text: 'cyclomatic complexity 16 of func `renewForDomains` is high'
# Those elements have been replaced by non-exposed structures.
- path: providers/dns/linode/linode_test.go
linters:
- staticcheck
text: "SA1019: linodego\\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated"
output:
sort-results: true
sort-order:
- linter
- file

View file

@ -41,14 +41,6 @@ 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 }}'
@ -141,33 +133,3 @@ 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,228 +1,13 @@
# Changelog
## [v4.19.2] - 2024-10-06
### Fixed
- **[lib]** go1.22 compatibility
## [v4.19.1] - 2024-10-06
### Fixed
- **[dnsprovider]** selectelv2: use baseURL from configuration
- **[dnsprovider]** epik: add User-Agent
## [v4.19.0] - 2024-10-03
### Added
- **[dnsprovider]** Add DNS provider for HuaweiCloud
- **[dnsprovider]** Add DNS provider for SelfHost.(de|eu)
- **[lib,cli,dnsprovider]** Add `dns.propagation-rns` option
- **[cli,dnsprovider]** Add `dns.propagation-wait` flag
- **[lib,dnsprovider]** Add `PropagationWait` function
### Changed
- **[dnsprovider]** ionos: follow CNAME
- **[lib,dnsprovider]** Reducing the lock strength of the soa cache entry
- **[lib,cli,dnsprovider]** Deprecation of `dns.disable-cp`, replaced by `dns.propagation-disable-ans`.
### Fixed
- **[dnsprovider]** Use UTC instead of GMT when possible
- **[dnsprovider]** namesilo: restrict CleanUp
- **[dnsprovider]** godaddy: fix cleanup
## [v4.18.0] - 2024-08-30
### Added
- **[dnsprovider]** Add DNS provider for mijn.host
- **[dnsprovider]** Add DNS provider for Lima-City
- **[dnsprovider]** Add DNS provider for DirectAdmin
- **[dnsprovider]** Add DNS provider for Mittwald
- **[lib,cli]** feat: add option to handle the overall request limit
- **[lib]** feat: expose certificates pool creation
### Changed
- **[cli]** feat: add LEGO_ISSUER_CERT_PATH to run hook
- **[dnsprovider]** bluecat: skip deploy
- **[dnsprovider]** ovh: allow to use ovh.conf file
- **[dnsprovider]** designate: allow manually overwriting DNS zone
### Fixed
- **[ari]** fix: avoid Int63n panic in ShouldRenewAt()
## [v4.17.4] - 2024-06-12
### Fixed
- **[dnsprovider]** Update dependencies
## [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
- **[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
- **[dnsprovider]** Add DNS provider for Yandex 360
- **[dnsprovider]** Add DNS provider for cloud.ru
- **[httpprovider]** Adding S3 support for HTTP domain validation
### Changed
- **[cli]** Allow to set EAB kid and hmac via environment variables
- **[dnsprovider]** Migrate to aws-sdk-go-v2 (lightsail, route53)
### Fixed
- **[dnsprovider]** nearlyfreespeech: fix authentication
- **[dnsprovider]** pdns: fix notify
- **[dnsprovider]** route53: avoid unexpected records deletion
## [v4.13.3] - 2023-07-25
### 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
@ -230,12 +15,10 @@ Cancelled due to CI failure.
- **[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
@ -246,39 +29,33 @@ 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
@ -286,15 +63,13 @@ 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
@ -303,30 +78,26 @@ 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
@ -336,12 +107,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
@ -351,7 +122,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
@ -360,7 +131,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
@ -373,8 +144,7 @@ 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
@ -701,7 +471,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
- **[dnsprovider]** azure: Allow for the use of MSI
- **[dnsprovider]** constellix: improve challenge.
- **[dnsprovider]** godaddy: allow parallel solve.
- **[dnsprovider]** namedotcom: get the actual registered domain, so we can remove just that from the hostname to be created
- **[dnsprovider]** namedotcom: get the actual registered domain so we can remove just that from the hostname to be created
- **[dnsprovider]** transip: updated the client to v6
### Fixed:
@ -739,7 +509,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 "LEGO_PATH" environment variable
- **[cli]** Add &#34;LEGO_PATH&#34; environment variable
### Changed:
@ -752,7 +522,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
### Fixed:
- **[dnsprovider]** zoneee: fix subdomains.
- **[dnsprovider]** designate: Don't clean up managed records like SOA and NS
- **[dnsprovider]** designate: Don&#39;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.
@ -777,7 +547,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't deactivate valid authorizations
- **[lib]** Don&#39;t deactivate valid authorizations
- **[lib]** Expose more SOA fields found by dns01.FindZoneByFqdn
### Fixed:
@ -805,7 +575,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
## [v3.0.1] - 2019-08-14
There was a problem when creating the tag v3.0.1, this tag has been invalidated.
There was a problem when creating the tag v3.0.1, this tag has been invalidate.
## [v3.0.0] - 2019-08-05
@ -950,7 +720,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 "HTTP request".
- **[dnsprovider]** Add DNS provider for &#34;HTTP request&#34;.
- **[dnsprovider]** Add DNS Provider for Vscale
- **[dnsprovider]** Add DNS Provider for TransIP
- **[dnsprovider]** Add DNS Provider for inwx
@ -1080,7 +850,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` environment variable to make the timeout for the RFC2136 provider configurable.
- lib: The `RFC2136_TIMEOUT` enviroment 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:
@ -1097,7 +867,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated.
- lib: The `DeleteRegistration` function on `acme.Client`. This deletes the registration as currently configured in the client.
- lib: The `ObtainCertificateForCSR` function on `acme.Client`. The function allows to request a certificate for an already existing CSR.
- CLI: The `--csr` switch. Allows to use already existing CSRs for certificate requests on the command line.
- CLI: The `--pem` flag. This will change the certificate output, so it outputs a .pem file concatanating the .key and .crt files together.
- CLI: The `--pem` flag. This will change the certificate output so it outputs a .pem file concatanating the .key and .crt files together.
- CLI: The `--dns-resolvers` flag. Allows for users to override the default DNS servers used for recursive lookup.
- lib: Added a memcached provider for the HTTP challenge.
- CLI: The `--memcached-host` flag. This allows to use memcached for challenge storage.
@ -1119,11 +889,11 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated.
- lib: The library will now skip challenge solving if a valid Authz already exists.
### Removed:
- lib: The library will no longer check for auto-renewed certificates. This has been removed from the spec and is not supported in Boulder.
- lib: The library will no longer check for auto renewed certificates. This has been removed from the spec and is not supported in Boulder.
### Fixed:
- lib: Fix a problem with the Route53 provider where it was possible the verification was published to a private zone.
- lib: Loading an account from file should fail if an integral part is nil
- lib: Loading an account from file should fail if a integral part is nil
- lib: Fix a potential issue where the Dyn provider could resolve to an incorrect zone.
- lib: If a registration encounteres a conflict, the old registration is now recovered.
- CLI: The account.json file no longer has the executable flag set.
@ -1191,7 +961,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated.
### Changed:
- lib: NewClient does no longer accept the optPort parameter
- lib: ObtainCertificate now returns a SAN certificate if you pass more than one domain.
- lib: ObtainCertificate now returns a SAN certificate if you pass more then one domain.
- lib: GetOCSPForCert now returns the parsed OCSP response instead of just the status.
- lib: ObtainCertificate has a new parameter `privKey crypto.PrivateKey` which lets you reuse an existing private key for new certificates.
- lib: RenewCertificate now expects the PrivateKey property of the CertificateResource to be set only if you want to reuse the key.

View file

@ -7,7 +7,7 @@ To ensure a great and easy experience for everyone, please review the few guidel
- Use the issue search to see if the issue has already been reported.
- Also look for closed issues to see if your issue has already been fixed.
- If both of the above do not apply, create a new issue and include as much information as possible.
- If both of the above do not apply create a new issue and include as much information as possible.
Bug reports should include all information a person could need to reproduce your problem without the need to
follow up for more information. If possible, provide detailed steps for us to reproduce it, the expected behaviour and the actual behaviour.

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-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
- Support [draft-ietf-acme-ari-01](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
@ -57,37 +57,33 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [Amazon Route 53](https://go-acme.github.io/lego/dns/route53/) | [ArvanCloud](https://go-acme.github.io/lego/dns/arvancloud/) | [Aurora DNS](https://go-acme.github.io/lego/dns/auroradns/) | [Autodns](https://go-acme.github.io/lego/dns/autodns/) |
| [Azure (deprecated)](https://go-acme.github.io/lego/dns/azure/) | [AzureDNS](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/) | [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/) |
| [DirectAdmin](https://go-acme.github.io/lego/dns/directadmin/) | [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/) | [Huawei Cloud](https://go-acme.github.io/lego/dns/huaweicloud/) | [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/) | [Lima-City](https://go-acme.github.io/lego/dns/limacity/) | [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/) | [mijn.host](https://go-acme.github.io/lego/dns/mijnhost/) | [Mittwald](https://go-acme.github.io/lego/dns/mittwald/) | [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/) | [SelfHost.(de/eu)](https://go-acme.github.io/lego/dns/selfhostde/) |
| [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/) |
| [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/) | [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/) | | | |
| [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 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 location != "" {
if len(location) > 0 {
a.core.jws.SetKid(location)
}

View file

@ -63,7 +63,7 @@ func (n *Manager) getNonce() (string, error) {
return GetFromResponse(resp)
}
// GetFromResponse Extracts a nonce from an HTTP response.
// GetFromResponse Extracts a nonce from a HTTP response.
func GetFromResponse(resp *http.Response) (string, error) {
if resp == nil {
return "", errors.New("nil response")

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/v4"
jose "github.com/go-jose/go-jose/v3"
)
// JWS Represents a JWS.

View file

@ -119,14 +119,6 @@ func (d *Doer) formatUserAgent() string {
func checkError(req *http.Request, resp *http.Response) error {
if resp.StatusCode >= http.StatusBadRequest {
if resp.StatusCode == http.StatusTooManyRequests {
return acme.TooManyRequestsError{
StatusCode: resp.StatusCode,
Method: req.Method,
URL: req.URL,
}
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)

View file

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

View file

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

View file

@ -3,6 +3,8 @@ 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.
@ -26,3 +28,26 @@ 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,6 +44,7 @@ func Test_getLink(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -76,7 +76,7 @@ type Meta struct {
ExternalAccountRequired bool `json:"externalAccountRequired"`
}
// ExtendedAccount an extended Account.
// ExtendedAccount a extended Account.
type ExtendedAccount struct {
Account
// Contains the value of the response header `Location`
@ -181,12 +181,6 @@ 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.
@ -327,7 +321,7 @@ type RenewalInfoResponse struct {
// SuggestedWindow contains two fields, start and end,
// whose values are timestamps which bound the window of time in which the CA recommends renewing the certificate.
SuggestedWindow Window `json:"suggestedWindow"`
// ExplanationURL is an optional URL pointing to a page which may explain why the suggested renewal window is what it is.
// ExplanationURL is a optional URL pointing to a page which may explain why the suggested renewal window is what it is.
// For example, it may be a page explaining the CA's dynamic load-balancing strategy,
// or a page documenting which certificates are affected by a mass revocation event.
// Callers SHOULD provide this URL to their operator, if present.
@ -335,11 +329,9 @@ type RenewalInfoResponse struct {
}
// RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint.
// - (4.2. RenewalInfo Objects) https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
// - (4.2. Updating Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
type RenewalInfoUpdateRequest struct {
// CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the
// certificate's authority key identifier and Serial is the certificate's serial number. For details, see:
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1
// CertID is the base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960] with any trailing '=' characters stripped.
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

@ -2,7 +2,6 @@ package acme
import (
"fmt"
"net/url"
)
// Errors types.
@ -57,14 +56,3 @@ func (p ProblemDetails) Error() string {
type NonceError struct {
*ProblemDetails
}
// TooManyRequestsError represents API rate limit violations reported by server.
type TooManyRequestsError struct {
StatusCode int
Method string
URL *url.URL
}
func (e TooManyRequestsError) Error() string {
return fmt.Sprintf("too many requests: HTTP %d: %s (%s)", e.StatusCode, e.URL, e.Method)
}

View file

@ -15,7 +15,6 @@ import (
"fmt"
"math/big"
"net"
"slices"
"strings"
"time"
@ -85,11 +84,11 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
// Borrowed from Go standard library, to handle various private key and PEM block types.
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlockDER, _ := pem.Decode(key)
if keyBlockDER == nil {
return nil, errors.New("invalid PEM block")
return nil, fmt.Errorf("invalid PEM block")
}
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
@ -217,26 +216,6 @@ 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 != "" {
@ -269,7 +248,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
// loop over the SubjectAltName DNS names
for _, sanName := range csr.DNSNames {
if slices.Contains(domains, sanName) {
if containsSAN(domains, sanName) {
// Duplicate; skip this name
continue
}
@ -288,6 +267,15 @@ 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 (nil)",
desc: "without SAN",
privateKey: privateKey,
domain: "lego.acme",
mustStaple: true,
expected: expected{len: 245},
},
{
desc: "without SAN (empty)",
desc: "without SAN",
privateKey: privateKey,
domain: "lego.acme",
san: []string{},
@ -86,6 +86,7 @@ func TestGenerateCSR(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -7,10 +7,18 @@ import (
"github.com/go-acme/lego/v4/log"
)
const (
// overallRequestLimit is the overall number of request per second
// 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.
overallRequestLimit = 18
)
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
resc, errc := make(chan acme.Authorization), make(chan domainError)
delay := time.Second / time.Duration(c.overallRequestLimit)
delay := time.Second / overallRequestLimit
for _, authzURL := range order.Authorizations {
time.Sleep(delay)
@ -27,14 +35,13 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
}
var responses []acme.Authorization
failures := newObtainError()
for range len(order.Authorizations) {
failures := make(obtainError)
for i := 0; i < len(order.Authorizations); i++ {
select {
case res := <-resc:
responses = append(responses, res)
case err := <-errc:
failures.Add(err.Domain, err.Error)
failures[err.Domain] = err.Error
}
}
@ -45,7 +52,12 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
close(resc)
close(errc)
return responses, failures.Join()
// be careful to not return an empty failures map;
// even if empty, they become non-nil error values
if len(failures) > 0 {
return responses, failures
}
return responses, nil
}
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {

View file

@ -22,17 +22,6 @@ import (
"golang.org/x/net/idna"
)
const (
// DefaultOverallRequestLimit is the overall number of request per second
// 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/
// ZeroSSL has a limit of 7.
// https://help.zerossl.com/hc/en-us/articles/17864245480093-Advantages-over-Using-Let-s-Encrypt#h_01HT4Z1JCJFJQFJ1M3P7S085Q9
DefaultOverallRequestLimit = 18
)
// maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024
@ -74,10 +63,6 @@ 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.
@ -94,10 +79,6 @@ 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 {
@ -107,7 +88,6 @@ type resolver interface {
type CertifierOptions struct {
KeyType certcrypto.KeyType
Timeout time.Duration
OverallRequestLimit int
}
// Certifier A service to obtain/renew/revoke certificates.
@ -115,23 +95,15 @@ type Certifier struct {
core *api.Core
resolver resolver
options CertifierOptions
overallRequestLimit int
}
// NewCertifier creates a Certifier.
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
c := &Certifier{
return &Certifier{
core: core,
resolver: resolver,
options: options,
}
c.overallRequestLimit = options.OverallRequestLimit
if c.overallRequestLimit <= 0 {
c.overallRequestLimit = DefaultOverallRequestLimit
}
return c
}
// Obtain tries to obtain a single certificate using all domains passed into it.
@ -154,7 +126,6 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
}
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -178,11 +149,11 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := newObtainError()
failures := make(obtainError)
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
if err != nil {
for _, auth := range authz {
failures.Add(challenge.GetTargetedDomain(auth), err)
failures[challenge.GetTargetedDomain(auth)] = err
}
}
@ -190,7 +161,12 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
c.deactivateAuthorizations(order, true)
}
return cert, failures.Join()
// Do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
@ -220,7 +196,6 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore,
NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
}
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -244,11 +219,11 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := newObtainError()
failures := make(obtainError)
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
if err != nil {
for _, auth := range authz {
failures.Add(challenge.GetTargetedDomain(auth), err)
failures[challenge.GetTargetedDomain(auth)] = err
}
}
@ -261,7 +236,12 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
cert.CSR = certcrypto.PEMEncode(request.CSR)
}
return cert, failures.Join()
// Do not return an empty failures map,
// because it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
}
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
@ -273,10 +253,8 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
}
}
commonName := ""
if len(domains[0]) <= 64 {
commonName = domains[0]
}
// Determine certificate name(s) based on the authorization resources
commonName := domains[0]
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
@ -284,12 +262,7 @@ 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.
var san []string
if commonName != "" {
san = append(san, commonName)
}
san := []string{commonName}
for _, auth := range order.Identifiers {
if auth.Value != commonName {
san = append(san, auth.Value)
@ -311,8 +284,9 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
return nil, err
}
commonName := domains[0]
certRes := &Resource{
Domain: domains[0],
Domain: commonName,
CertURL: respOrder.Certificate,
PrivateKey: privateKeyPem,
}
@ -634,13 +608,8 @@ 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: domain,
Domain: x509Certs[0].Subject.CommonName,
Certificate: cert,
IssuerCertificate: issuer,
CertURL: url,

View file

@ -1,37 +1,27 @@
package certificate
import (
"errors"
"bytes"
"fmt"
"sort"
)
type obtainError struct {
data map[string]error
}
// obtainError is returned when there are specific errors available per domain.
type obtainError map[string]error
func newObtainError() *obtainError {
return &obtainError{data: make(map[string]error)}
}
func (e obtainError) Error() string {
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
func (e *obtainError) Add(domain string, err error) {
e.data[domain] = err
var domains []string
for domain := range e {
domains = append(domains, domain)
}
sort.Strings(domains)
func (e *obtainError) Join() error {
if e == nil {
return nil
for _, domain := range domains {
_, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain])
}
if len(e.data) == 0 {
return nil
}
var err error
for d, e := range e.data {
err = errors.Join(err, fmt.Errorf("%s: %w", d, e))
}
return fmt.Errorf("error: one or more domains had a problem:\n%w", err)
return buffer.String()
}
type domainError struct {

View file

@ -1,69 +0,0 @@
package certificate
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
)
type TomatoError struct{}
func (t TomatoError) Error() string {
return "tomato"
}
type CarrotError struct{}
func (t CarrotError) Error() string {
return "carrot"
}
func Test_obtainError_Join(t *testing.T) {
failures := newObtainError()
failures.Add("example.com", &TomatoError{})
err := failures.Join()
to := &TomatoError{}
require.ErrorAs(t, err, &to)
}
func Test_obtainError_Join_multiple_domains(t *testing.T) {
failures := newObtainError()
failures.Add("example.com", &TomatoError{})
failures.Add("example.org", &CarrotError{})
err := failures.Join()
to := &TomatoError{}
require.ErrorAs(t, err, &to)
ca := &CarrotError{}
require.ErrorAs(t, err, &ca)
}
func Test_obtainError_Join_no_error(t *testing.T) {
failures := newObtainError()
require.NoError(t, failures.Join())
}
func Test_obtainError_Join_same_domain(t *testing.T) {
failures := newObtainError()
failures.Add("example.com", &TomatoError{})
failures.Add("example.com", &CarrotError{})
err := failures.Join()
to := &TomatoError{}
if errors.As(err, &to) {
require.Fail(t, "TomatoError should be overridden by CarrotError")
}
ca := &CarrotError{}
require.ErrorAs(t, err, &ca)
}

View file

@ -1,13 +1,16 @@
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"
@ -16,17 +19,15 @@ 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
}
// 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.
@ -41,11 +42,9 @@ func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.D
end := r.SuggestedWindow.End.UTC()
// Select a uniform random time within the suggested window.
rt := start
if window := end.Sub(start); window > 0 {
window := end.Sub(start)
randomDuration := time.Duration(rand.Int63n(int64(window)))
rt = rt.Add(randomDuration)
}
rt := start.Add(randomDuration)
// If the selected time is in the past, attempt renewal immediately.
if rt.Before(now) {
@ -73,7 +72,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 := MakeARICertID(req.Cert)
certID, err := makeCertID(req.Cert, req.Issuer, req.HashName)
if err != nil {
return nil, fmt.Errorf("error making certID: %w", err)
}
@ -89,43 +88,117 @@ 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
}
// 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 "", errors.New("leaf certificate is 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)
}
// Marshal the Serial Number into DER.
der, err := asn1.Marshal(leaf.SerialNumber)
_, 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) {
if leaf == nil {
return "", fmt.Errorf("leaf certificate is nil")
}
if issuer == nil {
return "", fmt.Errorf("issuer 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,
})
if err != nil {
return "", err
}
// 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")
}
// 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
// base64url-encode [RFC4648] the bytes of the DER-encoded CertID ASN.1 sequence [RFC6960].
encodedBytes := base64.URLEncoding.EncodeToString(certIDBytes)
// Any trailing '=' characters MUST be stripped.
return strings.TrimRight(encodedBytes, "="), nil
}

View file

@ -1,8 +1,11 @@
package certificate
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"io"
"net/http"
"testing"
"time"
@ -11,28 +14,62 @@ 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-----
MIIBQzCB66ADAgECAgUAh2VDITAKBggqhkjOPQQDAjAVMRMwEQYDVQQDEwpFeGFt
cGxlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBYxFDAS
BgNVBAMTC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeBZu
7cbpAYNXZLbbh8rNIzuOoqOOtmxA1v7cRm//AwyMwWxyHz4zfwmBhcSrf47NUAFf
qzLQ2PPQxdTXREYEnKMjMCEwHwYDVR0jBBgwFoAUaYhba4dGQEHhs3uEe6CuLN4B
yNQwCgYIKoZIzj0EAwIDRwAwRAIge09+S5TZAlw5tgtiVvuERV6cT4mfutXIlwTb
+FYN/8oCIClDsqBklhB9KAelFiYt9+6FDj3z4KGVelYM5MdsO3pK
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==
-----END CERTIFICATE-----`
ariLeafCertID = "aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE"
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"
)
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 := MakeARICertID(leaf)
actual, err := makeCertID(leaf, issuer, crypto.SHA256.String())
require.NoError(t, err)
assert.Equal(t, ariLeafCertID, actual)
}
@ -40,6 +77,8 @@ 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)
@ -50,7 +89,6 @@ 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": {
@ -71,18 +109,19 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf})
ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()})
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")
@ -96,7 +135,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},
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
handler: func(w http.ResponseWriter, r *http.Request) {
// API that takes 2ms to respond.
time.Sleep(2 * time.Millisecond)
@ -105,15 +144,24 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
{
desc: "API error",
httpClient: http.DefaultClient,
request: RenewalInfoRequest{leaf},
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
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()
@ -132,19 +180,105 @@ 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{
RenewalInfoResponse: acme.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)
@ -154,14 +288,13 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
t.Run("Window is in the future", func(t *testing.T) {
ri := RenewalInfoResponse{
RenewalInfoResponse: acme.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)
@ -170,14 +303,13 @@ 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{
RenewalInfoResponse: acme.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)
@ -187,17 +319,38 @@ 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{
RenewalInfoResponse: acme.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

@ -20,6 +20,9 @@ const (
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8737.html
TLSALPN01 = Type("tls-alpn-01")
// NNS01 is the "nns-01" ACME challenge
NNS01 = Type("nns-01")
)
func (t Type) String() string {

View file

@ -6,7 +6,6 @@ import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/acme"
@ -125,7 +124,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
}
log.Infof("[%s] acme: Checking DNS record propagation. [nameservers=%s]", domain, strings.Join(recursiveNameservers, ","))
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
time.Sleep(interval)
@ -215,7 +214,7 @@ func getChallengeFQDN(domain string, followCNAME bool) string {
}
// recursion counter so it doesn't spin out of control
for range 50 {
for limit := 0; limit < 50; limit++ {
// 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 fmt.Errorf("manual: could not find zone: %w", err)
return err
}
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
@ -33,11 +33,8 @@ 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 nil
return err
}
// CleanUp prints instructions for manually removing the TXT record.
@ -46,7 +43,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("manual: could not find zone: %w", err)
return err
}
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)

View file

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

View file

@ -47,6 +47,7 @@ func TestExtractSubDomain(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -92,6 +93,7 @@ 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,17 +14,18 @@ func TestToFqdn(t *testing.T) {
}{
{
desc: "simple",
domain: "foo.example.com",
expected: "foo.example.com.",
domain: "foo.bar.com",
expected: "foo.bar.com.",
},
{
desc: "already FQDN",
domain: "foo.example.com.",
expected: "foo.example.com.",
domain: "foo.bar.com.",
expected: "foo.bar.com.",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -42,17 +43,18 @@ func TestUnFqdn(t *testing.T) {
}{
{
desc: "simple",
fqdn: "foo.example.",
expected: "foo.example",
fqdn: "foo.bar.com.",
expected: "foo.bar.com",
},
{
desc: "already domain",
fqdn: "foo.example",
expected: "foo.example",
fqdn: "foo.bar.com",
expected: "foo.bar.com",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -5,7 +5,6 @@ import (
"fmt"
"net"
"os"
"slices"
"strconv"
"strings"
"sync"
@ -16,7 +15,10 @@ import (
const defaultResolvConf = "/etc/resolv.conf"
var fqdnSoaCache = &sync.Map{}
var (
fqdnSoaCache = map[string]*soaCacheEntry{}
muFqdnSoaCache sync.Mutex
)
var defaultNameservers = []string{
"google-public-dns-a.google.com:53",
@ -48,11 +50,9 @@ func (cache *soaCacheEntry) isExpired() bool {
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
func ClearFqdnCache() {
// TODO(ldez): use `fqdnSoaCache.Clear()` when updating to go1.23
fqdnSoaCache.Range(func(k, v any) bool {
fqdnSoaCache.Delete(k)
return true
})
muFqdnSoaCache.Lock()
fqdnSoaCache = map[string]*soaCacheEntry{}
muFqdnSoaCache.Unlock()
}
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
@ -98,12 +98,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
zone, err := FindZoneByFqdn(fqdn)
if err != nil {
return nil, fmt.Errorf("could not find zone: %w", err)
return nil, fmt.Errorf("could not determine the zone: %w", err)
}
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
if err != nil {
return nil, fmt.Errorf("NS call failed: %w", err)
return nil, err
}
for _, rr := range r.Answer {
@ -115,8 +115,7 @@ func lookupNameservers(fqdn string) ([]string, error) {
if len(authoritativeNss) > 0 {
return authoritativeNss, nil
}
return nil, fmt.Errorf("[zone=%s] could not determine authoritative nameservers", zone)
return nil, errors.New("could not determine authoritative nameservers")
}
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
@ -130,7 +129,7 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
return "", err
}
return soa.primaryNs, nil
}
@ -146,62 +145,60 @@ func FindZoneByFqdn(fqdn string) (string, error) {
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
soa, err := lookupSoaByFqdn(fqdn, nameservers)
if err != nil {
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
return "", err
}
return soa.zone, nil
}
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
muFqdnSoaCache.Lock()
defer muFqdnSoaCache.Unlock()
// Do we have it cached and is it still fresh?
entAny, ok := fqdnSoaCache.Load(fqdn)
if ok && entAny != nil {
ent, ok1 := entAny.(*soaCacheEntry)
if ok1 && !ent.isExpired() {
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
return ent, nil
}
}
ent, err := fetchSoaByFqdn(fqdn, nameservers)
if err != nil {
return nil, err
}
fqdnSoaCache.Store(fqdn, ent)
fqdnSoaCache[fqdn] = ent
return ent, nil
}
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
var err error
var r *dns.Msg
var in *dns.Msg
labelIndexes := dns.Split(fqdn)
for _, index := range labelIndexes {
domain := fqdn[index:]
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
if err != nil {
continue
}
if r == nil {
if in == nil {
continue
}
switch r.Rcode {
switch in.Rcode {
case dns.RcodeSuccess:
// Check if we got a SOA RR in the answer section
if len(r.Answer) == 0 {
if len(in.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(r) {
if dnsMsgContainsCNAME(in) {
continue
}
for _, ans := range r.Answer {
for _, ans := range in.Answer {
if soa, ok := ans.(*dns.SOA); ok {
return newSoaCacheEntry(soa), nil
}
@ -210,46 +207,36 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
// NXDOMAIN
default:
// Any response code other than NOERROR and NXDOMAIN is treated as error
return nil, &DNSError{Message: fmt.Sprintf("unexpected response for '%s'", domain), MsgOut: r}
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
}
}
return nil, &DNSError{Message: fmt.Sprintf("could not find the start of authority for '%s'", fqdn), MsgOut: r, Err: err}
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
}
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
_, ok := rr.(*dns.CNAME)
return ok
})
for _, ans := range msg.Answer {
if _, ok := ans.(*dns.CNAME); ok {
return true
}
}
return false
}
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
m := createDNSMsg(fqdn, rtype, recursive)
if len(nameservers) == 0 {
return nil, &DNSError{Message: "empty list of nameservers"}
}
var r *dns.Msg
var in *dns.Msg
var err error
var errAll error
for _, ns := range nameservers {
r, err = sendDNSQuery(m, ns)
if err == nil && len(r.Answer) > 0 {
in, err = sendDNSQuery(m, ns)
if err == nil && len(in.Answer) > 0 {
break
}
errAll = errors.Join(errAll, err)
}
if err != nil {
return r, errAll
}
return r, nil
return in, err
}
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
@ -267,82 +254,37 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
r, _, err := tcp.Exchange(m, ns)
if err != nil {
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
}
in, _, err := tcp.Exchange(m, ns)
return r, nil
return in, err
}
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
r, _, err := udp.Exchange(m, ns)
in, _, err := udp.Exchange(m, ns)
if r != nil && r.Truncated {
if in != nil && in.Truncated {
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
// If the TCP request succeeds, the "err" will reset to nil
r, _, err = tcp.Exchange(m, ns)
// 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])
}
if err != nil {
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
parts = append(parts, err.Error())
}
return r, nil
if len(parts) > 0 {
return ": " + strings.Join(parts, " ")
}
// 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, ";")
return ""
}

View file

@ -1,11 +1,9 @@
package dns01
import (
"errors"
"sort"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -30,6 +28,7 @@ func TestLookupNameserversOK(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.fqdn, func(t *testing.T) {
t.Parallel()
@ -53,11 +52,12 @@ func TestLookupNameserversErr(t *testing.T) {
{
desc: "invalid tld",
fqdn: "_null.n0n0.",
error: "could not find zone",
error: "could not determine the 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: "[fqdn=test.lego.zz.] could not find the start of authority for 'test.lego.zz.' [question='zz. IN SOA', code=NXDOMAIN]",
expectedError: "could not find the start of authority for test.lego.zz.: NXDOMAIN",
},
{
desc: "several non existent nameservers",
@ -123,15 +123,14 @@ var findXByFqdnTestCases = []struct {
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 ",
expectedError: "could not find the start of authority for mail.google.com.: read udp",
},
{
desc: "no nameservers",
fqdn: "test.ldez.com.",
zone: "ldez.com.",
nameservers: []string{},
expectedError: "[fqdn=test.ldez.com.] could not find the start of authority for 'test.ldez.com.': empty list of nameservers",
expectedError: "could not find the start of authority for test.ldez.com.",
},
}
@ -143,7 +142,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) {
zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers)
if test.expectedError != "" {
require.Error(t, err)
assert.ErrorContains(t, err, test.expectedError)
assert.Contains(t, err.Error(), test.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, test.zone, zone)
@ -160,7 +159,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) {
ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers)
if test.expectedError != "" {
require.Error(t, err)
assert.ErrorContains(t, err, test.expectedError)
assert.Contains(t, err.Error(), test.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, test.primaryNs, ns)
@ -198,69 +197,3 @@ 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

@ -4,7 +4,6 @@ import (
"fmt"
"net"
"strings"
"time"
"github.com/miekg/dns"
)
@ -24,52 +23,23 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
}
}
// DisableCompletePropagationRequirement obsolete.
// Deprecated: use DisableAuthoritativeNssPropagationRequirement instead.
func DisableCompletePropagationRequirement() ChallengeOption {
return DisableAuthoritativeNssPropagationRequirement()
}
func DisableAuthoritativeNssPropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.requireAuthoritativeNssPropagation = false
chlg.preCheck.requireCompletePropagation = false
return nil
}
}
func RecursiveNSsPropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.requireRecursiveNssPropagation = true
return nil
}
}
func PropagationWait(wait time.Duration, skipCheck bool) ChallengeOption {
return WrapPreCheck(func(domain, fqdn, value string, check PreCheckFunc) (bool, error) {
time.Sleep(wait)
if skipCheck {
return true, nil
}
return check(fqdn, value)
})
}
type preCheck struct {
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
checkFunc WrapPreCheckFunc
// require the TXT record to be propagated to all authoritative name servers
requireAuthoritativeNssPropagation bool
// require the TXT record to be propagated to all recursive name servers
requireRecursiveNssPropagation bool
requireCompletePropagation bool
}
func newPreCheck() preCheck {
return preCheck{
requireAuthoritativeNssPropagation: true,
requireCompletePropagation: true,
}
}
@ -83,43 +53,32 @@ func (p preCheck) call(domain, fqdn, value string) (bool, error) {
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
// Initial attempt to resolve at the recursive NS (require to get CNAME)
// Initial attempt to resolve at the recursive NS
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
if err != nil {
return false, err
}
if !p.requireCompletePropagation {
return true, nil
}
if r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
if p.requireRecursiveNssPropagation {
_, err = checkNameserversPropagation(fqdn, value, recursiveNameservers, false)
if err != nil {
return false, err
}
}
if !p.requireAuthoritativeNssPropagation {
return true, nil
}
authoritativeNss, err := lookupNameservers(fqdn)
if err != nil {
return false, err
}
return checkNameserversPropagation(fqdn, value, authoritativeNss, true)
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
}
// checkNameserversPropagation queries each of the given nameservers for the expected TXT record.
func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) {
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
for _, ns := range nameservers {
if addPort {
ns = net.JoinHostPort(ns, "53")
}
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false)
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
if err != nil {
return false, err
}

View file

@ -28,6 +28,7 @@ func TestCheckDNSPropagation(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()
@ -36,8 +37,8 @@ func TestCheckDNSPropagation(t *testing.T) {
ok, err := check.checkDNSPropagation(test.fqdn, test.value)
if test.expectError {
assert.Errorf(t, err, "PreCheckDNS must fail for %s", test.fqdn)
assert.False(t, ok, "PreCheckDNS must fail for %s", test.fqdn)
assert.Errorf(t, err, "PreCheckDNS must failed for %s", test.fqdn)
assert.False(t, ok, "PreCheckDNS must failed for %s", test.fqdn)
} else {
assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn)
assert.True(t, ok, "PreCheckDNS failed for %s", test.fqdn)
@ -68,11 +69,12 @@ func TestCheckAuthoritativeNss(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()
ok, _ := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
ok, _ := checkAuthoritativeNss(test.fqdn, test.value, test.ns)
assert.Equal(t, test.expected, ok, test.fqdn)
})
}
@ -102,11 +104,12 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ClearFqdnCache()
_, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
_, err := checkAuthoritativeNss(test.fqdn, test.value, test.ns)
require.Error(t, err)
assert.Contains(t, err.Error(), test.error)
})

View file

@ -57,7 +57,7 @@ func (m *hostMatcher) matches(r *http.Request, domain string) bool {
return strings.HasPrefix(r.Host, domain)
}
// arbitraryMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
type arbitraryMatcher string
func (m arbitraryMatcher) name() string {

View file

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

View file

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

View file

@ -0,0 +1,125 @@
package nns01
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
"github.com/go-acme/lego/v4/acme"
"github.com/go-acme/lego/v4/acme/api"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/log"
)
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
// Challenge implements the nns-01 challenge.
type Challenge struct {
core *api.Core
validate ValidateFunc
provider challenge.Provider
}
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
chlg := &Challenge{
core: core,
validate: validate,
provider: provider,
}
return chlg
}
// PreSolve submits the txt record to the nns provider.
func (c *Challenge) PreSolve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Preparing to solve NNS-01", domain)
chlng, keyAuth, err := c.getChallengeInfo(authz)
if err != nil {
return err
}
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
if err != nil {
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
}
return nil
}
func (c *Challenge) Solve(authz acme.Authorization) error {
domain := challenge.GetTargetedDomain(authz)
log.Infof("[%s] acme: Trying to solve NNS-01", domain)
chlng, keyAuth, err := c.getChallengeInfo(authz)
if err != nil {
return err
}
chlng.KeyAuthorization = keyAuth
return c.validate(c.core, domain, chlng)
}
// CleanUp cleans the challenge.
func (c *Challenge) CleanUp(authz acme.Authorization) error {
log.Infof("[%s] acme: Cleaning NNS-01 challenge", challenge.GetTargetedDomain(authz))
chlng, keyAuth, err := c.getChallengeInfo(authz)
if err != nil {
return err
}
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
}
func (c *Challenge) Sequential() (bool, time.Duration) {
if p, ok := c.provider.(sequential); ok {
return ok, p.Sequential()
}
return false, 0
}
type sequential interface {
Sequential() time.Duration
}
// RecordInfo contains the information use to create the TXT record.
type RecordInfo struct {
// FQDN is the full-qualified challenge domain (`acme-challenge.[domain]`)
FQDN string
// Value contains the value for the TXT record
Value string
}
// GetRecordInfo returns information used to create a TXT record which will fulfill the `nns-01` challenge.
func GetRecordInfo(domain, keyAuth string) RecordInfo {
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
// base64URL encoding without padding
value := base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
return RecordInfo{
Value: value,
FQDN: getRecordFQDN(domain),
}
}
func getRecordFQDN(domain string) string {
return fmt.Sprintf("acme-challenge.%s", domain)
}
func (c *Challenge) getChallengeInfo(authz acme.Authorization) (acme.Challenge, string, error) {
chlng, err := challenge.FindChallenge(challenge.NNS01, authz)
if err != nil {
return acme.Challenge{}, "", err
}
// Generate the Key Authorization for the challenge
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
if err != nil {
return acme.Challenge{}, "", err
}
return chlng, keyAuth, nil
}

View file

@ -0,0 +1,171 @@
package nns01
import (
"context"
"fmt"
"net/url"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
// multiSchemeClient unites invoker.RPCInvoke and common interface of
// rpcclient.Client and rpcclient.WSClient.
type multiSchemeClient interface {
actor.RPCActor
actor.RPCPollingWaiter
// Init turns client to "ready-to-work" state.
Init() error
// Close closes connections.
Close()
// GetContractStateByID returns state of the NNS contract on 1 input.
GetContractStateByID(int32) (*state.Contract, error)
}
type NNSProvider struct {
nnsServer string
account *wallet.Account
nnsContract util.Uint160
client multiSchemeClient
}
// NewNNSProvider returns configured NNSProvider instance.
func NewNNSProvider(nnsServer string, walletFile string, accountAddress string, accountPassword string) (*NNSProvider, error) {
w, err := wallet.NewWalletFromFile(walletFile)
if err != nil {
return nil, fmt.Errorf("retrieve wallet from file: %w", err)
}
var address util.Uint160
if len(accountAddress) == 0 {
address = w.GetChangeAddress()
} else {
address, err = flags.ParseAddress(accountAddress)
if err != nil {
return nil, fmt.Errorf("parse account address: %w", err)
}
}
acc := w.GetAccount(address)
err = acc.Decrypt(accountPassword, w.Scrypt)
if err != nil {
return nil, fmt.Errorf("decrypt account: %w", err)
}
provider := NNSProvider{
nnsServer: nnsServer,
account: acc,
}
return &provider, nil
}
// dial connects to the address of the NNS server.
// If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used, otherwise HTTP.
func (n *NNSProvider) dial() error {
var err error
uri, err := url.Parse(n.nnsServer)
if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") {
// WSOptions not in package `github.com/nspcc-dev/neo-go v0.101.3`
n.client, err = rpcclient.NewWS(context.Background(), n.nnsServer, rpcclient.Options{})
if err != nil {
return fmt.Errorf("create Neo WebSocket client: %w", err)
}
} else {
n.client, err = rpcclient.New(context.Background(), n.nnsServer, rpcclient.Options{})
if err != nil {
return fmt.Errorf("create Neo HTTP client: %w", err)
}
}
if err = n.client.Init(); err != nil {
return fmt.Errorf("initialize Neo client: %w", err)
}
nnsContract, err := n.client.GetContractStateByID(1)
if err != nil {
return fmt.Errorf("get NNS contract state: %w", err)
}
n.nnsContract = nnsContract.Hash
return nil
}
// Close closes connections of multiSchemeClient.
func (n *NNSProvider) close() {
if n.client != nil {
n.client.Close()
}
}
// Present creates a TXT record using the specified parameters to fulfill the nns-01 challenge.
// It implements Provider interface in order to use NNSProvider as Provider.
func (n *NNSProvider) Present(domain, _, keyAuth string) error {
err := n.dial()
if err != nil {
return fmt.Errorf("connect to the NNS server: %w", err)
}
act, err := actor.NewSimple(n.client, n.account)
if err != nil {
return fmt.Errorf("create actor: %w", err)
}
info := GetRecordInfo(domain, keyAuth)
err = n.addTXTRecord(act, info.FQDN, info.Value)
if err != nil {
return fmt.Errorf("add txt record: %w", err)
}
return nil
}
// addTXTRecord adds a new TXT record with the specified data to the provided domain by calling `addRecord` method
// of NNS contract.
func (n *NNSProvider) addTXTRecord(act *actor.Actor, name string, data string) error {
waiter, err := actor.NewPollingWaiter(n.client)
if err != nil {
return fmt.Errorf("waiter creation: %w", err)
}
_, err = waiter.Wait(act.SendCall(n.nnsContract, "addRecord", name, int64(nns.TXT), data))
if err != nil {
return fmt.Errorf("contract invocation: %w", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
// It implements Provider interface in order to use NNSProvider as Provider.
func (n *NNSProvider) CleanUp(domain, _, keyAuth string) error {
defer n.close()
act, err := actor.NewSimple(n.client, n.account)
if err != nil {
return fmt.Errorf("create actor: %w", err)
}
info := GetRecordInfo(domain, keyAuth)
err = n.deleteTXTRecords(act, info.FQDN)
if err != nil {
return fmt.Errorf("delete txt records: %w", err)
}
return nil
}
// deleteTXTRecords removes TXT records from the provided domain by calling `deleteRecords` method of NNS contract.
func (n *NNSProvider) deleteTXTRecords(act *actor.Actor, name string) error {
waiter, err := actor.NewPollingWaiter(n.client)
if err != nil {
return fmt.Errorf("waiter creation: %w", err)
}
_, err = waiter.Wait(act.SendCall(n.nnsContract, "deleteRecords", name, int64(nns.TXT)))
if err != nil {
return fmt.Errorf("contract invocation: %w", err)
}
return nil
}

View file

@ -128,7 +128,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
}
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
// For all valid preSolvers, first submit the challenges, so they have max time to propagate
// For all valid preSolvers, first submit the challenges so they have max time to propagate
for _, authSolver := range authSolvers {
authz := authSolver.authz
if solvr, ok := authSolver.solver.(preSolver); ok {

View file

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

View file

@ -13,6 +13,7 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/nns01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/log"
)
@ -53,6 +54,12 @@ func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.Cha
return nil
}
// SetNNS01Provider specifies a custom provider p that can solve the given NNS-01 challenge.
func (c *SolverManager) SetNNS01Provider(p challenge.Provider) error {
c.solvers[challenge.NNS01] = nns01.NewChallenge(c.core, validate, p)
return nil
}
// Remove removes a challenge type from the available solvers.
func (c *SolverManager) Remove(chlgType challenge.Type) {
delete(c.solvers, chlgType)

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/v4"
"github.com/go-jose/go-jose/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -158,8 +158,7 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error {
return err
}
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
jws, err := jose.ParseSigned(string(reqBody))
if err != nil {
return err
}

View file

@ -40,7 +40,7 @@ func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
}
// Present generates a certificate with an SHA-256 digest of the keyAuth provided
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
if s.port == "" {

View file

@ -24,7 +24,7 @@ func TestChallenge(t *testing.T) {
_, apiURL := tester.SetupFakeAPI(t)
domain := "localhost"
port := "24457"
port := "23457"
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: port},
&ProviderServer{port: "23457"},
)
authz := acme.Authorization{
@ -126,7 +126,7 @@ func TestChallengeIPaddress(t *testing.T) {
_, apiURL := tester.SetupFakeAPI(t)
domain := "127.0.0.1"
port := "24457"
port := "23457"
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.Empty(t, remoteCert.DNSNames, "Expected the challenge certificate to have no DNSNames entry in context of challenge for IP")
assert.Len(t, remoteCert.DNSNames, 0, "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: port},
&ProviderServer{port: "23457"},
)
authz := acme.Authorization{

View file

@ -71,12 +71,12 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
// TODO: move to account struct? Currently MUST pass email.
email := getEmail(ctx)
serverURL, err := url.Parse(ctx.String(flgServer))
serverURL, err := url.Parse(ctx.String("server"))
if err != nil {
log.Fatal(err)
}
rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName)
rootPath := filepath.Join(ctx.String("path"), baseAccountsRootFolderName)
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
accountsPath := filepath.Join(rootPath, serverPath)
rootUserPath := filepath.Join(accountsPath, email)
@ -224,7 +224,7 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
// couldn't load account but got a key. Try to look the account up.
config := lego.NewConfig(&Account{key: privateKey})
config.CADirURL = ctx.String(flgServer)
config.CADirURL = ctx.String("server")
config.UserAgent = getUserAgent(ctx)
client, err := lego.NewClient(config)

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,28 +55,18 @@ 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(flgPFXFormat)
switch pfxFormat {
case "DES", "RC2", "SHA256":
default:
log.Fatalf("Invalid PFX format: %s", pfxFormat)
}
return &CertificatesStorage{
rootPath: filepath.Join(ctx.String(flgPath), baseCertificatesFolderName),
archivePath: filepath.Join(ctx.String(flgPath), baseArchivesFolderName),
pem: ctx.Bool(flgPEM),
pfx: ctx.Bool(flgPFX),
pfxPassword: ctx.String(flgPFXPass),
pfxFormat: pfxFormat,
filename: ctx.String(flgFilename),
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"),
filename: ctx.String("filename"),
}
}
@ -228,9 +218,14 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
return fmt.Errorf("unable to load Certificate for domain %s: %w", domain, err)
}
certChain, err := getCertificateChain(certRes)
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)
if err != nil {
return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err)
return fmt.Errorf("unable to load Issuer Certificate for domain %s: %w", domain, err)
}
keyPemBlock, _ := pem.Decode(certRes.PrivateKey)
@ -256,12 +251,7 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain)
}
encoder, err := getPFXEncoder(s.pfxFormat)
if err != nil {
return fmt.Errorf("PFX encoder: %w", err)
}
pfxBytes, err := encoder.Encode(privateKey, cert, certChain, s.pfxPassword)
pfxBytes, err := pkcs12.Encode(rand.Reader, privateKey, cert, []*x509.Certificate{issuerCert}, s.pfxPassword)
if err != nil {
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
}
@ -295,42 +285,6 @@ 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

@ -6,17 +6,17 @@ import (
)
func Before(ctx *cli.Context) error {
if ctx.String(flgPath) == "" {
log.Fatalf("Could not determine current working directory. Please pass --%s.", flgPath)
if ctx.String("path") == "" {
log.Fatal("Could not determine current working directory. Please pass --path.")
}
err := createNonExistingFolder(ctx.String(flgPath))
err := createNonExistingFolder(ctx.String("path"))
if err != nil {
log.Fatalf("Could not check/create path: %v", err)
}
if ctx.String(flgServer) == "" {
log.Fatalf("Could not determine current working server. Please pass --%s.", flgServer)
if ctx.String("server") == "" {
log.Fatal("Could not determine current working server. Please pass --server.")
}
return nil

View file

@ -9,8 +9,6 @@ import (
"github.com/urfave/cli/v2"
)
const flgCode = "code"
func createDNSHelp() *cli.Command {
return &cli.Command{
Name: "dnshelp",
@ -18,7 +16,7 @@ func createDNSHelp() *cli.Command {
Action: dnsHelp,
Flags: []cli.Flag{
&cli.StringFlag{
Name: flgCode,
Name: "code",
Aliases: []string{"c"},
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
},
@ -27,7 +25,7 @@ func createDNSHelp() *cli.Command {
}
func dnsHelp(ctx *cli.Context) error {
code := ctx.String(flgCode)
code := ctx.String("code")
if code == "" {
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}

View file

@ -12,11 +12,6 @@ import (
"github.com/urfave/cli/v2"
)
const (
flgAccounts = "accounts"
flgNames = "names"
)
func createList() *cli.Command {
return &cli.Command{
Name: "list",
@ -24,18 +19,18 @@ func createList() *cli.Command {
Action: list,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: flgAccounts,
Name: "accounts",
Aliases: []string{"a"},
Usage: "Display accounts.",
},
&cli.BoolFlag{
Name: flgNames,
Name: "names",
Aliases: []string{"n"},
Usage: "Display certificate common names only.",
},
// fake email, needed by NewAccountsStorage
&cli.StringFlag{
Name: flgEmail,
Name: "email",
Value: "unknown",
Hidden: true,
},
@ -44,7 +39,7 @@ func createList() *cli.Command {
}
func list(ctx *cli.Context) error {
if ctx.Bool(flgAccounts) && !ctx.Bool(flgNames) {
if ctx.Bool("accounts") && !ctx.Bool("names") {
if err := listAccount(ctx); err != nil {
return err
}
@ -61,7 +56,7 @@ func listCertificates(ctx *cli.Context) error {
return err
}
names := ctx.Bool(flgNames)
names := ctx.Bool("names")
if len(matches) == 0 {
if !names {
@ -75,7 +70,7 @@ func listCertificates(ctx *cli.Context) error {
}
for _, filename := range matches {
if strings.HasSuffix(filename, issuerExt) {
if strings.HasSuffix(filename, ".issuer.crt") {
continue
}
@ -89,15 +84,10 @@ func listCertificates(ctx *cli.Context) error {
return err
}
name, err := certcrypto.GetCertificateMainDomain(pCert)
if err != nil {
return err
}
if names {
fmt.Println(name)
fmt.Println(pCert.Subject.CommonName)
} else {
fmt.Println(" Certificate Name:", name)
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
fmt.Println(" Expiry Date:", pCert.NotAfter)
fmt.Println(" Certificate Path:", filename)

View file

@ -17,22 +17,11 @@ import (
"github.com/urfave/cli/v2"
)
// Flag names.
const (
flgDays = "days"
flgARIEnable = "ari-enable"
flgARIWaitToRenewDuration = "ari-wait-to-renew-duration"
flgReuseKey = "reuse-key"
flgRenewHook = "renew-hook"
flgNoRandomSleep = "no-random-sleep"
)
const (
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
renewEnvCertPath = "LEGO_CERT_PATH"
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
renewEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH"
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
)
@ -44,68 +33,73 @@ func createRenew() *cli.Command {
Action: renew,
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
hasCsr := ctx.String(flgCSR) != ""
hasDomains := len(ctx.StringSlice("domains")) > 0
hasCsr := len(ctx.String("csr")) > 0
if hasDomains && hasCsr {
log.Fatal("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR)
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
}
if !hasDomains && !hasCsr {
log.Fatal("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR)
log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
}
return nil
},
Flags: []cli.Flag{
&cli.IntFlag{
Name: flgDays,
Name: "days",
Value: 30,
Usage: "The number of days left on a certificate to renew it.",
},
&cli.BoolFlag{
Name: flgARIEnable,
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: flgARIWaitToRenewDuration,
Name: "ari-wait-to-renew-duration",
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
},
&cli.BoolFlag{
Name: flgReuseKey,
Name: "reuse-key",
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
},
&cli.BoolFlag{
Name: flgNoBundle,
Name: "no-bundle",
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: flgMustStaple,
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Name: "not-before",
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
},
&cli.TimestampFlag{
Name: flgNotAfter,
Name: "not-after",
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
},
&cli.StringFlag{
Name: flgPreferredChain,
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: flgAlwaysDeactivateAuthorizations,
Name: "always-deactivate-authorizations",
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
&cli.StringFlag{
Name: flgRenewHook,
Name: "renew-hook",
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
},
&cli.BoolFlag{
Name: flgNoRandomSleep,
Name: "no-random-sleep",
Usage: "Do not add a random sleep before the renewal." +
" We do not recommend using this flag if you are doing your renewals in an automated way.",
},
@ -123,12 +117,12 @@ func renew(ctx *cli.Context) error {
certsStorage := NewCertificatesStorage(ctx)
bundle := !ctx.Bool(flgNoBundle)
bundle := !ctx.Bool("no-bundle")
meta := map[string]string{renewEnvAccountEmail: account.Email}
// CSR
if ctx.IsSet(flgCSR) {
if ctx.IsSet("csr") {
return renewForCSR(ctx, client, certsStorage, bundle, meta)
}
@ -137,13 +131,13 @@ func renew(ctx *cli.Context) error {
}
func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
domains := ctx.StringSlice(flgDomains)
domains := ctx.StringSlice("domains")
domain := domains[0]
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, certExt)
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
if err != nil {
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
}
@ -151,8 +145,12 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
cert := certificates[0]
var ariRenewalTime *time.Time
if ctx.Bool(flgARIEnable) {
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
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)
}
if ariRenewalTime != nil {
now := time.Now().UTC()
// Figure out if we need to sleep before renewing.
@ -163,7 +161,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
}
}
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) {
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) {
return nil
}
@ -174,8 +172,8 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
certDomains := certcrypto.ExtractDomains(cert)
var privateKey crypto.PrivateKey
if ctx.Bool(flgReuseKey) {
keyBytes, errR := certsStorage.ReadFile(domain, keyExt)
if ctx.Bool("reuse-key") {
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
if errR != nil {
log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, errR)
}
@ -188,7 +186,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
// https://github.com/go-acme/lego/issues/1656
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L435-L440
if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool(flgNoRandomSleep) {
if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool("no-random-sleep") {
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472
const jitter = 8 * time.Minute
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
@ -201,19 +199,12 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
request := certificate.ObtainRequest{
Domains: merge(certDomains, domains),
PrivateKey: privateKey,
MustStaple: ctx.Bool(flgMustStaple),
NotBefore: getTime(ctx, flgNotBefore),
NotAfter: getTime(ctx, flgNotAfter),
MustStaple: ctx.Bool("must-staple"),
NotBefore: getTime(ctx, "not-before"),
NotAfter: getTime(ctx, "not-after"),
Bundle: bundle,
PreferredChain: ctx.String(flgPreferredChain),
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
}
if ctx.Bool(flgARIEnable) {
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)
}
PreferredChain: ctx.String("preferred-chain"),
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
certRes, err := client.Certificate.Obtain(request)
@ -223,26 +214,39 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
certsStorage.SaveResource(certRes)
addPathToMetadata(meta, domain, certRes, certsStorage)
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)
}
}
return launchHook(ctx.String(flgRenewHook), meta)
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")
return launchHook(ctx.String("renew-hook"), meta)
}
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
csr, err := readCSRFile(ctx.String(flgCSR))
csr, err := readCSRFile(ctx.String("csr"))
if err != nil {
log.Fatal(err)
}
domain, err := certcrypto.GetCSRMainDomain(csr)
if err != nil {
log.Fatalf("Error: %v", err)
}
domain := csr.Subject.CommonName
// load the cert resource from files.
// We store the certificate, private key and metadata in different files
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, certExt)
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
if err != nil {
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
}
@ -250,8 +254,12 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
cert := certificates[0]
var ariRenewalTime *time.Time
if ctx.Bool(flgARIEnable) {
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
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)
}
if ariRenewalTime != nil {
now := time.Now().UTC()
// Figure out if we need to sleep before renewing.
@ -262,7 +270,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
}
}
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) {
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) {
return nil
}
@ -272,18 +280,11 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: getTime(ctx, flgNotBefore),
NotAfter: getTime(ctx, flgNotAfter),
NotBefore: getTime(ctx, "not-before"),
NotAfter: getTime(ctx, "not-after"),
Bundle: bundle,
PreferredChain: ctx.String(flgPreferredChain),
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
}
if ctx.Bool(flgARIEnable) {
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)
}
PreferredChain: ctx.String("preferred-chain"),
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
certRes, err := client.Certificate.ObtainForCSR(request)
@ -293,9 +294,23 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
certsStorage.SaveResource(certRes)
addPathToMetadata(meta, domain, certRes, certsStorage)
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)
}
}
return launchHook(ctx.String(flgRenewHook), meta)
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
return launchHook(ctx.String("renew-hook"), meta)
}
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
@ -316,24 +331,28 @@ 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 *x509.Certificate, domain string, client *lego.Client) *time.Time {
func getARIRenewalTime(ctx *cli.Context, cert, issuer *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})
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{
Cert: cert,
Issuer: issuer,
HashName: ctx.String("ari-hash-name"),
})
if err != nil {
if errors.Is(err, api.ErrNoARI) {
// The server does not advertise a renewal info endpoint.
log.Warnf("[%s] acme: %v", domain, err)
log.Warnf("[%s] acme: %w", domain, err)
return nil
}
log.Warnf("[%s] acme: calling renewal info endpoint: %v", domain, err)
log.Warnf("[%s] acme: calling renewal info endpoint: %w", domain, err)
return nil
}
now := time.Now().UTC()
renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration(flgARIWaitToRenewDuration))
renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration("ari-wait-to-renew-duration"))
if renewalTime == nil {
log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain)
return nil
@ -347,24 +366,6 @@ func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string,
return renewalTime
}
func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) {
meta[renewEnvCertDomain] = domain
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, certExt)
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, keyExt)
if certRes.IssuerCertificate != nil {
meta[renewEnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, issuerExt)
}
if certsStorage.pem {
meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, pemExt)
}
if certsStorage.pfx {
meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, pfxExt)
}
}
func merge(prevDomains, nextDomains []string) []string {
for _, next := range nextDomains {
var found bool

View file

@ -48,6 +48,7 @@ func Test_merge(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -107,6 +108,7 @@ 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

@ -6,12 +6,6 @@ import (
"github.com/urfave/cli/v2"
)
// Flag names.
const (
flgKeep = "keep"
flgReason = "reason"
)
func createRevoke() *cli.Command {
return &cli.Command{
Name: "revoke",
@ -19,12 +13,12 @@ func createRevoke() *cli.Command {
Action: revoke,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: flgKeep,
Name: "keep",
Aliases: []string{"k"},
Usage: "Keep the certificates after the revocation instead of archiving them.",
},
&cli.UintFlag{
Name: flgReason,
Name: "reason",
Usage: "Identifies the reason for the certificate revocation." +
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
" Valid values are:" +
@ -47,15 +41,15 @@ func revoke(ctx *cli.Context) error {
certsStorage := NewCertificatesStorage(ctx)
certsStorage.CreateRootFolder()
for _, domain := range ctx.StringSlice(flgDomains) {
for _, domain := range ctx.StringSlice("domains") {
log.Printf("Trying to revoke certificate for domain %s", domain)
certBytes, err := certsStorage.ReadFile(domain, certExt)
certBytes, err := certsStorage.ReadFile(domain, ".crt")
if err != nil {
log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
}
reason := ctx.Uint(flgReason)
reason := ctx.Uint("reason")
err = client.Certificate.RevokeWithReason(certBytes, &reason)
if err != nil {
@ -64,7 +58,7 @@ func revoke(ctx *cli.Context) error {
log.Println("Certificate was revoked.")
if ctx.Bool(flgKeep) {
if ctx.Bool("keep") {
return nil
}

View file

@ -14,25 +14,14 @@ import (
"github.com/urfave/cli/v2"
)
// Flag names.
const (
flgNoBundle = "no-bundle"
flgMustStaple = "must-staple"
flgNotBefore = "not-before"
flgNotAfter = "not-after"
flgPreferredChain = "preferred-chain"
flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations"
flgRunHook = "run-hook"
)
func createRun() *cli.Command {
return &cli.Command{
Name: "run",
Usage: "Register an account, then create and install a certificate",
Before: func(ctx *cli.Context) error {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
hasCsr := ctx.String(flgCSR) != ""
hasDomains := len(ctx.StringSlice("domains")) > 0
hasCsr := len(ctx.String("csr")) > 0
if hasDomains && hasCsr {
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
}
@ -44,35 +33,35 @@ func createRun() *cli.Command {
Action: run,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: flgNoBundle,
Name: "no-bundle",
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: flgMustStaple,
Name: "must-staple",
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Name: "not-before",
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
},
&cli.TimestampFlag{
Name: flgNotAfter,
Name: "not-after",
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
},
&cli.StringFlag{
Name: flgPreferredChain,
Name: "preferred-chain",
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: flgAlwaysDeactivateAuthorizations,
Name: "always-deactivate-authorizations",
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
&cli.StringFlag{
Name: flgRunHook,
Name: "run-hook",
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
},
},
@ -124,16 +113,19 @@ func run(ctx *cli.Context) error {
meta := map[string]string{
renewEnvAccountEmail: account.Email,
renewEnvCertDomain: cert.Domain,
renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"),
renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"),
renewEnvCertPEMPath: certsStorage.GetFileName(cert.Domain, ".pem"),
renewEnvCertPFXPath: certsStorage.GetFileName(cert.Domain, ".pfx"),
}
addPathToMetadata(meta, cert.Domain, cert, certsStorage)
return launchHook(ctx.String(flgRunHook), meta)
return launchHook(ctx.String("run-hook"), meta)
}
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
// Check for a global accept override
if ctx.Bool(flgAcceptTOS) {
if ctx.Bool("accept-tos") {
return true
}
@ -165,12 +157,12 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er
log.Fatal("You did not accept the TOS. Unable to proceed.")
}
if ctx.Bool(flgEAB) {
kid := ctx.String(flgKID)
hmacEncoded := ctx.String(flgHMAC)
if ctx.Bool("eab") {
kid := ctx.String("kid")
hmacEncoded := ctx.String("hmac")
if kid == "" || hmacEncoded == "" {
log.Fatalf("Requires arguments --%s and --%s.", flgKID, flgHMAC)
log.Fatalf("Requires arguments --kid and --hmac.")
}
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
@ -184,25 +176,25 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er
}
func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
bundle := !ctx.Bool(flgNoBundle)
bundle := !ctx.Bool("no-bundle")
domains := ctx.StringSlice(flgDomains)
domains := ctx.StringSlice("domains")
if len(domains) > 0 {
// obtain a certificate, generating a new private key
request := certificate.ObtainRequest{
Domains: domains,
Bundle: bundle,
MustStaple: ctx.Bool(flgMustStaple),
PreferredChain: ctx.String(flgPreferredChain),
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
MustStaple: ctx.Bool("must-staple"),
PreferredChain: ctx.String("preferred-chain"),
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
notBefore := ctx.Timestamp(flgNotBefore)
notBefore := ctx.Timestamp("not-before")
if notBefore != nil {
request.NotBefore = *notBefore
}
notAfter := ctx.Timestamp(flgNotAfter)
notAfter := ctx.Timestamp("not-after")
if notAfter != nil {
request.NotAfter = *notAfter
}
@ -211,7 +203,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
}
// read the CSR
csr, err := readCSRFile(ctx.String(flgCSR))
csr, err := readCSRFile(ctx.String("csr"))
if err != nil {
return nil, err
}
@ -219,11 +211,11 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
// obtain a certificate for this CSR
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: getTime(ctx, flgNotBefore),
NotAfter: getTime(ctx, flgNotAfter),
NotBefore: getTime(ctx, "not-before"),
NotAfter: getTime(ctx, "not-after"),
Bundle: bundle,
PreferredChain: ctx.String(flgPreferredChain),
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
PreferredChain: ctx.String("preferred-chain"),
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
}
return client.Certificate.ObtainForCSR(request)

View file

@ -1,249 +1,162 @@
package cmd
import (
"fmt"
"time"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/urfave/cli/v2"
"software.sslmate.com/src/go-pkcs12"
)
// Flag names.
const (
flgDomains = "domains"
flgServer = "server"
flgAcceptTOS = "accept-tos"
flgEmail = "email"
flgCSR = "csr"
flgEAB = "eab"
flgKID = "kid"
flgHMAC = "hmac"
flgKeyType = "key-type"
flgFilename = "filename"
flgPath = "path"
flgHTTP = "http"
flgHTTPPort = "http.port"
flgHTTPProxyHeader = "http.proxy-header"
flgHTTPWebroot = "http.webroot"
flgHTTPMemcachedHost = "http.memcached-host"
flgHTTPS3Bucket = "http.s3-bucket"
flgHTTPFrostFSEndpoint = "http.frostfs-endpoint"
flgHTTPFrostFSContainer = "http.frostfs-container"
flgHTTPFrostFSWallet = "http.frostfs-wallet"
flgHTTPFrostFSWalletAccount = "http.frostfs-wallet-account"
flgHTTPFrostFSWalletPass = "http.frostfs-wallet-password"
flgTLS = "tls"
flgTLSPort = "tls.port"
flgDNS = "dns"
flgDNSDisableCP = "dns.disable-cp"
flgDNSPropagationWait = "dns.propagation-wait"
flgDNSPropagationDisableANS = "dns.propagation-disable-ans"
flgDNSPropagationRNS = "dns.propagation-rns"
flgDNSResolvers = "dns.resolvers"
flgHTTPTimeout = "http-timeout"
flgDNSTimeout = "dns-timeout"
flgPEM = "pem"
flgPFX = "pfx"
flgPFXPass = "pfx.pass"
flgPFXFormat = "pfx.format"
flgCertTimeout = "cert.timeout"
flgOverallRequestLimit = "overall-request-limit"
flgUserAgent = "user-agent"
)
func CreateFlags(defaultPath string) []cli.Flag {
return []cli.Flag{
&cli.StringSliceFlag{
Name: flgDomains,
Name: "domains",
Aliases: []string{"d"},
Usage: "Add a domain to the process. Can be specified multiple times.",
},
&cli.StringFlag{
Name: flgServer,
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,
},
&cli.BoolFlag{
Name: flgAcceptTOS,
Name: "accept-tos",
Aliases: []string{"a"},
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
},
&cli.StringFlag{
Name: flgEmail,
Name: "email",
Aliases: []string{"m"},
Usage: "Email used for registration and recovery contact.",
},
&cli.StringFlag{
Name: flgCSR,
Name: "csr",
Aliases: []string{"c"},
Usage: "Certificate signing request filename, if an external CSR is to be used.",
},
&cli.BoolFlag{
Name: flgEAB,
EnvVars: []string{"LEGO_EAB"},
Name: "eab",
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
},
&cli.StringFlag{
Name: flgKID,
EnvVars: []string{"LEGO_EAB_KID"},
Name: "kid",
Usage: "Key identifier from External CA. Used for External Account Binding.",
},
&cli.StringFlag{
Name: flgHMAC,
EnvVars: []string{"LEGO_EAB_HMAC"},
Name: "hmac",
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
},
&cli.StringFlag{
Name: flgKeyType,
Name: "key-type",
Aliases: []string{"k"},
Value: "ec256",
Usage: "Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384.",
},
&cli.StringFlag{
Name: flgFilename,
Name: "filename",
Usage: "(deprecated) Filename of the generated certificate.",
},
&cli.StringFlag{
Name: flgPath,
Name: "path",
EnvVars: []string{"LEGO_PATH"},
Usage: "Directory to use for storing the data.",
Value: defaultPath,
},
&cli.BoolFlag{
Name: flgHTTP,
Name: "http",
Usage: "Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.",
},
&cli.StringFlag{
Name: flgHTTPPort,
Name: "http.port",
Usage: "Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port.",
Value: ":80",
},
&cli.StringFlag{
Name: flgHTTPProxyHeader,
Name: "http.proxy-header",
Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.",
Value: "Host",
},
&cli.StringFlag{
Name: flgHTTPWebroot,
Name: "http.webroot",
Usage: "Set the webroot folder to use for HTTP-01 based challenges to write directly to the .well-known/acme-challenge file." +
" This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
},
&cli.StringSliceFlag{
Name: flgHTTPMemcachedHost,
Name: "http.memcached-host",
Usage: "Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.",
},
&cli.StringFlag{
Name: flgHTTPS3Bucket,
Usage: "Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.",
},
&cli.StringFlag{
Name: flgHTTPFrostFSEndpoint,
Usage: "Set FrostFS endpoint to use for HTTP-01 based challenges. Challenges will be written to FrostFS container",
EnvVars: []string{"FROSTFS_ENDPOINT"},
},
&cli.StringFlag{
Name: flgHTTPFrostFSContainer,
Usage: "Set FrostFS container ID to use for HTTP-01 based challenges. Challenges will be written to FrostFS container",
EnvVars: []string{"FROSTFS_CONTAINER"},
},
&cli.StringFlag{
Name: flgHTTPFrostFSWallet,
Usage: "Path to NEO wallet to use for interaction with FrostFS. If no wallet is provided an ephemeral one will be generated. Such key will only work for publicly writable containers and will significantly reduce security: any attacker with knowledge of FrostFS endpoint and CID will be able to obtain certificates for your domain",
EnvVars: []string{"FROSTFS_WALLET"},
},
&cli.StringFlag{
Name: flgHTTPFrostFSWalletAccount,
Usage: "Wallet account to use for interaction with FrostFS. If not set, the first account from wallet will be used",
EnvVars: []string{"FROSTFS_WALLET_ACCOUNT"},
},
&cli.StringFlag{
Name: flgHTTPFrostFSWalletPass,
Usage: "Account password to decrypt the wallet. If not set, an empty password is assumed",
EnvVars: []string{"FROSTFS_WALLET_PASSWORD"},
},
&cli.BoolFlag{
Name: flgTLS,
Name: "tls",
Usage: "Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.",
},
&cli.StringFlag{
Name: flgTLSPort,
Name: "tls.port",
Usage: "Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port.",
Value: ":443",
},
&cli.StringFlag{
Name: flgDNS,
Name: "dns",
Usage: "Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.",
},
&cli.BoolFlag{
Name: flgDNSDisableCP,
Usage: fmt.Sprintf("(deprecated) use %s instead.", flgDNSPropagationDisableANS),
},
&cli.BoolFlag{
Name: flgDNSPropagationDisableANS,
Name: "dns.disable-cp",
Usage: "By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.",
},
&cli.BoolFlag{
Name: flgDNSPropagationRNS,
Usage: "By setting this flag to true, use all the recursive nameservers to check the propagation of the TXT record.",
},
&cli.DurationFlag{
Name: flgDNSPropagationWait,
Usage: "By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead.",
},
&cli.StringSliceFlag{
Name: flgDNSResolvers,
Name: "dns.resolvers",
Usage: "Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination." +
" For DNS-01 challenge verification, the authoritative DNS server is queried directly." +
" Supported: host:port." +
" The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
},
&cli.IntFlag{
Name: flgHTTPTimeout,
Name: "http-timeout",
Usage: "Set the HTTP timeout value to a specific value in seconds.",
},
&cli.IntFlag{
Name: flgDNSTimeout,
Name: "dns-timeout",
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries.",
Value: 10,
},
&cli.BoolFlag{
Name: flgPEM,
Name: "pem",
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
},
&cli.BoolFlag{
Name: flgPFX,
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: flgPFXPass,
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: flgPFXFormat,
Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.",
Value: "RC2",
EnvVars: []string{"LEGO_PFX_FORMAT"},
},
&cli.IntFlag{
Name: flgCertTimeout,
Name: "cert.timeout",
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
Value: 30,
},
&cli.IntFlag{
Name: flgOverallRequestLimit,
Usage: "ACME overall requests limit.",
Value: certificate.DefaultOverallRequestLimit,
&cli.StringFlag{
Name: "user-agent",
Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli",
},
&cli.StringFlag{
Name: flgUserAgent,
Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli",
Name: "nns",
Usage: "Solve a NNS-01 challenge using the specified URL of NNS server.",
},
&cli.StringFlag{
Name: "wallet",
Usage: "Path to wallet file for NNS provider.",
},
&cli.StringFlag{
Name: "wallet.account-address",
Usage: "Address of account to use from wallet file.",
},
&cli.StringFlag{
Name: "wallet.password",
Usage: "Password to account from wallet file.",
},
}
}

View file

@ -35,17 +35,16 @@ func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
config := lego.NewConfig(acc)
config.CADirURL = ctx.String(flgServer)
config.CADirURL = ctx.String("server")
config.Certificate = lego.CertificateConfig{
KeyType: keyType,
Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second,
OverallRequestLimit: ctx.Int(flgOverallRequestLimit),
Timeout: time.Duration(ctx.Int("cert.timeout")) * time.Second,
}
config.UserAgent = getUserAgent(ctx)
if ctx.IsSet(flgHTTPTimeout) {
config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second
if ctx.IsSet("http-timeout") {
config.HTTPClient.Timeout = time.Duration(ctx.Int("http-timeout")) * time.Second
}
client, err := lego.NewClient(config)
@ -53,8 +52,8 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
log.Fatalf("Could not create client: %v", err)
}
if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) {
log.Fatalf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC)
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") {
log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
}
return client
@ -62,7 +61,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
// getKeyType the type from which private keys should be generated.
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
keyType := ctx.String(flgKeyType)
keyType := ctx.String("key-type")
switch strings.ToUpper(keyType) {
case "RSA2048":
return certcrypto.RSA2048
@ -83,15 +82,15 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType {
}
func getEmail(ctx *cli.Context) string {
email := ctx.String(flgEmail)
email := ctx.String("email")
if email == "" {
log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail)
log.Fatal("You have to pass an account (email address) to the program using --email or -m")
}
return email
}
func getUserAgent(ctx *cli.Context) string {
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version))
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String("user-agent"), ctx.App.Version))
}
func createNonExistingFolder(path string) error {

View file

@ -1,7 +1,6 @@
package cmd
import (
"fmt"
"net"
"strings"
"time"
@ -9,81 +8,62 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/nns01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/log"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/providers/http/frostfs"
"github.com/go-acme/lego/v4/providers/http/memcached"
"github.com/go-acme/lego/v4/providers/http/s3"
"github.com/go-acme/lego/v4/providers/http/webroot"
"github.com/urfave/cli/v2"
)
func setupChallenges(ctx *cli.Context, client *lego.Client) {
if !ctx.Bool(flgHTTP) && !ctx.Bool(flgTLS) && !ctx.IsSet(flgDNS) {
log.Fatalf("No challenge selected. You must specify at least one challenge: `--%s`, `--%s`, `--%s`.", flgHTTP, flgTLS, flgDNS)
if !ctx.Bool("http") && !ctx.Bool("tls") && !ctx.IsSet("dns") && !ctx.IsSet("nns") {
log.Fatal("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`, `--nns`.")
}
if ctx.Bool(flgHTTP) {
if ctx.Bool("http") {
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
if err != nil {
log.Fatal(err)
}
}
if ctx.Bool(flgTLS) {
if ctx.Bool("tls") {
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
if err != nil {
log.Fatal(err)
}
}
if ctx.IsSet(flgDNS) {
err := setupDNS(ctx, client)
if err != nil {
log.Fatal(err)
if ctx.IsSet("dns") {
setupDNS(ctx, client)
}
if ctx.IsSet("nns") {
setupNNS(ctx, client)
}
}
//nolint:gocyclo // the complexity is expected.
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
switch {
case ctx.IsSet(flgHTTPWebroot):
ps, err := webroot.NewHTTPProvider(ctx.String(flgHTTPWebroot))
case ctx.IsSet("http.webroot"):
ps, err := webroot.NewHTTPProvider(ctx.String("http.webroot"))
if err != nil {
log.Fatal(err)
}
return ps
case ctx.IsSet(flgHTTPMemcachedHost):
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost))
case ctx.IsSet("http.memcached-host"):
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice("http.memcached-host"))
if err != nil {
log.Fatal(err)
}
return ps
case ctx.IsSet(flgHTTPS3Bucket):
ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket))
if err != nil {
log.Fatal(err)
}
return ps
case ctx.IsSet(flgHTTPFrostFSEndpoint):
ps, err := frostfs.NewHTTPProvider(
ctx.String(flgHTTPFrostFSEndpoint),
ctx.String(flgHTTPFrostFSContainer),
ctx.String(flgHTTPFrostFSWallet),
ctx.String(flgHTTPFrostFSWalletAccount),
ctx.String(flgHTTPFrostFSWalletPass),
)
if err != nil {
log.Fatal(err)
}
return ps
case ctx.IsSet(flgHTTPPort):
iface := ctx.String(flgHTTPPort)
case ctx.IsSet("http.port"):
iface := ctx.String("http.port")
if !strings.Contains(iface, ":") {
log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgHTTPPort)
log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
}
host, port, err := net.SplitHostPort(iface)
@ -92,13 +72,13 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
}
srv := http01.NewProviderServer(host, port)
if header := ctx.String(flgHTTPProxyHeader); header != "" {
if header := ctx.String("http.proxy-header"); header != "" {
srv.SetProxyHeader(header)
}
return srv
case ctx.Bool(flgHTTP):
case ctx.Bool("http"):
srv := http01.NewProviderServer("", "")
if header := ctx.String(flgHTTPProxyHeader); header != "" {
if header := ctx.String("http.proxy-header"); header != "" {
srv.SetProxyHeader(header)
}
return srv
@ -110,10 +90,10 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
switch {
case ctx.IsSet(flgTLSPort):
iface := ctx.String(flgTLSPort)
case ctx.IsSet("tls.port"):
iface := ctx.String("tls.port")
if !strings.Contains(iface, ":") {
log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgTLSPort)
log.Fatalf("The --tls switch only accepts interface:port or :port for its argument.")
}
host, port, err := net.SplitHostPort(iface)
@ -122,7 +102,7 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
}
return tlsalpn01.NewProviderServer(host, port)
case ctx.Bool(flgTLS):
case ctx.Bool("tls"):
return tlsalpn01.NewProviderServer("", "")
default:
log.Fatal("Invalid HTTP challenge options.")
@ -130,62 +110,46 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
}
}
func setupDNS(ctx *cli.Context, client *lego.Client) error {
err := checkPropagationExclusiveOptions(ctx)
func setupDNS(ctx *cli.Context, client *lego.Client) {
provider, err := dns.NewDNSChallengeProviderByName(ctx.String("dns"))
if err != nil {
return err
log.Fatal(err)
}
wait := ctx.Duration(flgDNSPropagationWait)
if wait < 0 {
return fmt.Errorf("'%s' cannot be negative", flgDNSPropagationWait)
}
provider, err := dns.NewDNSChallengeProviderByName(ctx.String(flgDNS))
if err != nil {
return err
}
servers := ctx.StringSlice(flgDNSResolvers)
servers := ctx.StringSlice("dns.resolvers")
err = client.Challenge.SetDNS01Provider(provider,
dns01.CondOption(len(servers) > 0,
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice(flgDNSResolvers)))),
dns01.CondOption(ctx.Bool(flgDNSDisableCP) || ctx.Bool(flgDNSPropagationDisableANS),
dns01.DisableAuthoritativeNssPropagationRequirement()),
dns01.CondOption(ctx.Duration(flgDNSPropagationWait) > 0,
// TODO(ldez): inside the next major version we will use flgDNSDisableCP here.
// This will change the meaning of this flag to really disable all propagation checks.
dns01.PropagationWait(wait, true)),
dns01.CondOption(ctx.Bool(flgDNSPropagationRNS),
dns01.RecursiveNSsPropagationRequirement()),
dns01.CondOption(ctx.IsSet(flgDNSTimeout),
dns01.AddDNSTimeout(time.Duration(ctx.Int(flgDNSTimeout))*time.Second)),
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice("dns.resolvers")))),
dns01.CondOption(ctx.Bool("dns.disable-cp"),
dns01.DisableCompletePropagationRequirement()),
dns01.CondOption(ctx.IsSet("dns-timeout"),
dns01.AddDNSTimeout(time.Duration(ctx.Int("dns-timeout"))*time.Second)),
)
return err
if err != nil {
log.Fatal(err)
}
}
func checkPropagationExclusiveOptions(ctx *cli.Context) error {
if ctx.IsSet(flgDNSDisableCP) {
log.Println("The flag '%s' is deprecated use '%s' instead.", flgDNSDisableCP, flgDNSPropagationDisableANS)
func setupNNS(ctx *cli.Context, client *lego.Client) {
switch {
case !ctx.IsSet("wallet"):
log.Fatal("No wallet file provided for nns challenge.")
case !ctx.IsSet("wallet.password"):
log.Fatal("No password to account from wallet file provided.")
}
if (isSetBool(ctx, flgDNSDisableCP) || isSetBool(ctx, flgDNSPropagationDisableANS)) && ctx.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationDisableANS, flgDNSPropagationWait)
nnsServer := ctx.String("nns")
wallet := ctx.String("wallet")
accAddress := ctx.String("wallet.account-address")
accPassword := ctx.String("wallet.password")
provider, err := nns01.NewNNSProvider(nnsServer, wallet, accAddress, accPassword)
if err != nil {
log.Fatal(err)
}
if isSetBool(ctx, flgDNSPropagationRNS) && ctx.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationRNS, flgDNSPropagationWait)
err = client.Challenge.SetNNS01Provider(provider)
if err != nil {
log.Fatal(err)
}
return nil
}
func isSetBool(ctx *cli.Context, name string) bool {
return ctx.IsSet(name) && ctx.Bool(name)
}

View file

@ -1,7 +1,8 @@
// Code generated by 'make generate-dns'; DO NOT EDIT.
package cmd
// CODE GENERATED AUTOMATICALLY
// THIS FILE MUST NOT BE EDITED BY HAND
import (
"fmt"
"io"
@ -30,16 +31,13 @@ func allDNSCodes() string {
"clouddns",
"cloudflare",
"cloudns",
"cloudru",
"cloudxns",
"conoha",
"constellix",
"cpanel",
"derak",
"desec",
"designate",
"digitalocean",
"directadmin",
"dnshomede",
"dnsimple",
"dnsmadeeasy",
@ -67,9 +65,7 @@ func allDNSCodes() string {
"hetzner",
"hostingde",
"hosttech",
"httpnet",
"httpreq",
"huaweicloud",
"hurricane",
"hyperone",
"ibmcloud",
@ -85,15 +81,11 @@ func allDNSCodes() string {
"joker",
"liara",
"lightsail",
"limacity",
"linode",
"liquidweb",
"loopia",
"luadns",
"mailinabox",
"metaname",
"mijnhost",
"mittwald",
"mydnsjp",
"mythicbeasts",
"namecheap",
@ -123,10 +115,7 @@ func allDNSCodes() string {
"sakuracloud",
"scaleway",
"selectel",
"selectelv2",
"selfhostde",
"servercow",
"shellrent",
"simply",
"sonic",
"stackpath",
@ -141,11 +130,9 @@ func allDNSCodes() string {
"vkcloud",
"vscale",
"vultr",
"webnames",
"websupport",
"wedos",
"yandex",
"yandex360",
"yandexcloud",
"zoneee",
"zonomi",
@ -313,26 +300,22 @@ func displayDNSHelp(w io.Writer, name string) error {
// generated from: providers/dns/azuredns/azuredns.toml
ew.writeln(`Configuration for AzureDNS.`)
ew.writeln(`Code: 'azuredns'`)
ew.writeln(`Since: 'v4.13.0'`)
ew.writeln(`Since: 'v0.1.0'`)
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`)
@ -377,7 +360,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "BLUECAT_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "BLUECAT_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "BLUECAT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "BLUECAT_SKIP_DEPLOY": Skip deployements`)
ew.writeln(` - "BLUECAT_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
@ -504,10 +486,10 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout (in seconds)`)
ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check (in seconds)`)
ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation (in seconds)`)
ew.writeln(` - "CLOUDFLARE_TTL": The TTL of the TXT record used for the DNS challenge (in seconds)`)
ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "CLOUDFLARE_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/cloudflare`)
@ -534,29 +516,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudns`)
case "cloudru":
// generated from: providers/dns/cloudru/cloudru.toml
ew.writeln(`Configuration for Cloud.ru.`)
ew.writeln(`Code: 'cloudru'`)
ew.writeln(`Since: 'v4.14.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "CLOUDRU_KEY_ID": Key ID (login)`)
ew.writeln(` - "CLOUDRU_SECRET": Key Secret`)
ew.writeln(` - "CLOUDRU_SERVICE_INSTANCE_ID": Service Instance ID (parentId)`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "CLOUDRU_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "CLOUDRU_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "CLOUDRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "CLOUDRU_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "CLOUDRU_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudru`)
case "cloudxns":
// generated from: providers/dns/cloudxns/cloudxns.toml
ew.writeln(`Configuration for CloudXNS.`)
@ -622,30 +581,6 @@ 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.`)
@ -710,7 +645,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "DESIGNATE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "DESIGNATE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "DESIGNATE_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "DESIGNATE_ZONE_NAME": The zone name to use in the OpenStack Project to manage TXT records.`)
ew.writeln(` - "OS_PROJECT_ID": Project ID`)
ew.writeln(` - "OS_TENANT_NAME": Tenant name (deprecated see OS_PROJECT_NAME and OS_PROJECT_ID)`)
@ -738,29 +672,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/digitalocean`)
case "directadmin":
// generated from: providers/dns/directadmin/directadmin.toml
ew.writeln(`Configuration for DirectAdmin.`)
ew.writeln(`Code: 'directadmin'`)
ew.writeln(`Since: 'v4.18.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "DIRECTADMIN_API_URL": URL of the API`)
ew.writeln(` - "DIRECTADMIN_PASSWORD": API password`)
ew.writeln(` - "DIRECTADMIN_USERNAME": API username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "DIRECTADMIN_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "DIRECTADMIN_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "DIRECTADMIN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "DIRECTADMIN_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "DIRECTADMIN_ZONE_NAME": Zone name used to add the TXT record`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/directadmin`)
case "dnshomede":
// generated from: providers/dns/dnshomede/dnshomede.toml
ew.writeln(`Configuration for dnsHome.de.`)
@ -772,12 +683,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "DNSHOMEDE_CREDENTIALS": Comma-separated list of domain:password credential pairs`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "DNSHOMEDE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "DNSHOMEDE_POLLING_INTERVAL": Time between DNS propagation checks`)
ew.writeln(` - "DNSHOMEDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)`)
ew.writeln(` - "DNSHOMEDE_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnshomede`)
@ -1030,7 +935,6 @@ 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`)
@ -1082,6 +986,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "EXOSCALE_API_ZONE": API zone`)
ew.writeln(` - "EXOSCALE_ENDPOINT": API endpoint URL`)
ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "EXOSCALE_POLLING_INTERVAL": Time between DNS propagation check`)
@ -1140,8 +1045,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "GANDIV5_API_KEY": API key (Deprecated)`)
ew.writeln(` - "GANDIV5_PERSONAL_ACCESS_TOKEN": Personal Access Token`)
ew.writeln(` - "GANDIV5_API_KEY": API key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
@ -1172,7 +1076,6 @@ 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`)
@ -1320,27 +1223,6 @@ 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.`)
@ -1363,28 +1245,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/httpreq`)
case "huaweicloud":
// generated from: providers/dns/huaweicloud/huaweicloud.toml
ew.writeln(`Configuration for Huawei Cloud.`)
ew.writeln(`Code: 'huaweicloud'`)
ew.writeln(`Since: 'v4.19'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "HUAWEICLOUD_ACCESS_KEY_ID": Access key ID`)
ew.writeln(` - "HUAWEICLOUD_REGION": Region`)
ew.writeln(` - "HUAWEICLOUD_SECRET_ACCESS_KEY": Access Key secret`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "HUAWEICLOUD_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "HUAWEICLOUD_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "HUAWEICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "HUAWEICLOUD_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/huaweicloud`)
case "hurricane":
// generated from: providers/dns/hurricane/hurricane.toml
ew.writeln(`Configuration for Hurricane Electric DNS.`)
@ -1396,12 +1256,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(` - "HURRICANE_TOKENS": TXT record names and tokens`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "HURRICANE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "HURRICANE_POLLING_INTERVAL": Time between DNS propagation checks`)
ew.writeln(` - "HURRICANE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)`)
ew.writeln(` - "HURRICANE_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hurricane`)
@ -1611,6 +1465,7 @@ 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()
@ -1702,27 +1557,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/lightsail`)
case "limacity":
// generated from: providers/dns/limacity/limacity.toml
ew.writeln(`Configuration for Lima-City.`)
ew.writeln(`Code: 'limacity'`)
ew.writeln(`Since: 'v4.18.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "LIMACITY_API_KEY": The API key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "LIMACITY_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "LIMACITY_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "LIMACITY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "LIMACITY_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "LIMACITY_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/limacity`)
case "linode":
// generated from: providers/dns/linode/linode.toml
ew.writeln(`Configuration for Linode (v4).`)
@ -1751,17 +1585,17 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "LWAPI_PASSWORD": Liquid Web API Password`)
ew.writeln(` - "LWAPI_USERNAME": Liquid Web API Username`)
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()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "LWAPI_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
ew.writeln(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "LWAPI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "LWAPI_URL": Liquid Web API endpoint`)
ew.writeln(` - "LWAPI_ZONE": DNS Zone`)
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()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`)
@ -1809,26 +1643,6 @@ 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.`)
@ -1849,48 +1663,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaname`)
case "mijnhost":
// generated from: providers/dns/mijnhost/mijnhost.toml
ew.writeln(`Configuration for mijn.host.`)
ew.writeln(`Code: 'mijnhost'`)
ew.writeln(`Since: 'v4.18.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "MIJNHOST_API_KEY": The API key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "MIJNHOST_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "MIJNHOST_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "MIJNHOST_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "MIJNHOST_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "MIJNHOST_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/mijnhost`)
case "mittwald":
// generated from: providers/dns/mittwald/mittwald.toml
ew.writeln(`Configuration for Mittwald.`)
ew.writeln(`Code: 'mittwald'`)
ew.writeln(`Since: 'v1.48.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "MITTWALD_TOKEN": API token`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "MITTWALD_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "MITTWALD_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "MITTWALD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "MITTWALD_SEQUENCE_INTERVAL": Time between sequential requests`)
ew.writeln(` - "MITTWALD_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/mittwald`)
case "mydnsjp":
// generated from: providers/dns/mydnsjp/mydnsjp.toml
ew.writeln(`Configuration for MyDNS.jp.`)
@ -2211,7 +1983,6 @@ 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()
@ -2225,12 +1996,9 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "OVH_ACCESS_TOKEN": Access token`)
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_APPLICATION_KEY": Application key`)
ew.writeln(` - "OVH_APPLICATION_SECRET": Application secret`)
ew.writeln(` - "OVH_CONSUMER_KEY": Consumer key`)
ew.writeln(` - "OVH_ENDPOINT": Endpoint URL (ovh-eu or ovh-ca)`)
ew.writeln()
@ -2256,7 +2024,6 @@ 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`)
@ -2366,8 +2133,6 @@ 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()
@ -2433,7 +2198,6 @@ 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:`)
@ -2495,15 +2259,14 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "SCW_PROJECT_ID": Project to use (optional)`)
ew.writeln(` - "SCW_SECRET_KEY": Secret key`)
ew.writeln(` - "SCALEWAY_API_TOKEN": API token`)
ew.writeln(` - "SCALEWAY_PROJECT_ID": Project to use (optional)`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SCW_ACCESS_KEY": Access key`)
ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln(` - "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()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/scaleway`)
@ -2529,52 +2292,6 @@ 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 "selfhostde":
// generated from: providers/dns/selfhostde/selfhostde.toml
ew.writeln(`Configuration for SelfHost.(de|eu).`)
ew.writeln(`Code: 'selfhostde'`)
ew.writeln(`Since: 'v4.19.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "SELFHOSTDE_PASSWORD": Password`)
ew.writeln(` - "SELFHOSTDE_RECORDS_MAPPING": Record IDs mapping with domains (ex: example.com:123:456,example.org:789,foo.example.com:147)`)
ew.writeln(` - "SELFHOSTDE_USERNAME": Username`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "SELFHOSTDE_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "SELFHOSTDE_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "SELFHOSTDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "SELFHOSTDE_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selfhostde`)
case "servercow":
// generated from: providers/dns/servercow/servercow.toml
ew.writeln(`Configuration for Servercow.`)
@ -2596,27 +2313,6 @@ 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.`)
@ -2917,26 +2613,6 @@ 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.`)
@ -3000,27 +2676,6 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex`)
case "yandex360":
// generated from: providers/dns/yandex360/yandex360.toml
ew.writeln(`Configuration for Yandex 360.`)
ew.writeln(`Code: 'yandex360'`)
ew.writeln(`Since: 'v4.14.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "YANDEX360_OAUTH_TOKEN": The OAuth Token`)
ew.writeln(` - "YANDEX360_ORG_ID": The organization ID`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "YANDEX360_HTTP_TIMEOUT": API request timeout`)
ew.writeln(` - "YANDEX360_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "YANDEX360_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "YANDEX360_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex360`)
case "yandexcloud":
// generated from: providers/dns/yandexcloud/yandexcloud.toml
ew.writeln(`Configuration for Yandex Cloud.`)
@ -3030,7 +2685,7 @@ func displayDNSHelp(w io.Writer, name string) error {
ew.writeln(`Credentials:`)
ew.writeln(` - "YANDEX_CLOUD_FOLDER_ID": The string id of folder (aka project) in Yandex Cloud`)
ew.writeln(` - "YANDEX_CLOUD_IAM_TOKEN": The base64 encoded json which contains information about iam token of service account with 'dns.admin' permissions`)
ew.writeln(` - "YANDEX_CLOUD_IAM_TOKEN": The base64 encoded json which contains inforamtion about iam token of serivce account with 'dns.admin' permissions`)
ew.writeln()
ew.writeln(`Additional Configuration:`)

View file

@ -1,14 +1,20 @@
.PHONY: default clean hugo hugo-build
default: clean hugo
default: hugo
clean:
rm -rf public/
hugo-build: clean
hugo-build: clean hugo-themes
hugo --enableGitInfo --source .
hugo:
hugo server --disableFastRender --enableGitInfo --watch --source .
# hugo server -D
hugo-themes:
rm -rf themes
mkdir themes
git clone --depth=1 https://github.com/matcornic/hugo-theme-learn.git themes/hugo-theme-learn
rm -rf themes/hugo-theme-learn/.git

View file

@ -2,7 +2,9 @@ baseURL = "https://go-acme.github.io/lego/"
languageCode = "en-us"
title = "Lego"
# Code highlighting settings
theme = "hugo-theme-learn"
# Code higlighting settings
pygmentsCodefences = true
pygmentsCodeFencesGuesSsyntax = false
pygmentsOptions = ""
@ -18,6 +20,8 @@ pygmentsUseClasses = true
# Useful to give opportunity to people to create merge request for your doc.
# See the config.toml file from this documentation site to have an example.
# editURL = ""
# Author of the site, will be used in meta information
author = "Lego Team"
# Description of the site, will be used in meta information
# description = ""
# Shows a checkmark for visited pages on the menu
@ -44,10 +48,6 @@ pygmentsUseClasses = true
custom_css = ["css/theme-custom.css"]
disableLandingPageButton = true
# Author of the site, will be used in meta information
[params.author]
name = "Lego Team"
[Languages]
[Languages.en]
title = "Lets Encrypt client and ACME library written in Go."
@ -71,8 +71,4 @@ pygmentsUseClasses = true
weight = 12
[outputs]
home = [ "html", "rss", "search", "searchpage"]
[module]
[[module.imports]]
path = "github.com/McShelby/hugo-theme-relearn"
home = [ "HTML", "RSS", "JSON"]

View file

@ -1,10 +1,12 @@
---
title: "Lego"
title: "Welcome"
date: 2019-03-03T16:39:46+01:00
draft: false
chapter: false
chapter: true
---
# Lego
Let's Encrypt client and ACME library written in Go.
## Features
@ -23,7 +25,7 @@ Let's Encrypt client and ACME library written in Go.
- TLS (tls-alpn-01)
- SAN certificate support
- [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default
- Comes with multiple optional [DNS providers]({{% ref "dns" %}})
- [Custom challenge solvers]({{% ref "usage/library/Writing-a-Challenge-Solver" %}})
- Comes with multiple optional [DNS providers]({{< ref "dns" >}})
- [Custom challenge solvers]({{< ref "usage/library/Writing-a-Challenge-Solver" >}})
- Certificate bundling
- OCSP helper function

View file

@ -15,7 +15,7 @@ The environment variables can reference a value.
Here is an example bash command using the Cloudflare DNS provider:
```bash
```console
$ CLOUDFLARE_EMAIL=you@example.com \
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
lego --dns cloudflare --domains www.example.com --email you@example.com run
@ -33,7 +33,7 @@ The file must contain only the value.
Here is an example bash command using the CloudFlare DNS provider:
```bash
```console
$ cat /the/path/to/my/key
b9841238feb177a84330febba8a83208921177bffe733

View file

@ -21,7 +21,7 @@ To start using the CLI prompt "provider", start lego with `--dns manual`:
$ lego --email "you@example.com" --domains="example.com" --dns "manual" run
```
What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions:
What follows are a few log print outs, interspersed with some prompts, asking for you to do perform some actions:
```txt
No key found for account you@example.com. Generating a P256 key.

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns acme-dns --domains my.example.org run
| `ACME_DNS_STORAGE_PATH` | The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates. |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -50,7 +50,7 @@ lego --email you@example.com --dns alidns --domains my.example.org run
| `ALICLOUD_SECURITY_TOKEN` | STS Security Token (optional) |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -63,14 +63,14 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `ALICLOUD_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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## More information
- [API documentation](https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records)
- [API documentation](https://www.alibabacloud.com/help/doc-detail/42875.htm)
- [Go client](https://github.com/aliyun/alibaba-cloud-sdk-go)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns allinkl --domains my.example.org run
| `ALL_INKL_PASSWORD` | KAS 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -54,7 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `ALL_INKL_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns arvancloud --domains my.example.org run
| `ARVANCLOUD_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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -53,7 +53,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `ARVANCLOUD_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns auroradns --domains my.example.org run
| `AURORA_SECRET` | Secret password to be used |
The 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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -55,7 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `AURORA_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns autodns --domains my.example.org run
| `AUTODNS_API_USER` | 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -57,7 +57,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `AUTODNS_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -43,7 +43,7 @@ _Please contribute by adding a CLI example._
| `instance metadata service` | If the credentials are **not** set via the environment, then it will attempt to get a bearer token via the [instance metadata service](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service). |
The 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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -58,7 +58,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `AZURE_ZONE_NAME` | Zone name to use inside Azure DNS service to add the TXT record in |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -4,7 +4,7 @@ date: 2019-03-03T16:39:46+01:00
draft: false
slug: azuredns
dnsprovider:
since: "v4.13.0"
since: "v0.1.0"
code: "azuredns"
url: "https://azure.microsoft.com/services/dns/"
---
@ -20,44 +20,27 @@ Configuration for [Azure DNS](https://azure.microsoft.com/services/dns/).
<!--more-->
- Code: `azuredns`
- Since: v4.13.0
- Since: v0.1.0
Here is an example bash command using the AzureDNS provider:
```bash
### Using client secret
AZURE_CLIENT_ID=<your service principal client ID> \
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_CLIENT_SECRET=<your service principal client secret> \
lego --domains example.com --email your_example@email.com --dns azuredns run
### Using client certificate
AZURE_CLIENT_ID=<your service principal client ID> \
AZURE_TENANT_ID=<your service principal tenant ID> \
AZURE_CLIENT_CERTIFICATE_PATH=<your service principal certificate path> \
lego --domains example.com --email your_example@email.com --dns azuredns run
### Using Azure CLI
az login \
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_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> \
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
```
@ -67,168 +50,62 @@ 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.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
| 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 |
The 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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Description
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:
Azure Credentials are automatically detected 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 (defaults to `~/.azure` folder), used by Azure CLI
4. Shared credentials file (defaults to `~/.azure`), 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
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
#### Azure Managed Identity
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
Azure managed identity service allows linking Azure AD identities to Azure resources. \
Workloads running inside compute typed resource can inherit from this configuration to get rights on Azure resources.
#### Workload identity for AKS
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`.
* 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.
* 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.
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.
Workloads with a Managed Identity can manage their own certificates, with permissions on specific domain names set using IAM assignments.
For this to work, the Managed Identity requires the **Reader** role on the target DNS Zone,
and the **DNS Zone Contributor** on the relevant `_acme-challenge` TXT records.
For example, to allow a Managed Identity to create a certificate for "fw01.lab.example.com", using Azure CLI:
```bash
export AZURE_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export AZURE_RESOURCE_GROUP="rg1"
export SERVICE_PRINCIPAL_ID="00000000-0000-0000-0000-000000000000"
export AZURE_DNS_ZONE="lab.example.com"
export AZ_HOSTNAME="fw01"
export AZ_RECORD_SET="_acme-challenge.${AZ_HOSTNAME}"
az role assignment create \
--assignee "${SERVICE_PRINCIPAL_ID}" \
--role "Reader" \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}"
az role assignment create \
--assignee "${SERVICE_PRINCIPAL_ID}" \
--role "DNS Zone Contributor" \
--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
(such as on-prem virtual machines, or VMs in another cloud provider).
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`
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 CLI
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.
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`.
### 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

@ -40,7 +40,7 @@ lego --email you@example.com --dns bindman --domains my.example.org run
| `BINDMAN_MANAGER_ADDRESS` | The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server |
The 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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -52,7 +52,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `BINDMAN_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -49,7 +49,7 @@ lego --email you@example.com --dns bluecat --domains my.example.org run
| `BLUECAT_USER_NAME` | 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -59,11 +59,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `BLUECAT_HTTP_TIMEOUT` | API request timeout |
| `BLUECAT_POLLING_INTERVAL` | Time between DNS propagation check |
| `BLUECAT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `BLUECAT_SKIP_DEPLOY` | Skip deployements |
| `BLUECAT_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -42,7 +42,7 @@ lego --email myemail@example.com --dns brandit --domains my.example.org run
| `BRANDIT_API_USERNAME` | The 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -55,7 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `BRANDIT_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns bunny --domains my.example.org run
| `BUNNY_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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -52,7 +52,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `BUNNY_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns checkdomain --domains my.example.org run
| `CHECKDOMAIN_TOKEN` | API token |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -54,7 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CHECKDOMAIN_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns civo --domains my.example.org run
| `CIVO_TOKEN` | Authentication 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -52,7 +52,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CIVO_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -44,7 +44,7 @@ lego --email you@example.com --dns clouddns --domains my.example.org run
| `CLOUDDNS_PASSWORD` | Account 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -57,7 +57,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CLOUDDNS_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -53,20 +53,20 @@ lego --email you@example.com --dns cloudflare --domains my.example.org run
| `CLOUDFLARE_ZONE_API_TOKEN` | Alias to CF_ZONE_API_TOKEN |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout (in seconds) |
| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check (in seconds) |
| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation (in seconds) |
| `CLOUDFLARE_TTL` | The TTL of the TXT record used for the DNS challenge (in seconds) |
| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout |
| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check |
| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `CLOUDFLARE_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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Description
@ -85,7 +85,7 @@ very specific access can be granted to your resources at Cloudflare.
See this [Cloudflare announcement](https://blog.cloudflare.com/api-tokens-general-availability/) for details.
The main resources Lego cares for are the DNS entries for your Zones.
It also needs to resolve a domain name to an internal Zone ID in order to manipulate DNS entries.
It also need to resolve a domain name to an internal Zone ID in order to manipulate DNS entries.
Hence, you should create an API token with the following permissions:

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns cloudns --domains my.example.org run
| `CLOUDNS_AUTH_PASSWORD` | The password for API user ID |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -56,7 +56,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CLOUDNS_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -1,72 +0,0 @@
---
title: "Cloud.ru"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: cloudru
dnsprovider:
since: "v4.14.0"
code: "cloudru"
url: "https://cloud.ru"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/cloudru/cloudru.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [Cloud.ru](https://cloud.ru).
<!--more-->
- Code: `cloudru`
- Since: v4.14.0
Here is an example bash command using the Cloud.ru provider:
```bash
CLOUDRU_SERVICE_INSTANCE_ID=ppp \
CLOUDRU_KEY_ID=xxx \
CLOUDRU_SECRET=yyy \
lego --email you@example.com --dns cloudru --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `CLOUDRU_KEY_ID` | Key ID (login) |
| `CLOUDRU_SECRET` | Key Secret |
| `CLOUDRU_SERVICE_INSTANCE_ID` | Service Instance ID (parentId) |
The 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 |
|--------------------------------|-------------|
| `CLOUDRU_HTTP_TIMEOUT` | API request timeout |
| `CLOUDRU_POLLING_INTERVAL` | Time between DNS propagation check |
| `CLOUDRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `CLOUDRU_SEQUENCE_INTERVAL` | Time between sequential requests |
| `CLOUDRU_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
## More information
- [API documentation](https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref.html)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/cloudru/cloudru.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns cloudxns --domains my.example.org run
| `CLOUDXNS_SECRET_KEY` | The API 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -55,7 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CLOUDXNS_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -44,7 +44,7 @@ lego --email you@example.com --dns conoha --domains my.example.org run
| `CONOHA_TENANT_ID` | Tenant ID |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -58,7 +58,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CONOHA_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns constellix --domains my.example.org run
| `CONSTELLIX_SECRET_KEY` | User 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -55,7 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `CONSTELLIX_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -1,83 +0,0 @@
---
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

@ -40,7 +40,7 @@ lego --email myemail@example.com --dns derak --domains my.example.org run
| `DERAK_API_KEY` | The API key |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -54,7 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `DERAK_WEBSITE_ID` | Force the zone/website ID |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns desec --domains my.example.org run
| `DESEC_TOKEN` | Domain 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -53,7 +53,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `DESEC_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -67,7 +67,7 @@ lego --email you@example.com --dns designate --domains my.example.org run
| `OS_USER_ID` | User ID |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -77,12 +77,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `DESIGNATE_POLLING_INTERVAL` | Time between DNS propagation check |
| `DESIGNATE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `DESIGNATE_TTL` | The TTL of the TXT record used for the DNS challenge |
| `DESIGNATE_ZONE_NAME` | The zone name to use in the OpenStack Project to manage TXT records. |
| `OS_PROJECT_ID` | Project ID |
| `OS_TENANT_NAME` | Tenant name (deprecated see OS_PROJECT_NAME and OS_PROJECT_ID) |
The 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 [here]({{< ref "dns#configuration-and-credentials" >}}).
## Description
@ -99,10 +98,6 @@ 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

@ -40,7 +40,7 @@ lego --email you@example.com --dns digitalocean --domains my.example.org run
| `DO_AUTH_TOKEN` | Authentication 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" %}}).
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
## Additional Configuration
@ -54,7 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
| `DO_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 [here]({{< ref "dns#configuration-and-credentials" >}}).

View file

@ -1,72 +0,0 @@
---
title: "DirectAdmin"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: directadmin
dnsprovider:
since: "v4.18.0"
code: "directadmin"
url: "https://www.directadmin.com"
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/directadmin/directadmin.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Configuration for [DirectAdmin](https://www.directadmin.com).
<!--more-->
- Code: `directadmin`
- Since: v4.18.0
Here is an example bash command using the DirectAdmin provider:
```bash
DIRECTADMIN_API_URL="http://example.com:2222" \
DIRECTADMIN_USERNAME=xxxx \
DIRECTADMIN_PASSWORD=yyy \
lego --email you@example.com --dns directadmin --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `DIRECTADMIN_API_URL` | URL of the API |
| `DIRECTADMIN_PASSWORD` | API password |
| `DIRECTADMIN_USERNAME` | API username |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
## Additional Configuration
| Environment Variable Name | Description |
|--------------------------------|-------------|
| `DIRECTADMIN_HTTP_TIMEOUT` | API request timeout |
| `DIRECTADMIN_POLLING_INTERVAL` | Time between DNS propagation check |
| `DIRECTADMIN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `DIRECTADMIN_TTL` | The TTL of the TXT record used for the DNS challenge |
| `DIRECTADMIN_ZONE_NAME` | Zone name used to add the TXT record |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
## More information
- [API documentation](https://www.directadmin.com/api.php)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/directadmin/directadmin.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

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