Compare commits
183 commits
tcl/master
...
tcl/http-0
Author | SHA1 | Date | |
---|---|---|---|
|
e70ec01016 | ||
|
49dacb2db1 | ||
|
90a0b0e1e4 | ||
|
6fbfe93012 | ||
|
36fa8b661f | ||
|
748e4c0d70 | ||
|
85b5ef459a | ||
|
6e2e6d2ea7 | ||
|
0c0ee41c5f | ||
|
a83482cac4 | ||
|
d81507c126 | ||
|
56986eaa99 | ||
|
c704ba5832 | ||
|
d2898e1706 | ||
|
20c8d6c413 | ||
|
eb7de2a32f | ||
|
141d60d41e | ||
|
69bd2e0373 | ||
|
0da0942081 | ||
|
d52f7b0b58 | ||
|
253e3305bc | ||
|
b95f03d3b3 | ||
|
65cd007d3f | ||
|
75b910b296 | ||
|
b3e630761e | ||
|
dc429c26a0 | ||
|
5c6572392f | ||
|
2d880c2e6c | ||
|
fd2bdf9e48 | ||
|
beaa35caf9 | ||
|
7062e8c28f | ||
|
f93651a54d | ||
|
29e98f8a43 | ||
|
c083a989a1 | ||
|
cb24671848 | ||
|
501aded55b | ||
|
5ab212cf8d | ||
|
24f421463a | ||
|
fa7cf5cd4f | ||
|
db2ab55cdd | ||
|
13b5cb57f0 | ||
|
5bea70766f | ||
|
c759f56556 | ||
|
9873b9b897 | ||
|
04864ff13b | ||
|
321cea51e4 | ||
|
fa0c05f5d0 | ||
|
4cbe9a2af5 | ||
|
834a9089f1 | ||
|
c63be848f6 | ||
|
69cacab700 | ||
|
82073f3bcd | ||
|
8992dae8a7 | ||
|
2f464d47d6 | ||
|
c0f04e87e7 | ||
|
9dbc0988b8 | ||
|
96bb0ba904 | ||
|
e508b664b3 | ||
|
8abe1008bb | ||
|
96a8381a6b | ||
|
4d7fcb6048 | ||
|
65e8940311 | ||
|
71b48f8217 | ||
|
942ba6f591 | ||
|
de6f9d26b1 | ||
|
f038102ef9 | ||
|
975c40a8bb | ||
|
d3d59c166e | ||
|
5eb87685e2 | ||
|
f89e257694 | ||
|
220c608c80 | ||
|
92bde4cd56 | ||
|
d39d57fbc9 | ||
|
bf10a46784 | ||
|
c61aeba3e2 | ||
|
11b4beff7e | ||
|
2ec9e42ee3 | ||
|
983c181e45 | ||
|
f6d1413a3f | ||
|
acd338259d | ||
|
42aa57e2b9 | ||
|
76eb1eac8a | ||
|
d60c335cc0 | ||
|
55dd478cb2 | ||
|
ca32b56550 | ||
|
8623f0df01 | ||
|
a832515ad5 | ||
|
ef9086a0f9 | ||
|
8dd1fa5a32 | ||
|
3371145f01 | ||
|
874e3ea023 | ||
|
27fd142ca1 | ||
|
61553c4195 | ||
|
441775ec65 | ||
|
40dcce60be | ||
|
19bbefbc8c | ||
|
719d26c0fc | ||
|
5bde3fbedd | ||
|
008adfddca | ||
|
6dd8d035d1 | ||
|
adea063bb1 | ||
|
82e9a5e2a9 | ||
|
6933296e2f | ||
|
a7ca3d7f1c | ||
|
9d0cd24533 | ||
|
fac40196c5 | ||
|
1106ad382f | ||
|
fd6047a1b8 | ||
|
ba67a265c0 | ||
|
7fe1796157 | ||
|
b9b0412f7c | ||
|
23824af555 | ||
|
c5a95c4cd0 | ||
|
83ff393131 | ||
|
719adc3964 | ||
|
6decaed8a3 | ||
|
ac6c0a5f63 | ||
|
1a3de70183 | ||
|
ec8306f4b5 | ||
|
46fe435c2c | ||
|
755d479e62 | ||
|
4eab81a9eb | ||
|
9c1a856b73 | ||
|
d263a28c64 | ||
|
ad6e38e7db | ||
|
3c73f624ba | ||
|
bb830677d1 | ||
|
7fd1704282 | ||
|
143aa4f3ee | ||
|
9d4c60e67a | ||
|
c17f659c5d | ||
|
c847ac4a4c | ||
|
3ba40ff7da | ||
|
68dc83ae0a | ||
|
e98dea02de | ||
|
6da922047a | ||
|
7186ebb6f1 | ||
|
5af3c6c042 | ||
|
cab8e1f556 | ||
|
d51b5e408b | ||
|
4f242c93e6 | ||
|
c9ff534e39 | ||
|
52990b3c9e | ||
|
8afdc9d01c | ||
|
2140e6befe | ||
|
bf8c7abf6d | ||
|
c2fd4498e5 | ||
|
b523518108 | ||
|
d0aa6b3c0d | ||
|
113648a368 | ||
|
766e581f8d | ||
|
a05e3832a1 | ||
|
a6ddcac65a | ||
|
5ef996ee05 | ||
|
8a7fd67c58 | ||
|
6209be73de | ||
|
11724c7bf3 | ||
|
04359244cd | ||
|
668a529c48 | ||
|
6af2c756ac | ||
|
cef49ab93b | ||
|
838eff2c02 | ||
|
a7befed6e8 | ||
|
a423bb7411 | ||
|
406dad30fe | ||
|
6792701dbc | ||
|
b37d60c033 | ||
|
c365d7efc8 | ||
|
ed559f0568 | ||
|
e8a97d9b90 | ||
|
f4f42f16b7 | ||
|
07c4daeff3 | ||
|
01747e39cc | ||
|
35c259e91d | ||
|
3cefc7a51b | ||
|
f582d12f65 | ||
|
d21706420a | ||
|
ae7823705e | ||
|
6c13564bad | ||
|
fc47c35e89 | ||
|
ed14dda361 | ||
|
fa815959fa | ||
|
f6aad431be |
605 changed files with 18762 additions and 3349 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
|||
github: ldez
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Bug Report
|
||||
name: 🐞 Bug Report
|
||||
description: Create a report to help us improve.
|
||||
labels: [bug]
|
||||
body:
|
||||
|
@ -42,6 +42,7 @@ body:
|
|||
- Through Caddy
|
||||
- Through Terraform ACME provider
|
||||
- Through Bitnami
|
||||
- Through Zoraxy
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -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: lego documentation
|
||||
- name: 📖 Documentation
|
||||
url: https://go-acme.github.io/lego/
|
||||
about: Please take a look to our documentation.
|
||||
|
|
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Feature request
|
||||
name: 💡 Feature request
|
||||
description: Suggest an idea for this project.
|
||||
body:
|
||||
- type: checkboxes
|
||||
|
@ -21,6 +21,7 @@ body:
|
|||
- Through Caddy
|
||||
- Through Terraform ACME provider
|
||||
- Through Bitnami
|
||||
- Through Zoraxy
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
|
2
.github/ISSUE_TEMPLATE/new_dns_provider.yml
vendored
2
.github/ISSUE_TEMPLATE/new_dns_provider.yml
vendored
|
@ -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]
|
||||
|
|
30
.github/PULL_REQUEST_TEMPLATE/mnp.md
vendored
Normal file
30
.github/PULL_REQUEST_TEMPLATE/mnp.md
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
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 #
|
||||
|
22
.github/workflows/documentation.yml
vendored
22
.github/workflows/documentation.yml
vendored
|
@ -11,30 +11,30 @@ jobs:
|
|||
name: Build and deploy documentation
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO_VERSION: '1.20'
|
||||
HUGO_VERSION: 0.101.0
|
||||
GO_VERSION: stable
|
||||
HUGO_VERSION: 0.131.0
|
||||
CGO_ENABLED: 0
|
||||
|
||||
steps:
|
||||
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
# https://github.com/marketplace/actions/checkout
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Generate DNS docs
|
||||
run: make generate-dns
|
||||
|
||||
- name: Install Hugo
|
||||
run: |
|
||||
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.deb
|
||||
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-amd64.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@v3
|
||||
uses: crazy-max/ghaction-github-pages@v4
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: docs/public
|
||||
|
|
30
.github/workflows/go-cross.yml
vendored
30
.github/workflows/go-cross.yml
vendored
|
@ -16,37 +16,19 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ '1.19', '1.20', 1.x ]
|
||||
go-version: [ oldstable, stable ]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
# https://github.com/marketplace/actions/checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://github.com/marketplace/actions/cache
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v3
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v5
|
||||
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-
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Test
|
||||
run: go test -v -cover ./...
|
||||
|
|
31
.github/workflows/pr.yml
vendored
31
.github/workflows/pr.yml
vendored
|
@ -12,35 +12,26 @@ jobs:
|
|||
name: Main Process
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO_VERSION: '1.20'
|
||||
GOLANGCI_LINT_VERSION: v1.53.1
|
||||
HUGO_VERSION: 0.54.0
|
||||
GO_VERSION: stable
|
||||
GOLANGCI_LINT_VERSION: v1.60.1
|
||||
HUGO_VERSION: 0.131.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@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# https://github.com/marketplace/actions/cache
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v3
|
||||
# https://github.com/marketplace/actions/setup-go-environment
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Check and get dependencies
|
||||
run: |
|
||||
|
@ -55,10 +46,10 @@ jobs:
|
|||
golangci-lint --version
|
||||
|
||||
- name: Install Pebble
|
||||
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@main
|
||||
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@3fe019bbc0a41ed16e2fee31592bb91751acaa47
|
||||
|
||||
- name: Install challtestsrv
|
||||
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@main
|
||||
run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@3fe019bbc0a41ed16e2fee31592bb91751acaa47
|
||||
|
||||
- name: Set up a Memcached server
|
||||
uses: niden/actions-memcached@v7
|
||||
|
@ -78,7 +69,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-64bit.deb
|
||||
wget -O /tmp/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-amd64.deb
|
||||
sudo dpkg -i /tmp/hugo.deb
|
||||
|
||||
- name: Build Documentation
|
||||
|
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
|
@ -11,10 +11,16 @@ jobs:
|
|||
name: Release version
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO_VERSION: '1.20'
|
||||
GO_VERSION: stable
|
||||
CGO_ENABLED: 0
|
||||
|
||||
steps:
|
||||
# temporary workaround for an error in free disk space action
|
||||
# https://github.com/jlumbroso/free-disk-space/issues/14
|
||||
- name: Update Package List and Remove Dotnet
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get remove -y '^dotnet-.*'
|
||||
|
||||
# https://github.com/marketplace/actions/free-disk-space-ubuntu
|
||||
- name: Free Disk Space
|
||||
|
@ -32,12 +38,12 @@ jobs:
|
|||
swap-storage: false
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
@ -47,17 +53,21 @@ jobs:
|
|||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
|
||||
|
||||
- name: Install snapcraft
|
||||
run: sudo snap install snapcraft --classic
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# https://goreleaser.com/ci/actions/
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
version: latest
|
||||
args: release -p 1 --clean --timeout=90m
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
run:
|
||||
timeout: 10m
|
||||
skip-files: []
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable:
|
||||
- shadow
|
||||
gocyclo:
|
||||
min-complexity: 12
|
||||
maligned:
|
||||
suggest-new: true
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 3
|
||||
|
@ -88,20 +86,20 @@ linters-settings:
|
|||
disabled: true
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
testifylint:
|
||||
disable:
|
||||
- require-error
|
||||
- go-require
|
||||
perfsprint:
|
||||
err-error: true
|
||||
errorf: true
|
||||
sprintf1: true
|
||||
strconcat: false
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- deadcode # deprecated
|
||||
- exhaustivestruct # deprecated
|
||||
- golint # deprecated
|
||||
- ifshort # deprecated
|
||||
- interfacer # deprecated
|
||||
- maligned # deprecated
|
||||
- nosnakecase # deprecated
|
||||
- scopelint # deprecated
|
||||
- structcheck # deprecated
|
||||
- varcheck # deprecated
|
||||
- gomnd # deprecated
|
||||
- cyclop # duplicate of gocyclo
|
||||
- sqlclosecheck # not relevant (SQL)
|
||||
- rowserrcheck # not relevant (SQL)
|
||||
|
@ -111,13 +109,13 @@ linters:
|
|||
- dupl # not relevant
|
||||
- prealloc # too many false-positive
|
||||
- bodyclose # too many false-positive
|
||||
- gomnd
|
||||
- mnd
|
||||
- testpackage # not relevant
|
||||
- tparallel # not relevant
|
||||
- paralleltest # not relevant
|
||||
- nestif # too many false-positive
|
||||
- wrapcheck
|
||||
- goerr113 # not relevant
|
||||
- err113 # not relevant
|
||||
- nlreturn # not relevant
|
||||
- wsl # not relevant
|
||||
- exhaustive # not relevant
|
||||
|
@ -137,10 +135,13 @@ 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-per-linter: 0
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude:
|
||||
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||
|
@ -152,6 +153,10 @@ issues:
|
|||
- funlen
|
||||
- goconst
|
||||
- maintidx
|
||||
- path: (.+)_test.go
|
||||
text: 'Error return value of `fmt.Fprintln` is not checked'
|
||||
linters:
|
||||
- errcheck
|
||||
- path: providers/dns/dns_providers.go
|
||||
linters:
|
||||
- gocyclo
|
||||
|
@ -181,6 +186,8 @@ 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
|
||||
|
@ -221,5 +228,18 @@ issues:
|
|||
- path: providers/dns/hosttech/internal/client_test.go
|
||||
text: 'Duplicate words \(0\) found'
|
||||
- path: cmd/cmd_renew.go
|
||||
text: 'cyclomatic complexity 16 of func `renewForDomains` is high'
|
||||
text: 'cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high'
|
||||
- path: providers/dns/cpanel/cpanel.go
|
||||
text: 'cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high'
|
||||
|
||||
# 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
|
||||
|
|
|
@ -41,6 +41,14 @@ builds:
|
|||
- goos: openbsd
|
||||
goarch: arm
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '(?i)^chore:'
|
||||
- '(?i)^Detach v[\d|.]+'
|
||||
- '(?i)^Prepare release v[\d|.]+'
|
||||
|
||||
archives:
|
||||
- id: lego
|
||||
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}'
|
||||
|
@ -133,3 +141,33 @@ dockers:
|
|||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
||||
- '--platform=linux/arm/v7'
|
||||
|
||||
# Disabled because https://github.com/go-acme/lego/pull/2134#issuecomment-2135293270
|
||||
snapcrafts:
|
||||
- name: lego
|
||||
disable: true
|
||||
grade: stable
|
||||
confinement: strict
|
||||
license: MIT
|
||||
base: core22
|
||||
publish: true
|
||||
summary: Lego is a Let's Encrypt/ACME client.
|
||||
description: |
|
||||
Lego is a Let's Encrypt/ACME client written in Go.
|
||||
|
||||
The lego snap makes it easy to install and use Lego on any Linux distribution that supports snaps.
|
||||
|
||||
Usage:
|
||||
* `sudo snap install lego`
|
||||
* `sudo lego --email="you@example.com" --domains="example.com" --server=https://acme-staging-v02.api.letsencrypt.org/directory --http --http.port :8080 run
|
||||
|
||||
channel_templates:
|
||||
- edge
|
||||
|
||||
apps:
|
||||
lego:
|
||||
command: bin/lego
|
||||
environment:
|
||||
LEGO_PATH: /var/snap/lego/common/.lego
|
||||
plugs:
|
||||
- network-bind
|
||||
|
|
294
CHANGELOG.md
294
CHANGELOG.md
|
@ -1,13 +1,228 @@
|
|||
# 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
|
||||
|
@ -15,10 +230,12 @@
|
|||
- **[dnsprovider]** azure: new implementation based on the new API client
|
||||
- **[lib]** Experimental option to force DNS queries to use TCP
|
||||
|
||||
### Changed:
|
||||
### Changed
|
||||
|
||||
- **[dnsprovider]** cloudflare: update api client to v0.70.0
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider,cname]** fix: ensure case-insensitive comparison of CNAME records
|
||||
- **[cli]** fix: list command
|
||||
- **[lib]** fix: ARI explanationURL
|
||||
|
@ -29,33 +246,39 @@ Cancelled due to a CI issue (no space left on device).
|
|||
|
||||
## [v4.12.2] - 2023-06-19
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** dnsmadeeasy: fix DeleteRecord
|
||||
- **[lib]** fix: read status code from response
|
||||
|
||||
## [v4.12.1] - 2023-06-06
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** pdns: fix record value
|
||||
|
||||
## [v4.12.0] - 2023-05-28
|
||||
|
||||
### Added:
|
||||
### Added
|
||||
|
||||
- **[lib,cli]** Initial ACME Renewal Info (ARI) Implementation
|
||||
- **[dnsprovider]** Add DNS provider for Derak Cloud
|
||||
- **[dnsprovider]** route53: pass ExternalID property to STS:AssumeRole API operation
|
||||
- **[lib,cli]** Support custom duration for certificate
|
||||
|
||||
### Changed:
|
||||
### Changed
|
||||
|
||||
- **[dnsprovider]** Refactor DNS provider and client implementations
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used
|
||||
- **[cli]** fix: archive only domain-related files on revoke
|
||||
|
||||
## [v4.11.0] - 2023-05-02
|
||||
|
||||
### Added:
|
||||
### Added
|
||||
|
||||
- **[lib]** Support for certificate with raw IP SAN (RFC8738)
|
||||
- **[dnsprovider]** Add Brandit.com as DNS provider
|
||||
- **[dnsprovider]** Add DNS provider for Bunny
|
||||
|
@ -63,13 +286,15 @@ Cancelled due to a CI issue (no space left on device).
|
|||
- **[dnsprovider]** Add Google Domains as DNS provider
|
||||
- **[dnsprovider]** Add DNS provider for Plesk
|
||||
|
||||
### Changed:
|
||||
### Changed
|
||||
|
||||
- **[cli]** feat: add LEGO_CERT_PEM_PATH and LEGO_CERT_PFX_PATH to run hook
|
||||
- **[lib,cli]** feat: add RSA 3072
|
||||
- **[dnsprovider]** gcloud: update google APIs to latest version
|
||||
- **[lib,dnsprovider,cname]** chore: replace GetRecord by GetChallengeInfo
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** rimuhosting: fix API base URL
|
||||
|
||||
## [v4.10.2] - 2023-02-26
|
||||
|
@ -78,26 +303,30 @@ Fix Docker image builds.
|
|||
|
||||
## [v4.10.1] - 2023-02-25
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider,cname]** acmedns: fix CNAME support
|
||||
- **[dnsprovider]** dynu: fix subdomain support
|
||||
|
||||
## [v4.10.0] - 2023-02-10
|
||||
|
||||
### Added:
|
||||
### Added
|
||||
|
||||
- **[dnsprovider]** Add DNS provider for dnsHome.de
|
||||
- **[dnsprovider]** Add DNS provider for Liara
|
||||
- **[dnsprovider]** Add DNS provider for UltraDNS
|
||||
- **[dnsprovider]** Add DNS provider for Websupport
|
||||
|
||||
### Changed:
|
||||
### Changed
|
||||
|
||||
- **[dnsprovider]** ibmcloud: add support for subdomains
|
||||
- **[dnsprovider]** infomaniak: CNAME support
|
||||
- **[dnsprovider]** namesilo: add cleanup before add a DNS record
|
||||
- **[dnsprovider]** route53: Allow static credentials to be supplied
|
||||
- **[dnsprovider]** tencentcloud: support punycode domain
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** alidns: filter on record type
|
||||
- **[dnsprovider]** arvancloud: replace arvancloud.com by arvancloud.ir
|
||||
- **[dnsprovider]** hetzner: improve zone ID detection
|
||||
|
@ -107,12 +336,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
|
||||
|
@ -122,7 +351,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
|
||||
|
@ -131,7 +360,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
|
||||
|
@ -144,7 +373,8 @@ Fix Docker image builds.
|
|||
- **[lib,cname]** add recursive CNAME lookup support
|
||||
- **[lib]** Remove embedded issuer certificates from issued certificate if bundle is false
|
||||
|
||||
### Fixed:
|
||||
### Fixed
|
||||
|
||||
- **[dnsprovider]** luadns: fix cname support
|
||||
- **[dnsprovider]** njalla: fix record id unmarshal error
|
||||
- **[dnsprovider]** tencentcloud: fix subdomain error
|
||||
|
@ -471,7 +701,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:
|
||||
|
@ -509,7 +739,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 "LEGO_PATH" environment variable
|
||||
|
||||
### Changed:
|
||||
|
||||
|
@ -522,7 +752,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'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.
|
||||
|
@ -547,7 +777,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't deactivate valid authorizations
|
||||
- **[lib]** Expose more SOA fields found by dns01.FindZoneByFqdn
|
||||
|
||||
### Fixed:
|
||||
|
@ -575,7 +805,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 invalidate.
|
||||
There was a problem when creating the tag v3.0.1, this tag has been invalidated.
|
||||
|
||||
## [v3.0.0] - 2019-08-05
|
||||
|
||||
|
@ -720,7 +950,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidate.
|
|||
- **[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 "HTTP request".
|
||||
- **[dnsprovider]** Add DNS Provider for Vscale
|
||||
- **[dnsprovider]** Add DNS Provider for TransIP
|
||||
- **[dnsprovider]** Add DNS Provider for inwx
|
||||
|
@ -850,7 +1080,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidate.
|
|||
### Added:
|
||||
- lib: A new DNS provider for OTC.
|
||||
- lib: The `AWS_HOSTED_ZONE_ID` environment variable for the Route53 DNS provider to directly specify the zone.
|
||||
- lib: The `RFC2136_TIMEOUT` enviroment variable to make the timeout for the RFC2136 provider configurable.
|
||||
- lib: The `RFC2136_TIMEOUT` environment variable to make the timeout for the RFC2136 provider configurable.
|
||||
- lib: The `GCE_SERVICE_ACCOUNT_FILE` environment variable to specify a service account file for the Google Cloud DNS provider.
|
||||
|
||||
### Fixed:
|
||||
|
@ -867,7 +1097,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidate.
|
|||
- 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.
|
||||
|
@ -889,11 +1119,11 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidate.
|
|||
- 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 a integral part is nil
|
||||
- lib: Loading an account from file should fail if an 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.
|
||||
|
@ -961,7 +1191,7 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidate.
|
|||
|
||||
### Changed:
|
||||
- lib: NewClient does no longer accept the optPort parameter
|
||||
- lib: ObtainCertificate now returns a SAN certificate if you pass more then one domain.
|
||||
- lib: ObtainCertificate now returns a SAN certificate if you pass more than 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.
|
||||
|
|
|
@ -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.
|
||||
|
|
56
README.md
56
README.md
|
@ -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 Application‑Layer Protocol Negotiation (ALPN) Challenge Extension
|
||||
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses
|
||||
- Support [draft-ietf-acme-ari-01](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
|
||||
- Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension
|
||||
- Register with CA
|
||||
- Obtain certificates, both from scratch or with an existing CSR
|
||||
- Renew certificates
|
||||
|
@ -55,35 +55,39 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
|
|||
|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||
| [Akamai EdgeDNS](https://go-acme.github.io/lego/dns/edgedns/) | [Alibaba Cloud DNS](https://go-acme.github.io/lego/dns/alidns/) | [all-inkl](https://go-acme.github.io/lego/dns/allinkl/) | [Amazon Lightsail](https://go-acme.github.io/lego/dns/lightsail/) |
|
||||
| [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/) |
|
||||
| [Azure (deprecated)](https://go-acme.github.io/lego/dns/azure/) | [Azure DNS](https://go-acme.github.io/lego/dns/azuredns/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) |
|
||||
| [Brandit](https://go-acme.github.io/lego/dns/brandit/) | [Bunny](https://go-acme.github.io/lego/dns/bunny/) | [Checkdomain](https://go-acme.github.io/lego/dns/checkdomain/) | [Civo](https://go-acme.github.io/lego/dns/civo/) |
|
||||
| [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/) |
|
||||
| [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/) |
|
||||
| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) |
|
||||
| [Ultradns](https://go-acme.github.io/lego/dns/ultradns/) | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Vercel](https://go-acme.github.io/lego/dns/vercel/) |
|
||||
| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) |
|
||||
| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex 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/) | [Webnames](https://go-acme.github.io/lego/dns/webnames/) | [Websupport](https://go-acme.github.io/lego/dns/websupport/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) |
|
||||
| [Yandex 360](https://go-acme.github.io/lego/dns/yandex360/) | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/) | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) |
|
||||
| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | |
|
||||
|
||||
<!-- END DNS PROVIDERS LIST -->
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
|||
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||
location := getLocation(resp)
|
||||
|
||||
if len(location) > 0 {
|
||||
if location != "" {
|
||||
a.core.jws.SetKid(location)
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ func (n *Manager) getNonce() (string, error) {
|
|||
return GetFromResponse(resp)
|
||||
}
|
||||
|
||||
// GetFromResponse Extracts a nonce from a HTTP response.
|
||||
// GetFromResponse Extracts a nonce from an HTTP response.
|
||||
func GetFromResponse(resp *http.Response) (string, error) {
|
||||
if resp == nil {
|
||||
return "", errors.New("nil response")
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
||||
jose "github.com/go-jose/go-jose/v3"
|
||||
jose "github.com/go-jose/go-jose/v4"
|
||||
)
|
||||
|
||||
// JWS Represents a JWS.
|
||||
|
|
|
@ -5,10 +5,10 @@ package sender
|
|||
|
||||
const (
|
||||
// ourUserAgent is the User-Agent of this underlying library package.
|
||||
ourUserAgent = "xenolf-acme/4.13.2"
|
||||
ourUserAgent = "xenolf-acme/4.19.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 = "detach"
|
||||
ourUserAgentComment = "release"
|
||||
)
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
type OrderOptions struct {
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
// A string uniquely identifying a previously-issued certificate which this
|
||||
// order is intended to replace.
|
||||
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
ReplacesCertID string
|
||||
}
|
||||
|
||||
type OrderService service
|
||||
|
@ -45,6 +49,10 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm
|
|||
if !opts.NotBefore.IsZero() {
|
||||
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if o.core.GetDirectory().RenewalInfo != "" {
|
||||
orderReq.Replaces = opts.ReplacesCertID
|
||||
}
|
||||
}
|
||||
|
||||
var order acme.Order
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -94,7 +94,6 @@ func TestOrderService_NewWithOptions(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -112,7 +111,8 @@ func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
jws, err := jose.ParseSigned(string(reqBody))
|
||||
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
|
||||
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package api
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
)
|
||||
|
||||
// ErrNoARI is returned when the server does not advertise a renewal info endpoint.
|
||||
|
@ -28,26 +26,3 @@ func (c *CertificateService) GetRenewalInfo(certID string) (*http.Response, erro
|
|||
|
||||
return c.core.HTTPClient.Get(c.core.GetDirectory().RenewalInfo + "/" + certID)
|
||||
}
|
||||
|
||||
// UpdateRenewalInfo POSTs updated renewal information for a certificate to the renewalInfo endpoint.
|
||||
// This is used to indicate that a certificate has been replaced.
|
||||
//
|
||||
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
||||
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||
func (c *CertificateService) UpdateRenewalInfo(req acme.RenewalInfoUpdateRequest) (*http.Response, error) {
|
||||
if c.core.GetDirectory().RenewalInfo == "" {
|
||||
return nil, ErrNoARI
|
||||
}
|
||||
|
||||
if req.CertID == "" {
|
||||
return nil, errors.New("renewalInfo[post]: 'certID' cannot be empty")
|
||||
}
|
||||
|
||||
if !req.Replaced {
|
||||
return nil, errors.New("renewalInfo[post]: 'replaced' cannot be false")
|
||||
}
|
||||
|
||||
return c.core.post(c.core.GetDirectory().RenewalInfo, req, nil)
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ func Test_getLink(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -71,12 +71,12 @@ type Meta struct {
|
|||
|
||||
// externalAccountRequired (optional, boolean):
|
||||
// If this field is present and set to "true",
|
||||
// then the CA requires that all new- account requests include an "externalAccountBinding" field
|
||||
// then the CA requires that all new-account requests include an "externalAccountBinding" field
|
||||
// associating the new account with an external account.
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||
}
|
||||
|
||||
// ExtendedAccount a extended Account.
|
||||
// ExtendedAccount an extended Account.
|
||||
type ExtendedAccount struct {
|
||||
Account
|
||||
// Contains the value of the response header `Location`
|
||||
|
@ -91,7 +91,7 @@ type Account struct {
|
|||
// The status of this account.
|
||||
// Possible values are: "valid", "deactivated", and "revoked".
|
||||
// The value "deactivated" should be used to indicate client-initiated deactivation
|
||||
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
|
||||
// whereas "revoked" should be used to indicate server-initiated deactivation. (See Section 7.1.6)
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
// contact (optional, array of string):
|
||||
|
@ -181,6 +181,12 @@ type Order struct {
|
|||
// certificate (optional, string):
|
||||
// A URL for the certificate that has been issued in response to this order
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
|
||||
// replaces (optional, string):
|
||||
// replaces (string, optional): A string uniquely identifying a
|
||||
// previously-issued certificate which this order is intended to replace.
|
||||
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
Replaces string `json:"replaces,omitempty"`
|
||||
}
|
||||
|
||||
// Authorization the ACME authorization object.
|
||||
|
@ -321,7 +327,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 a optional URL pointing to a page which may explain why the suggested renewal window is what it is.
|
||||
// ExplanationURL is an 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.
|
||||
|
@ -329,9 +335,11 @@ type RenewalInfoResponse struct {
|
|||
}
|
||||
|
||||
// RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint.
|
||||
// - (4.2. Updating Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
|
||||
// - (4.2. RenewalInfo Objects) https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
|
||||
type RenewalInfoUpdateRequest struct {
|
||||
// CertID is the base64url-encoded [RFC4648] bytes of a DER-encoded CertID ASN.1 sequence [RFC6960] with any trailing '=' characters stripped.
|
||||
// CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the
|
||||
// certificate's authority key identifier and Serial is the certificate's serial number. For details, see:
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1
|
||||
CertID string `json:"certID"`
|
||||
// Replaced is required and indicates whether or not the client considers the certificate to have been replaced.
|
||||
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -84,11 +85,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, fmt.Errorf("invalid PEM block")
|
||||
return nil, errors.New("invalid PEM block")
|
||||
}
|
||||
|
||||
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
||||
|
@ -216,6 +217,26 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
|||
return x509.ParseCertificate(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
func GetCertificateMainDomain(cert *x509.Certificate) (string, error) {
|
||||
return getMainDomain(cert.Subject, cert.DNSNames)
|
||||
}
|
||||
|
||||
func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) {
|
||||
return getMainDomain(cert.Subject, cert.DNSNames)
|
||||
}
|
||||
|
||||
func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) {
|
||||
if subject.CommonName == "" && len(dnsNames) == 0 {
|
||||
return "", errors.New("missing domain")
|
||||
}
|
||||
|
||||
if subject.CommonName != "" {
|
||||
return subject.CommonName, nil
|
||||
}
|
||||
|
||||
return dnsNames[0], nil
|
||||
}
|
||||
|
||||
func ExtractDomains(cert *x509.Certificate) []string {
|
||||
var domains []string
|
||||
if cert.Subject.CommonName != "" {
|
||||
|
@ -248,7 +269,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
|||
|
||||
// loop over the SubjectAltName DNS names
|
||||
for _, sanName := range csr.DNSNames {
|
||||
if containsSAN(domains, sanName) {
|
||||
if slices.Contains(domains, sanName) {
|
||||
// Duplicate; skip this name
|
||||
continue
|
||||
}
|
||||
|
@ -267,15 +288,6 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
|||
return domains
|
||||
}
|
||||
|
||||
func containsSAN(domains []string, sanName string) bool {
|
||||
for _, existingName := range domains {
|
||||
if existingName == sanName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
||||
if err != nil {
|
||||
|
|
|
@ -39,14 +39,14 @@ func TestGenerateCSR(t *testing.T) {
|
|||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "without SAN",
|
||||
desc: "without SAN (nil)",
|
||||
privateKey: privateKey,
|
||||
domain: "lego.acme",
|
||||
mustStaple: true,
|
||||
expected: expected{len: 245},
|
||||
},
|
||||
{
|
||||
desc: "without SAN",
|
||||
desc: "without SAN (empty)",
|
||||
privateKey: privateKey,
|
||||
domain: "lego.acme",
|
||||
san: []string{},
|
||||
|
@ -86,7 +86,6 @@ func TestGenerateCSR(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -7,18 +7,10 @@ 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 / overallRequestLimit
|
||||
delay := time.Second / time.Duration(c.overallRequestLimit)
|
||||
|
||||
for _, authzURL := range order.Authorizations {
|
||||
time.Sleep(delay)
|
||||
|
@ -35,13 +27,14 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
|||
}
|
||||
|
||||
var responses []acme.Authorization
|
||||
failures := make(obtainError)
|
||||
for i := 0; i < len(order.Authorizations); i++ {
|
||||
|
||||
failures := newObtainError()
|
||||
for range len(order.Authorizations) {
|
||||
select {
|
||||
case res := <-resc:
|
||||
responses = append(responses, res)
|
||||
case err := <-errc:
|
||||
failures[err.Domain] = err.Error
|
||||
failures.Add(err.Domain, err.Error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,12 +45,7 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
|||
close(resc)
|
||||
close(errc)
|
||||
|
||||
// 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
|
||||
return responses, failures.Join()
|
||||
}
|
||||
|
||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
|
||||
|
|
|
@ -22,6 +22,17 @@ 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
|
||||
|
||||
|
@ -63,6 +74,10 @@ type ObtainRequest struct {
|
|||
Bundle bool
|
||||
PreferredChain string
|
||||
AlwaysDeactivateAuthorizations bool
|
||||
// A string uniquely identifying a previously-issued certificate which this
|
||||
// order is intended to replace.
|
||||
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
ReplacesCertID string
|
||||
}
|
||||
|
||||
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
||||
|
@ -79,6 +94,10 @@ type ObtainForCSRRequest struct {
|
|||
Bundle bool
|
||||
PreferredChain string
|
||||
AlwaysDeactivateAuthorizations bool
|
||||
// A string uniquely identifying a previously-issued certificate which this
|
||||
// order is intended to replace.
|
||||
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
ReplacesCertID string
|
||||
}
|
||||
|
||||
type resolver interface {
|
||||
|
@ -86,24 +105,33 @@ type resolver interface {
|
|||
}
|
||||
|
||||
type CertifierOptions struct {
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
OverallRequestLimit int
|
||||
}
|
||||
|
||||
// Certifier A service to obtain/renew/revoke certificates.
|
||||
type Certifier struct {
|
||||
core *api.Core
|
||||
resolver resolver
|
||||
options CertifierOptions
|
||||
core *api.Core
|
||||
resolver resolver
|
||||
options CertifierOptions
|
||||
overallRequestLimit int
|
||||
}
|
||||
|
||||
// NewCertifier creates a Certifier.
|
||||
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
||||
return &Certifier{
|
||||
c := &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.
|
||||
|
@ -124,8 +152,9 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
|||
}
|
||||
|
||||
orderOpts := &api.OrderOptions{
|
||||
NotBefore: request.NotBefore,
|
||||
NotAfter: request.NotAfter,
|
||||
NotBefore: request.NotBefore,
|
||||
NotAfter: request.NotAfter,
|
||||
ReplacesCertID: request.ReplacesCertID,
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||
|
@ -149,11 +178,11 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
|||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
failures := newObtainError()
|
||||
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
failures.Add(challenge.GetTargetedDomain(auth), err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,12 +190,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
|||
c.deactivateAuthorizations(order, true)
|
||||
}
|
||||
|
||||
// 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
|
||||
return cert, failures.Join()
|
||||
}
|
||||
|
||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||
|
@ -194,8 +218,9 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
|||
}
|
||||
|
||||
orderOpts := &api.OrderOptions{
|
||||
NotBefore: request.NotBefore,
|
||||
NotAfter: request.NotAfter,
|
||||
NotBefore: request.NotBefore,
|
||||
NotAfter: request.NotAfter,
|
||||
ReplacesCertID: request.ReplacesCertID,
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
||||
|
@ -219,11 +244,11 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
|||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
failures := newObtainError()
|
||||
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
failures.Add(challenge.GetTargetedDomain(auth), err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,12 +261,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
|||
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
||||
}
|
||||
|
||||
// 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
|
||||
return cert, failures.Join()
|
||||
}
|
||||
|
||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||
|
@ -253,8 +273,10 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
|
|||
}
|
||||
}
|
||||
|
||||
// Determine certificate name(s) based on the authorization resources
|
||||
commonName := domains[0]
|
||||
commonName := ""
|
||||
if len(domains[0]) <= 64 {
|
||||
commonName = domains[0]
|
||||
}
|
||||
|
||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
||||
|
@ -262,7 +284,12 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
|
|||
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||
// "identifiers" or "authorizations" elements in the returned order
|
||||
// object.
|
||||
san := []string{commonName}
|
||||
|
||||
var san []string
|
||||
if commonName != "" {
|
||||
san = append(san, commonName)
|
||||
}
|
||||
|
||||
for _, auth := range order.Identifiers {
|
||||
if auth.Value != commonName {
|
||||
san = append(san, auth.Value)
|
||||
|
@ -284,15 +311,14 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
|
|||
return nil, err
|
||||
}
|
||||
|
||||
commonName := domains[0]
|
||||
certRes := &Resource{
|
||||
Domain: commonName,
|
||||
Domain: domains[0],
|
||||
CertURL: respOrder.Certificate,
|
||||
PrivateKey: privateKeyPem,
|
||||
}
|
||||
|
||||
if respOrder.Status == acme.StatusValid {
|
||||
// if the certificate is available right away, short cut!
|
||||
// if the certificate is available right away, shortcut!
|
||||
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
||||
if errR != nil {
|
||||
return nil, errR
|
||||
|
@ -608,8 +634,13 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Resource{
|
||||
Domain: x509Certs[0].Subject.CommonName,
|
||||
Domain: domain,
|
||||
Certificate: cert,
|
||||
IssuerCertificate: issuer,
|
||||
CertURL: url,
|
||||
|
|
|
@ -1,27 +1,37 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// obtainError is returned when there are specific errors available per domain.
|
||||
type obtainError map[string]error
|
||||
type obtainError struct {
|
||||
data map[string]error
|
||||
}
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||
func newObtainError() *obtainError {
|
||||
return &obtainError{data: make(map[string]error)}
|
||||
}
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
||||
domains = append(domains, domain)
|
||||
func (e *obtainError) Add(domain string, err error) {
|
||||
e.data[domain] = err
|
||||
}
|
||||
|
||||
func (e *obtainError) Join() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
_, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain])
|
||||
if len(e.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return buffer.String()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type domainError struct {
|
||||
|
|
69
certificate/errors_test.go
Normal file
69
certificate/errors_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
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)
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
|
@ -18,16 +15,18 @@ import (
|
|||
|
||||
// RenewalInfoRequest contains the necessary renewal information.
|
||||
type RenewalInfoRequest struct {
|
||||
Cert *x509.Certificate
|
||||
Issuer *x509.Certificate
|
||||
// HashName must be the string representation of a crypto.Hash constant in the golang.org/x/crypto package (e.g. "SHA-256").
|
||||
// The correct value depends on the algorithm expected by the ACME server's ARI implementation.
|
||||
HashName string
|
||||
Cert *x509.Certificate
|
||||
}
|
||||
|
||||
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
|
||||
type RenewalInfoResponse struct {
|
||||
acme.RenewalInfoResponse
|
||||
|
||||
// RetryAfter header indicating the polling interval that the ACME server recommends.
|
||||
// Conforming clients SHOULD query the renewalInfo URL again after the RetryAfter period has passed,
|
||||
// as the server may provide a different suggestedWindow.
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2
|
||||
RetryAfter time.Duration
|
||||
}
|
||||
|
||||
// ShouldRenewAt determines the optimal renewal time based on the current time (UTC),renewal window suggest by ARI, and the client's willingness to sleep.
|
||||
|
@ -42,9 +41,11 @@ func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.D
|
|||
end := r.SuggestedWindow.End.UTC()
|
||||
|
||||
// Select a uniform random time within the suggested window.
|
||||
window := end.Sub(start)
|
||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||
rt := start.Add(randomDuration)
|
||||
rt := start
|
||||
if window := end.Sub(start); window > 0 {
|
||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||
rt = rt.Add(randomDuration)
|
||||
}
|
||||
|
||||
// If the selected time is in the past, attempt renewal immediately.
|
||||
if rt.Before(now) {
|
||||
|
@ -72,7 +73,7 @@ func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.D
|
|||
//
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||
func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) {
|
||||
certID, err := makeCertID(req.Cert, req.Issuer, req.HashName)
|
||||
certID, err := MakeARICertID(req.Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error making certID: %w", err)
|
||||
}
|
||||
|
@ -88,117 +89,43 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if retry := resp.Header.Get("Retry-After"); retry != "" {
|
||||
info.RetryAfter, err = time.ParseDuration(retry + "s")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// UpdateRenewalInfo sends an update to the ACME server's renewal info endpoint to indicate that the client has successfully replaced a certificate.
|
||||
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
||||
// for instance because it has been renewed and the new certificate is in use, or because it is no longer in use.
|
||||
//
|
||||
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
||||
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||
func (c *Certifier) UpdateRenewalInfo(req RenewalInfoRequest) error {
|
||||
certID, err := makeCertID(req.Cert, req.Issuer, req.HashName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making certID: %w", err)
|
||||
}
|
||||
|
||||
_, err = c.core.Certificates.UpdateRenewalInfo(acme.RenewalInfoUpdateRequest{
|
||||
CertID: certID,
|
||||
Replaced: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeCertID returns a base64url-encoded string that uniquely identifies a certificate to endpoints
|
||||
// that implement the draft-ietf-acme-ari specification: https://datatracker.ietf.org/doc/draft-ietf-acme-ari.
|
||||
// hashName must be the string representation of a crypto.Hash constant in the golang.org/x/crypto package.
|
||||
// Supported hash functions are SHA-1, SHA-256, SHA-384, and SHA-512.
|
||||
func makeCertID(leaf, issuer *x509.Certificate, hashName string) (string, error) {
|
||||
// MakeARICertID constructs a certificate identifier as described in draft-ietf-acme-ari-03, section 4.1.
|
||||
func MakeARICertID(leaf *x509.Certificate) (string, error) {
|
||||
if leaf == nil {
|
||||
return "", fmt.Errorf("leaf certificate is nil")
|
||||
}
|
||||
if issuer == nil {
|
||||
return "", fmt.Errorf("issuer certificate is nil")
|
||||
return "", errors.New("leaf certificate is nil")
|
||||
}
|
||||
|
||||
var hashFunc crypto.Hash
|
||||
var oid asn1.ObjectIdentifier
|
||||
|
||||
switch hashName {
|
||||
// The following correlation of hashFunc to OID is copied from a private mapping in golang.org/x/crypto/ocsp:
|
||||
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.8.0:ocsp/ocsp.go;l=156
|
||||
case crypto.SHA1.String():
|
||||
hashFunc = crypto.SHA1
|
||||
oid = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
|
||||
|
||||
case crypto.SHA256.String():
|
||||
hashFunc = crypto.SHA256
|
||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
|
||||
|
||||
case crypto.SHA384.String():
|
||||
hashFunc = crypto.SHA384
|
||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2})
|
||||
|
||||
case crypto.SHA512.String():
|
||||
hashFunc = crypto.SHA512
|
||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3})
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("hashName %q is not supported by this package", hashName)
|
||||
}
|
||||
|
||||
if !hashFunc.Available() {
|
||||
// This should never happen.
|
||||
return "", fmt.Errorf("hash function %q is not available on your platform", hashFunc)
|
||||
}
|
||||
|
||||
var spki struct {
|
||||
Algorithm pkix.AlgorithmIdentifier
|
||||
PublicKey asn1.BitString
|
||||
}
|
||||
|
||||
_, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &spki)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := hashFunc.New()
|
||||
h.Write(spki.PublicKey.RightAlign())
|
||||
issuerKeyHash := h.Sum(nil)
|
||||
|
||||
h.Reset()
|
||||
h.Write(issuer.RawSubject)
|
||||
issuerNameHash := h.Sum(nil)
|
||||
|
||||
type certID struct {
|
||||
HashAlgorithm pkix.AlgorithmIdentifier
|
||||
IssuerNameHash []byte
|
||||
IssuerKeyHash []byte
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// DER-encode the CertID ASN.1 sequence [RFC6960].
|
||||
certIDBytes, err := asn1.Marshal(certID{
|
||||
HashAlgorithm: pkix.AlgorithmIdentifier{
|
||||
Algorithm: oid,
|
||||
},
|
||||
IssuerNameHash: issuerNameHash,
|
||||
IssuerKeyHash: issuerKeyHash,
|
||||
SerialNumber: leaf.SerialNumber,
|
||||
})
|
||||
// Marshal the Serial Number into DER.
|
||||
der, err := asn1.Marshal(leaf.SerialNumber)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// base64url-encode [RFC4648] the bytes of the DER-encoded CertID ASN.1 sequence [RFC6960].
|
||||
encodedBytes := base64.URLEncoding.EncodeToString(certIDBytes)
|
||||
// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
|
||||
// length, and value).
|
||||
if len(der) < 3 {
|
||||
return "", errors.New("invalid DER encoding of serial number")
|
||||
}
|
||||
|
||||
// Any trailing '=' characters MUST be stripped.
|
||||
return strings.TrimRight(encodedBytes, "="), nil
|
||||
// Extract only the integer bytes from the DER encoded Serial Number
|
||||
// Skipping the first 2 bytes (tag and length).
|
||||
serial := base64.RawURLEncoding.EncodeToString(der[2:])
|
||||
|
||||
// Convert the Authority Key Identifier to base64url encoding without
|
||||
// padding.
|
||||
aki := base64.RawURLEncoding.EncodeToString(leaf.AuthorityKeyId)
|
||||
|
||||
// Construct the final identifier by concatenating AKI and Serial Number.
|
||||
return fmt.Sprintf("%s.%s", aki, serial), nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -14,62 +11,28 @@ import (
|
|||
"github.com/go-acme/lego/v4/acme/api"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
ariLeafPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIDMDCCAhigAwIBAgIIPqNFaGVEHxwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MB4XDTIyMDMxNzE3NTEwOVoXDTI0MDQx
|
||||
NjE3NTEwOVowFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCgm9K/c+il2Pf0f8qhgxn9SKqXq88cOm9ov9AVRbPA
|
||||
OWAAewqX2yUAwI4LZBGEgzGzTATkiXfoJ3cN3k39cH6tBbb3iSPuEn7OZpIk9D+e
|
||||
3Q9/hX+N/jlWkaTB/FNA+7aE5IVWhmdczYilXa10V9r+RcvACJt0gsipBZVJ4jfJ
|
||||
HnWJJGRZzzxqG/xkQmpXxZO7nOPFc8SxYKWdfcgp+rjR2ogYhSz7BfKoVakGPbpX
|
||||
vZOuT9z4kkHra/WjwlkQhtHoTXdAxH3qC2UjMzO57Tx+otj0CxAv9O7CTJXISywB
|
||||
vEVcmTSZkHS3eZtvvIwPx7I30ITRkYk/tLl1MbyB3SiZAgMBAAGjeDB2MA4GA1Ud
|
||||
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
|
||||
AQH/BAIwADAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTAWBgNVHREE
|
||||
DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAx0aYvmCk7JYGNEXe
|
||||
+hrOfKawkHYzWvA92cI/Oi6h+oSdHZ2UKzwFNf37cVKZ37FCrrv5pFP/xhhHvrNV
|
||||
EnOx4IaF7OrnaTu5miZiUWuvRQP7ZGmGNFYbLTEF6/dj+WqyYdVaWzxRqHFu1ptC
|
||||
TXysJCeyiGnR+KOOjOOQ9ZlO5JUK3OE4hagPLfaIpDDy6RXQt3ss0iNLuB1+IOtp
|
||||
1URpvffLZQ8xPsEgOZyPWOcabTwJrtqBwily+lwPFn2mChUx846LwQfxtsXU/lJg
|
||||
HX2RteNJx7YYNeX3Uf960mgo5an6vE8QNAsIoNHYrGyEmXDhTRe9mCHyiW2S7fZq
|
||||
o9q12g==
|
||||
MIIBQzCB66ADAgECAgUAh2VDITAKBggqhkjOPQQDAjAVMRMwEQYDVQQDEwpFeGFt
|
||||
cGxlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBYxFDAS
|
||||
BgNVBAMTC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeBZu
|
||||
7cbpAYNXZLbbh8rNIzuOoqOOtmxA1v7cRm//AwyMwWxyHz4zfwmBhcSrf47NUAFf
|
||||
qzLQ2PPQxdTXREYEnKMjMCEwHwYDVR0jBBgwFoAUaYhba4dGQEHhs3uEe6CuLN4B
|
||||
yNQwCgYIKoZIzj0EAwIDRwAwRAIge09+S5TZAlw5tgtiVvuERV6cT4mfutXIlwTb
|
||||
+FYN/8oCIClDsqBklhB9KAelFiYt9+6FDj3z4KGVelYM5MdsO3pK
|
||||
-----END CERTIFICATE-----`
|
||||
ariIssuerPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIIOhNWtJ7Igr0wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MCAXDTIyMDMxNzE3NTEwOVoYDzIxMjIw
|
||||
MzE3MTc1MTA5WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAzYTEzNTYwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDc3P6cxcCZ7FQOQrYuigReSa8T
|
||||
IOPNKmlmX9OrTkPwjThiMNEETYKO1ea99yXPK36LUHC6OLmZ9jVQW2Ny1qwQCOy6
|
||||
TrquhnwKgtkBMDAZBLySSEXYdKL3r0jA4sflW130/OLwhstU/yv0J8+pj7eSVOR3
|
||||
zJBnYd1AqnXHRSwQm299KXgqema7uwsa8cgjrXsBzAhrwrvYlVhpWFSv3lQRDFQg
|
||||
c5Z/ZDV9i26qiaJsCCmdisJZWN7N2luUgxdRqzZ4Cr2Xoilg3T+hkb2y/d6ttsPA
|
||||
kaSA+pq3q6Qa7/qfGdT5WuUkcHpvKNRWqnwT9rCYlmG00r3hGgc42D/z1VvfAgMB
|
||||
AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||
BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ4zzDRUaXHVKql
|
||||
STWkULGU4zGZpTAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEArbDHhEjGedjb/YjU80aFTPWOMRjgyfQaPPgyxwX6Dsid
|
||||
1i2H1x4ud4ntz3sTZZxdQIrOqtlIWTWVCjpStwGxaC+38SdreiTTwy/nikXGa/6W
|
||||
ZyQRppR3agh/pl5LHVO6GsJz3YHa7wQhEhj3xsRwa9VrRXgHbLGbPOFVRTHPjaPg
|
||||
Gtsv2PN3f67DsPHF47ASqyOIRpLZPQmZIw6D3isJwfl+8CzvlB1veO0Q3uh08IJc
|
||||
fspYQXvFBzYa64uKxNAJMi4Pby8cf4r36Wnb7cL4ho3fOHgAltxdW8jgibRzqZpQ
|
||||
QKyxn2jX7kxeUDt0hFDJE8lOrhP73m66eBNzxe//FQ==
|
||||
-----END CERTIFICATE-----`
|
||||
ariLeafCertID = "MFswCwYJYIZIAWUDBAIBBCCeWLRusNLb--vmWOkxm34qDjTMWkc3utIhOMoMwKDqbgQg2iiKWySZrD-6c88HMZ6vhIHZPamChLlzGHeZ7pTS8jYCCD6jRWhlRB8c"
|
||||
ariLeafCertID = "aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE"
|
||||
)
|
||||
|
||||
func Test_makeCertID(t *testing.T) {
|
||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||
require.NoError(t, err)
|
||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := makeCertID(leaf, issuer, crypto.SHA256.String())
|
||||
actual, err := MakeARICertID(leaf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ariLeafCertID, actual)
|
||||
}
|
||||
|
@ -77,8 +40,6 @@ func Test_makeCertID(t *testing.T) {
|
|||
func TestCertifier_GetRenewalInfo(t *testing.T) {
|
||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||
require.NoError(t, err)
|
||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with a fake API.
|
||||
mux, apiURL := tester.SetupFakeAPI(t)
|
||||
|
@ -89,6 +50,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Retry-After", "21600")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, wErr := w.Write([]byte(`{
|
||||
"suggestedWindow": {
|
||||
|
@ -109,19 +71,18 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
|
|||
|
||||
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
|
||||
|
||||
ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()})
|
||||
ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ri)
|
||||
assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339))
|
||||
assert.Equal(t, "2020-03-17T18:21:09Z", ri.SuggestedWindow.End.Format(time.RFC3339))
|
||||
assert.Equal(t, "https://aricapable.ca/docs/renewal-advice/", ri.ExplanationURL)
|
||||
assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter)
|
||||
}
|
||||
|
||||
func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||
require.NoError(t, err)
|
||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err, "Could not generate test key")
|
||||
|
@ -135,7 +96,7 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
|||
{
|
||||
desc: "API timeout",
|
||||
httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms.
|
||||
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
|
||||
request: RenewalInfoRequest{leaf},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
// API that takes 2ms to respond.
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
|
@ -144,24 +105,15 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
|||
{
|
||||
desc: "API error",
|
||||
httpClient: http.DefaultClient,
|
||||
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
|
||||
request: RenewalInfoRequest{leaf},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
// API that responds with error instead of renewal info.
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Issuer certificate is nil",
|
||||
httpClient: http.DefaultClient,
|
||||
request: RenewalInfoRequest{leaf, nil, crypto.SHA256.String()},
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -180,105 +132,19 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCertifier_UpdateRenewalInfo(t *testing.T) {
|
||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||
require.NoError(t, err)
|
||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err, "Could not generate test key")
|
||||
|
||||
// Test with a fake API.
|
||||
mux, apiURL := tester.SetupFakeAPI(t)
|
||||
mux.HandleFunc("/renewalInfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, rsbErr := readSignedBody(r, key)
|
||||
if rsbErr != nil {
|
||||
http.Error(w, rsbErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req acme.RenewalInfoUpdateRequest
|
||||
err = json.Unmarshal(body, &req)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, req.Replaced)
|
||||
assert.Equal(t, ariLeafCertID, req.CertID)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key)
|
||||
require.NoError(t, err)
|
||||
|
||||
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
|
||||
|
||||
err = certifier.UpdateRenewalInfo(RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCertifier_UpdateRenewalInfo_errors(t *testing.T) {
|
||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||
require.NoError(t, err)
|
||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err, "Could not generate test key")
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
request RenewalInfoRequest
|
||||
}{
|
||||
{
|
||||
desc: "API error",
|
||||
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
|
||||
},
|
||||
{
|
||||
desc: "Certificate is nil",
|
||||
request: RenewalInfoRequest{nil, issuer, crypto.SHA256.String()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mux, apiURL := tester.SetupFakeAPI(t)
|
||||
|
||||
// Always returns an error.
|
||||
mux.HandleFunc("/renewalInfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
})
|
||||
|
||||
core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key)
|
||||
require.NoError(t, err)
|
||||
|
||||
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
|
||||
|
||||
err = certifier.UpdateRenewalInfo(test.request)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
|
||||
t.Run("Window is in the past", func(t *testing.T) {
|
||||
ri := RenewalInfoResponse{
|
||||
acme.RenewalInfoResponse{
|
||||
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||
SuggestedWindow: acme.Window{
|
||||
Start: now.Add(-2 * time.Hour),
|
||||
End: now.Add(-1 * time.Hour),
|
||||
},
|
||||
ExplanationURL: "",
|
||||
},
|
||||
RetryAfter: 0,
|
||||
}
|
||||
|
||||
rt := ri.ShouldRenewAt(now, 0)
|
||||
|
@ -288,13 +154,14 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
|
|||
|
||||
t.Run("Window is in the future", func(t *testing.T) {
|
||||
ri := RenewalInfoResponse{
|
||||
acme.RenewalInfoResponse{
|
||||
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||
SuggestedWindow: acme.Window{
|
||||
Start: now.Add(1 * time.Hour),
|
||||
End: now.Add(2 * time.Hour),
|
||||
},
|
||||
ExplanationURL: "",
|
||||
},
|
||||
RetryAfter: 0,
|
||||
}
|
||||
|
||||
rt := ri.ShouldRenewAt(now, 0)
|
||||
|
@ -303,13 +170,14 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
|
|||
|
||||
t.Run("Window is in the future, but caller is willing to sleep", func(t *testing.T) {
|
||||
ri := RenewalInfoResponse{
|
||||
acme.RenewalInfoResponse{
|
||||
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||
SuggestedWindow: acme.Window{
|
||||
Start: now.Add(1 * time.Hour),
|
||||
End: now.Add(2 * time.Hour),
|
||||
},
|
||||
ExplanationURL: "",
|
||||
},
|
||||
RetryAfter: 0,
|
||||
}
|
||||
|
||||
rt := ri.ShouldRenewAt(now, 2*time.Hour)
|
||||
|
@ -319,38 +187,17 @@ func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
|
|||
|
||||
t.Run("Window is in the future, but caller isn't willing to sleep long enough", func(t *testing.T) {
|
||||
ri := RenewalInfoResponse{
|
||||
acme.RenewalInfoResponse{
|
||||
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||
SuggestedWindow: acme.Window{
|
||||
Start: now.Add(1 * time.Hour),
|
||||
End: now.Add(2 * time.Hour),
|
||||
},
|
||||
ExplanationURL: "",
|
||||
},
|
||||
RetryAfter: 0,
|
||||
}
|
||||
|
||||
rt := ri.ShouldRenewAt(now, 59*time.Minute)
|
||||
assert.Nil(t, rt)
|
||||
})
|
||||
}
|
||||
|
||||
func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jws, err := jose.ParseSigned(string(reqBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := jws.Verify(&jose.JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
Algorithm: "RSA",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
|
@ -124,7 +125,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
|||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
|
||||
log.Infof("[%s] acme: Checking DNS record propagation. [nameservers=%s]", domain, strings.Join(recursiveNameservers, ","))
|
||||
|
||||
time.Sleep(interval)
|
||||
|
||||
|
@ -214,7 +215,7 @@ func getChallengeFQDN(domain string, followCNAME bool) string {
|
|||
}
|
||||
|
||||
// recursion counter so it doesn't spin out of control
|
||||
for limit := 0; limit < 50; limit++ {
|
||||
for range 50 {
|
||||
// Keep following CNAMEs
|
||||
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
|||
|
||||
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("manual: could not find zone: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
||||
|
@ -33,8 +33,11 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
|||
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||
|
||||
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("manual: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp prints instructions for manually removing the TXT record.
|
||||
|
@ -43,7 +46,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("manual: could not find zone: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -32,14 +31,14 @@ func TestDNSProviderManual(t *testing.T) {
|
|||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
file, err := os.CreateTemp("", "lego_test")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = os.Remove(file.Name()) }()
|
||||
|
||||
_, err = file.WriteString(test.input)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Stdin = file
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ func TestExtractSubDomain(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -93,7 +92,6 @@ func TestExtractSubDomain_errors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -14,18 +14,17 @@ func TestToFqdn(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
desc: "simple",
|
||||
domain: "foo.bar.com",
|
||||
expected: "foo.bar.com.",
|
||||
domain: "foo.example.com",
|
||||
expected: "foo.example.com.",
|
||||
},
|
||||
{
|
||||
desc: "already FQDN",
|
||||
domain: "foo.bar.com.",
|
||||
expected: "foo.bar.com.",
|
||||
domain: "foo.example.com.",
|
||||
expected: "foo.example.com.",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -43,18 +42,17 @@ func TestUnFqdn(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
desc: "simple",
|
||||
fqdn: "foo.bar.com.",
|
||||
expected: "foo.bar.com",
|
||||
fqdn: "foo.example.",
|
||||
expected: "foo.example",
|
||||
},
|
||||
{
|
||||
desc: "already domain",
|
||||
fqdn: "foo.bar.com",
|
||||
expected: "foo.bar.com",
|
||||
fqdn: "foo.example",
|
||||
expected: "foo.example",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -15,10 +16,7 @@ import (
|
|||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
||||
|
||||
var (
|
||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||
muFqdnSoaCache sync.Mutex
|
||||
)
|
||||
var fqdnSoaCache = &sync.Map{}
|
||||
|
||||
var defaultNameservers = []string{
|
||||
"google-public-dns-a.google.com:53",
|
||||
|
@ -50,9 +48,11 @@ func (cache *soaCacheEntry) isExpired() bool {
|
|||
|
||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||
func ClearFqdnCache() {
|
||||
muFqdnSoaCache.Lock()
|
||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||
muFqdnSoaCache.Unlock()
|
||||
// TODO(ldez): use `fqdnSoaCache.Clear()` when updating to go1.23
|
||||
fqdnSoaCache.Range(func(k, v any) bool {
|
||||
fqdnSoaCache.Delete(k)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
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 determine the zone: %w", err)
|
||||
return nil, fmt.Errorf("could not find zone: %w", err)
|
||||
}
|
||||
|
||||
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("NS call failed: %w", err)
|
||||
}
|
||||
|
||||
for _, rr := range r.Answer {
|
||||
|
@ -115,7 +115,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
|||
if len(authoritativeNss) > 0 {
|
||||
return authoritativeNss, nil
|
||||
}
|
||||
return nil, errors.New("could not determine authoritative nameservers")
|
||||
|
||||
return nil, fmt.Errorf("[zone=%s] could not determine authoritative nameservers", zone)
|
||||
}
|
||||
|
||||
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
||||
|
@ -129,7 +130,7 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
|
|||
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||
}
|
||||
return soa.primaryNs, nil
|
||||
}
|
||||
|
@ -145,18 +146,19 @@ func FindZoneByFqdn(fqdn string) (string, error) {
|
|||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||
}
|
||||
return soa.zone, nil
|
||||
}
|
||||
|
||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
muFqdnSoaCache.Lock()
|
||||
defer muFqdnSoaCache.Unlock()
|
||||
|
||||
// Do we have it cached and is it still fresh?
|
||||
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
|
||||
return ent, nil
|
||||
entAny, ok := fqdnSoaCache.Load(fqdn)
|
||||
if ok && entAny != nil {
|
||||
ent, ok1 := entAny.(*soaCacheEntry)
|
||||
if ok1 && !ent.isExpired() {
|
||||
return ent, nil
|
||||
}
|
||||
}
|
||||
|
||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||
|
@ -164,41 +166,42 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
fqdnSoaCache[fqdn] = ent
|
||||
fqdnSoaCache.Store(fqdn, ent)
|
||||
|
||||
return ent, nil
|
||||
}
|
||||
|
||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||
var err error
|
||||
var in *dns.Msg
|
||||
var r *dns.Msg
|
||||
|
||||
labelIndexes := dns.Split(fqdn)
|
||||
for _, index := range labelIndexes {
|
||||
domain := fqdn[index:]
|
||||
|
||||
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if in == nil {
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch in.Rcode {
|
||||
switch r.Rcode {
|
||||
case dns.RcodeSuccess:
|
||||
// Check if we got a SOA RR in the answer section
|
||||
if len(in.Answer) == 0 {
|
||||
if len(r.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// CNAME records cannot/should not exist at the root of a zone.
|
||||
// So we skip a domain when a CNAME is found.
|
||||
if dnsMsgContainsCNAME(in) {
|
||||
if dnsMsgContainsCNAME(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range in.Answer {
|
||||
for _, ans := range r.Answer {
|
||||
if soa, ok := ans.(*dns.SOA); ok {
|
||||
return newSoaCacheEntry(soa), nil
|
||||
}
|
||||
|
@ -207,36 +210,46 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
|||
// NXDOMAIN
|
||||
default:
|
||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||
return nil, &DNSError{Message: fmt.Sprintf("unexpected response for '%s'", domain), MsgOut: r}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||
return nil, &DNSError{Message: fmt.Sprintf("could not find the start of authority for '%s'", fqdn), MsgOut: r, Err: err}
|
||||
}
|
||||
|
||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||
for _, ans := range msg.Answer {
|
||||
if _, ok := ans.(*dns.CNAME); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
|
||||
_, ok := rr.(*dns.CNAME)
|
||||
return ok
|
||||
})
|
||||
}
|
||||
|
||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||
m := createDNSMsg(fqdn, rtype, recursive)
|
||||
|
||||
var in *dns.Msg
|
||||
if len(nameservers) == 0 {
|
||||
return nil, &DNSError{Message: "empty list of nameservers"}
|
||||
}
|
||||
|
||||
var r *dns.Msg
|
||||
var err error
|
||||
var errAll error
|
||||
|
||||
for _, ns := range nameservers {
|
||||
in, err = sendDNSQuery(m, ns)
|
||||
if err == nil && len(in.Answer) > 0 {
|
||||
r, err = sendDNSQuery(m, ns)
|
||||
if err == nil && len(r.Answer) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
errAll = errors.Join(errAll, err)
|
||||
}
|
||||
return in, err
|
||||
|
||||
if err != nil {
|
||||
return r, errAll
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||
|
@ -254,37 +267,82 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
|||
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
|
||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||
in, _, err := tcp.Exchange(m, ns)
|
||||
r, _, err := tcp.Exchange(m, ns)
|
||||
if err != nil {
|
||||
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
||||
}
|
||||
|
||||
return in, err
|
||||
return r, nil
|
||||
}
|
||||
|
||||
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||
in, _, err := udp.Exchange(m, ns)
|
||||
r, _, err := udp.Exchange(m, ns)
|
||||
|
||||
if in != nil && in.Truncated {
|
||||
if r != nil && r.Truncated {
|
||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||
// If the TCP request succeeds, the err will reset to nil
|
||||
in, _, err = tcp.Exchange(m, ns)
|
||||
}
|
||||
|
||||
return in, err
|
||||
}
|
||||
|
||||
func formatDNSError(msg *dns.Msg, err error) string {
|
||||
var parts []string
|
||||
|
||||
if msg != nil {
|
||||
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
||||
// If the TCP request succeeds, the "err" will reset to nil
|
||||
r, _, err = tcp.Exchange(m, ns)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
parts = append(parts, err.Error())
|
||||
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
return ": " + strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
return ""
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// DNSError error related to DNS calls.
|
||||
type DNSError struct {
|
||||
Message string
|
||||
NS string
|
||||
MsgIn *dns.Msg
|
||||
MsgOut *dns.Msg
|
||||
Err error
|
||||
}
|
||||
|
||||
func (d *DNSError) Error() string {
|
||||
var details []string
|
||||
if d.NS != "" {
|
||||
details = append(details, "ns="+d.NS)
|
||||
}
|
||||
|
||||
if d.MsgIn != nil && len(d.MsgIn.Question) > 0 {
|
||||
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgIn.Question)))
|
||||
}
|
||||
|
||||
if d.MsgOut != nil {
|
||||
if d.MsgIn == nil || len(d.MsgIn.Question) == 0 {
|
||||
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgOut.Question)))
|
||||
}
|
||||
|
||||
details = append(details, "code="+dns.RcodeToString[d.MsgOut.Rcode])
|
||||
}
|
||||
|
||||
msg := "DNS error"
|
||||
if d.Message != "" {
|
||||
msg = d.Message
|
||||
}
|
||||
|
||||
if d.Err != nil {
|
||||
msg += ": " + d.Err.Error()
|
||||
}
|
||||
|
||||
if len(details) > 0 {
|
||||
msg += " [" + strings.Join(details, ", ") + "]"
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (d *DNSError) Unwrap() error {
|
||||
return d.Err
|
||||
}
|
||||
|
||||
func formatQuestions(questions []dns.Question) string {
|
||||
var parts []string
|
||||
for _, question := range questions {
|
||||
parts = append(parts, strings.ReplaceAll(strings.TrimPrefix(question.String(), ";"), "\t", " "))
|
||||
}
|
||||
|
||||
return strings.Join(parts, ";")
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -28,7 +30,6 @@ func TestLookupNameserversOK(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.fqdn, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -52,12 +53,11 @@ func TestLookupNameserversErr(t *testing.T) {
|
|||
{
|
||||
desc: "invalid tld",
|
||||
fqdn: "_null.n0n0.",
|
||||
error: "could not determine the zone",
|
||||
error: "could not find zone",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -109,7 +109,7 @@ var findXByFqdnTestCases = []struct {
|
|||
fqdn: "test.lego.zz.",
|
||||
zone: "lego.zz.",
|
||||
nameservers: []string{"8.8.8.8:53"},
|
||||
expectedError: "could not find the start of authority for test.lego.zz.: NXDOMAIN",
|
||||
expectedError: "[fqdn=test.lego.zz.] could not find the start of authority for 'test.lego.zz.' [question='zz. IN SOA', code=NXDOMAIN]",
|
||||
},
|
||||
{
|
||||
desc: "several non existent nameservers",
|
||||
|
@ -119,18 +119,19 @@ var findXByFqdnTestCases = []struct {
|
|||
nameservers: []string{":7053", ":8053", "8.8.8.8:53"},
|
||||
},
|
||||
{
|
||||
desc: "only non-existent nameservers",
|
||||
fqdn: "mail.google.com.",
|
||||
zone: "google.com.",
|
||||
nameservers: []string{":7053", ":8053", ":9053"},
|
||||
expectedError: "could not find the start of authority for mail.google.com.: read udp",
|
||||
desc: "only non-existent nameservers",
|
||||
fqdn: "mail.google.com.",
|
||||
zone: "google.com.",
|
||||
nameservers: []string{":7053", ":8053", ":9053"},
|
||||
// use only the start of the message because the port changes with each call: 127.0.0.1:XXXXX->127.0.0.1:7053.
|
||||
expectedError: "[fqdn=mail.google.com.] could not find the start of authority for 'mail.google.com.': DNS call error: read udp ",
|
||||
},
|
||||
{
|
||||
desc: "no nameservers",
|
||||
fqdn: "test.ldez.com.",
|
||||
zone: "ldez.com.",
|
||||
nameservers: []string{},
|
||||
expectedError: "could not find the start of authority for test.ldez.com.",
|
||||
expectedError: "[fqdn=test.ldez.com.] could not find the start of authority for 'test.ldez.com.': empty list of nameservers",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -142,7 +143,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) {
|
|||
zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers)
|
||||
if test.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
assert.ErrorContains(t, err, test.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.zone, zone)
|
||||
|
@ -159,7 +160,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) {
|
|||
ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers)
|
||||
if test.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
assert.ErrorContains(t, err, test.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.primaryNs, ns)
|
||||
|
@ -197,3 +198,69 @@ func TestResolveConfServers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSError_Error(t *testing.T) {
|
||||
msgIn := createDNSMsg("example.com.", dns.TypeTXT, true)
|
||||
|
||||
msgOut := createDNSMsg("example.org.", dns.TypeSOA, true)
|
||||
msgOut.Rcode = dns.RcodeNameError
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
err *DNSError
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "empty error",
|
||||
err: &DNSError{},
|
||||
expected: "DNS error",
|
||||
},
|
||||
{
|
||||
desc: "all fields",
|
||||
err: &DNSError{
|
||||
Message: "Oops",
|
||||
NS: "example.com.",
|
||||
MsgIn: msgIn,
|
||||
MsgOut: msgOut,
|
||||
Err: errors.New("I did it again"),
|
||||
},
|
||||
expected: "Oops: I did it again [ns=example.com., question='example.com. IN TXT', code=NXDOMAIN]",
|
||||
},
|
||||
{
|
||||
desc: "only NS",
|
||||
err: &DNSError{
|
||||
NS: "example.com.",
|
||||
},
|
||||
expected: "DNS error [ns=example.com.]",
|
||||
},
|
||||
{
|
||||
desc: "only MsgIn",
|
||||
err: &DNSError{
|
||||
MsgIn: msgIn,
|
||||
},
|
||||
expected: "DNS error [question='example.com. IN TXT']",
|
||||
},
|
||||
{
|
||||
desc: "only MsgOut",
|
||||
err: &DNSError{
|
||||
MsgOut: msgOut,
|
||||
},
|
||||
expected: "DNS error [question='example.org. IN SOA', code=NXDOMAIN]",
|
||||
},
|
||||
{
|
||||
desc: "only Err",
|
||||
err: &DNSError{
|
||||
Err: errors.New("I did it again"),
|
||||
},
|
||||
expected: "DNS error: I did it again",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.EqualError(t, test.err, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
@ -23,23 +24,52 @@ 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.requireCompletePropagation = false
|
||||
chlg.preCheck.requireAuthoritativeNssPropagation = 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
|
||||
requireCompletePropagation bool
|
||||
requireAuthoritativeNssPropagation bool
|
||||
|
||||
// require the TXT record to be propagated to all recursive name servers
|
||||
requireRecursiveNssPropagation bool
|
||||
}
|
||||
|
||||
func newPreCheck() preCheck {
|
||||
return preCheck{
|
||||
requireCompletePropagation: true,
|
||||
requireAuthoritativeNssPropagation: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,32 +83,43 @@ 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
|
||||
// Initial attempt to resolve at the recursive NS (require to get CNAME)
|
||||
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 checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||
return checkNameserversPropagation(fqdn, value, authoritativeNss, true)
|
||||
}
|
||||
|
||||
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
|
||||
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
||||
// checkNameserversPropagation queries each of the given nameservers for the expected TXT record.
|
||||
func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) {
|
||||
for _, ns := range nameservers {
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
|
||||
if addPort {
|
||||
ns = net.JoinHostPort(ns, "53")
|
||||
}
|
||||
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ func TestCheckDNSPropagation(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ClearFqdnCache()
|
||||
|
@ -37,8 +36,8 @@ func TestCheckDNSPropagation(t *testing.T) {
|
|||
|
||||
ok, err := check.checkDNSPropagation(test.fqdn, test.value)
|
||||
if test.expectError {
|
||||
assert.Errorf(t, err, "PreCheckDNS must failed for %s", test.fqdn)
|
||||
assert.False(t, ok, "PreCheckDNS must failed for %s", test.fqdn)
|
||||
assert.Errorf(t, err, "PreCheckDNS must fail for %s", test.fqdn)
|
||||
assert.False(t, ok, "PreCheckDNS must fail for %s", test.fqdn)
|
||||
} else {
|
||||
assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn)
|
||||
assert.True(t, ok, "PreCheckDNS failed for %s", test.fqdn)
|
||||
|
@ -69,12 +68,11 @@ func TestCheckAuthoritativeNss(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ClearFqdnCache()
|
||||
|
||||
ok, _ := checkAuthoritativeNss(test.fqdn, test.value, test.ns)
|
||||
ok, _ := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
|
||||
assert.Equal(t, test.expected, ok, test.fqdn)
|
||||
})
|
||||
}
|
||||
|
@ -104,12 +102,11 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ClearFqdnCache()
|
||||
|
||||
_, err := checkAuthoritativeNss(test.fqdn, test.value, test.ns)
|
||||
_, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.error)
|
||||
})
|
||||
|
|
|
@ -57,7 +57,7 @@ func (m *hostMatcher) matches(r *http.Request, domain string) bool {
|
|||
return strings.HasPrefix(r.Host, domain)
|
||||
}
|
||||
|
||||
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
||||
// arbitraryMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
||||
type arbitraryMatcher string
|
||||
|
||||
func (m arbitraryMatcher) name() string {
|
||||
|
|
|
@ -69,7 +69,6 @@ func TestParseForwardedHeader(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -57,7 +57,6 @@ func TestProviderServer_GetAddress(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -99,7 +99,6 @@ func TestProber_Solve(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/go-acme/lego/v4/acme"
|
||||
"github.com/go-acme/lego/v4/acme/api"
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -158,7 +158,8 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error {
|
|||
return err
|
||||
}
|
||||
|
||||
jws, err := jose.ParseSigned(string(reqBody))
|
||||
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
|
||||
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ func (s *ProviderServer) GetAddress() string {
|
|||
return net.JoinHostPort(s.iface, s.port)
|
||||
}
|
||||
|
||||
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
||||
// Present generates a certificate with an 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 == "" {
|
||||
|
|
|
@ -24,7 +24,7 @@ func TestChallenge(t *testing.T) {
|
|||
_, apiURL := tester.SetupFakeAPI(t)
|
||||
|
||||
domain := "localhost"
|
||||
port := "23457"
|
||||
port := "24457"
|
||||
|
||||
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
|
||||
conn, err := tls.Dial("tcp", net.JoinHostPort(domain, port), &tls.Config{
|
||||
|
@ -75,7 +75,7 @@ func TestChallenge(t *testing.T) {
|
|||
solver := NewChallenge(
|
||||
core,
|
||||
mockValidate,
|
||||
&ProviderServer{port: "23457"},
|
||||
&ProviderServer{port: port},
|
||||
)
|
||||
|
||||
authz := acme.Authorization{
|
||||
|
@ -126,7 +126,7 @@ func TestChallengeIPaddress(t *testing.T) {
|
|||
_, apiURL := tester.SetupFakeAPI(t)
|
||||
|
||||
domain := "127.0.0.1"
|
||||
port := "23457"
|
||||
port := "24457"
|
||||
rd, _ := dns.ReverseAddr(domain)
|
||||
|
||||
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
|
||||
|
@ -141,7 +141,7 @@ func TestChallengeIPaddress(t *testing.T) {
|
|||
assert.Len(t, connState.PeerCertificates, 1, "Expected the challenge server to return exactly one certificate")
|
||||
|
||||
remoteCert := connState.PeerCertificates[0]
|
||||
assert.Len(t, remoteCert.DNSNames, 0, "Expected the challenge certificate to have no DNSNames entry in context of challenge for IP")
|
||||
assert.Empty(t, remoteCert.DNSNames, "Expected the challenge certificate to have no DNSNames entry in context of challenge for IP")
|
||||
assert.Len(t, remoteCert.IPAddresses, 1, "Expected the challenge certificate to have exactly one IPAddresses entry")
|
||||
assert.True(t, net.ParseIP("127.0.0.1").Equal(remoteCert.IPAddresses[0]), "challenge certificate IPAddress ")
|
||||
assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions")
|
||||
|
@ -176,7 +176,7 @@ func TestChallengeIPaddress(t *testing.T) {
|
|||
solver := NewChallenge(
|
||||
core,
|
||||
mockValidate,
|
||||
&ProviderServer{port: "23457"},
|
||||
&ProviderServer{port: port},
|
||||
)
|
||||
|
||||
authz := acme.Authorization{
|
||||
|
|
|
@ -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("server"))
|
||||
serverURL, err := url.Parse(ctx.String(flgServer))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rootPath := filepath.Join(ctx.String("path"), baseAccountsRootFolderName)
|
||||
rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName)
|
||||
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
||||
accountsPath := filepath.Join(rootPath, serverPath)
|
||||
rootUserPath := filepath.Join(accountsPath, 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("server")
|
||||
config.CADirURL = ctx.String(flgServer)
|
||||
config.UserAgent = getUserAgent(ctx)
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
|
|
|
@ -3,10 +3,10 @@ package cmd
|
|||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -55,18 +55,28 @@ 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("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"),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,14 +228,9 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
|
|||
return fmt.Errorf("unable to load Certificate for domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
issuerCertPemBlock, _ := pem.Decode(certRes.IssuerCertificate)
|
||||
if issuerCertPemBlock == nil {
|
||||
return fmt.Errorf("unable to parse Issuer Certificate for domain %s", domain)
|
||||
}
|
||||
|
||||
issuerCert, err := x509.ParseCertificate(issuerCertPemBlock.Bytes)
|
||||
certChain, err := getCertificateChain(certRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load Issuer Certificate for domain %s: %w", domain, err)
|
||||
return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
keyPemBlock, _ := pem.Decode(certRes.PrivateKey)
|
||||
|
@ -251,7 +256,12 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R
|
|||
return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain)
|
||||
}
|
||||
|
||||
pfxBytes, err := pkcs12.Encode(rand.Reader, privateKey, cert, []*x509.Certificate{issuerCert}, s.pfxPassword)
|
||||
encoder, err := getPFXEncoder(s.pfxFormat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PFX encoder: %w", err)
|
||||
}
|
||||
|
||||
pfxBytes, err := encoder.Encode(privateKey, cert, certChain, s.pfxPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
|
||||
}
|
||||
|
@ -285,6 +295,42 @@ func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, error) {
|
||||
chainCertPemBlock, rest := pem.Decode(certRes.IssuerCertificate)
|
||||
if chainCertPemBlock == nil {
|
||||
return nil, errors.New("unable to parse Issuer Certificate")
|
||||
}
|
||||
|
||||
var certChain []*x509.Certificate
|
||||
for chainCertPemBlock != nil {
|
||||
chainCert, err := x509.ParseCertificate(chainCertPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse Chain Certificate: %w", err)
|
||||
}
|
||||
|
||||
certChain = append(certChain, chainCert)
|
||||
chainCertPemBlock, rest = pem.Decode(rest) // Try decoding the next pem block
|
||||
}
|
||||
|
||||
return certChain, nil
|
||||
}
|
||||
|
||||
func getPFXEncoder(pfxFormat string) (*pkcs12.Encoder, error) {
|
||||
var encoder *pkcs12.Encoder
|
||||
switch pfxFormat {
|
||||
case "SHA256":
|
||||
encoder = pkcs12.Modern2023
|
||||
case "DES":
|
||||
encoder = pkcs12.LegacyDES
|
||||
case "RC2":
|
||||
encoder = pkcs12.LegacyRC2
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid PFX format: %s", pfxFormat)
|
||||
}
|
||||
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
||||
func sanitizedDomain(domain string) string {
|
||||
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
|
||||
|
|
|
@ -6,17 +6,17 @@ import (
|
|||
)
|
||||
|
||||
func Before(ctx *cli.Context) error {
|
||||
if ctx.String("path") == "" {
|
||||
log.Fatal("Could not determine current working directory. Please pass --path.")
|
||||
if ctx.String(flgPath) == "" {
|
||||
log.Fatalf("Could not determine current working directory. Please pass --%s.", flgPath)
|
||||
}
|
||||
|
||||
err := createNonExistingFolder(ctx.String("path"))
|
||||
err := createNonExistingFolder(ctx.String(flgPath))
|
||||
if err != nil {
|
||||
log.Fatalf("Could not check/create path: %v", err)
|
||||
}
|
||||
|
||||
if ctx.String("server") == "" {
|
||||
log.Fatal("Could not determine current working server. Please pass --server.")
|
||||
if ctx.String(flgServer) == "" {
|
||||
log.Fatalf("Could not determine current working server. Please pass --%s.", flgServer)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const flgCode = "code"
|
||||
|
||||
func createDNSHelp() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "dnshelp",
|
||||
|
@ -16,7 +18,7 @@ func createDNSHelp() *cli.Command {
|
|||
Action: dnsHelp,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "code",
|
||||
Name: flgCode,
|
||||
Aliases: []string{"c"},
|
||||
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
|
||||
},
|
||||
|
@ -25,7 +27,7 @@ func createDNSHelp() *cli.Command {
|
|||
}
|
||||
|
||||
func dnsHelp(ctx *cli.Context) error {
|
||||
code := ctx.String("code")
|
||||
code := ctx.String(flgCode)
|
||||
if code == "" {
|
||||
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
|
||||
ew := &errWriter{w: w}
|
||||
|
|
|
@ -12,6 +12,11 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
flgAccounts = "accounts"
|
||||
flgNames = "names"
|
||||
)
|
||||
|
||||
func createList() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "list",
|
||||
|
@ -19,18 +24,18 @@ func createList() *cli.Command {
|
|||
Action: list,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "accounts",
|
||||
Name: flgAccounts,
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Display accounts.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "names",
|
||||
Name: flgNames,
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Display certificate common names only.",
|
||||
},
|
||||
// fake email, needed by NewAccountsStorage
|
||||
&cli.StringFlag{
|
||||
Name: "email",
|
||||
Name: flgEmail,
|
||||
Value: "unknown",
|
||||
Hidden: true,
|
||||
},
|
||||
|
@ -39,7 +44,7 @@ func createList() *cli.Command {
|
|||
}
|
||||
|
||||
func list(ctx *cli.Context) error {
|
||||
if ctx.Bool("accounts") && !ctx.Bool("names") {
|
||||
if ctx.Bool(flgAccounts) && !ctx.Bool(flgNames) {
|
||||
if err := listAccount(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -56,7 +61,7 @@ func listCertificates(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
names := ctx.Bool("names")
|
||||
names := ctx.Bool(flgNames)
|
||||
|
||||
if len(matches) == 0 {
|
||||
if !names {
|
||||
|
@ -70,7 +75,7 @@ func listCertificates(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
for _, filename := range matches {
|
||||
if strings.HasSuffix(filename, ".issuer.crt") {
|
||||
if strings.HasSuffix(filename, issuerExt) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -84,10 +89,15 @@ func listCertificates(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
name, err := certcrypto.GetCertificateMainDomain(pCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if names {
|
||||
fmt.Println(pCert.Subject.CommonName)
|
||||
fmt.Println(name)
|
||||
} else {
|
||||
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
|
||||
fmt.Println(" Certificate Name:", name)
|
||||
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
|
||||
fmt.Println(" Expiry Date:", pCert.NotAfter)
|
||||
fmt.Println(" Certificate Path:", filename)
|
||||
|
|
205
cmd/cmd_renew.go
205
cmd/cmd_renew.go
|
@ -17,13 +17,24 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Flag names.
|
||||
const (
|
||||
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
||||
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
||||
renewEnvCertPath = "LEGO_CERT_PATH"
|
||||
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
||||
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
||||
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
||||
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"
|
||||
)
|
||||
|
||||
func createRenew() *cli.Command {
|
||||
|
@ -33,73 +44,68 @@ 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("domains")) > 0
|
||||
hasCsr := len(ctx.String("csr")) > 0
|
||||
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
|
||||
hasCsr := ctx.String(flgCSR) != ""
|
||||
if hasDomains && hasCsr {
|
||||
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
|
||||
log.Fatal("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR)
|
||||
}
|
||||
if !hasDomains && !hasCsr {
|
||||
log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
||||
log.Fatal("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "days",
|
||||
Name: flgDays,
|
||||
Value: 30,
|
||||
Usage: "The number of days left on a certificate to renew it.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ari-enable",
|
||||
Name: flgARIEnable,
|
||||
Usage: "Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ari-hash-name",
|
||||
Value: crypto.SHA256.String(),
|
||||
Usage: "The string representation of the hash expected by the renewalInfo endpoint (e.g. \"SHA-256\").",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "ari-wait-to-renew-duration",
|
||||
Name: flgARIWaitToRenewDuration,
|
||||
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "reuse-key",
|
||||
Name: flgReuseKey,
|
||||
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-bundle",
|
||||
Name: flgNoBundle,
|
||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "must-staple",
|
||||
Name: flgMustStaple,
|
||||
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: "not-before",
|
||||
Name: flgNotBefore,
|
||||
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||
Layout: time.RFC3339,
|
||||
},
|
||||
&cli.TimestampFlag{
|
||||
Name: "not-after",
|
||||
Name: flgNotAfter,
|
||||
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||
Layout: time.RFC3339,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "preferred-chain",
|
||||
Name: flgPreferredChain,
|
||||
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: "always-deactivate-authorizations",
|
||||
Name: flgAlwaysDeactivateAuthorizations,
|
||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "renew-hook",
|
||||
Name: flgRenewHook,
|
||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "no-random-sleep",
|
||||
Name: flgNoRandomSleep,
|
||||
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.",
|
||||
},
|
||||
|
@ -117,12 +123,12 @@ func renew(ctx *cli.Context) error {
|
|||
|
||||
certsStorage := NewCertificatesStorage(ctx)
|
||||
|
||||
bundle := !ctx.Bool("no-bundle")
|
||||
bundle := !ctx.Bool(flgNoBundle)
|
||||
|
||||
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
||||
|
||||
// CSR
|
||||
if ctx.IsSet("csr") {
|
||||
if ctx.IsSet(flgCSR) {
|
||||
return renewForCSR(ctx, client, certsStorage, bundle, meta)
|
||||
}
|
||||
|
||||
|
@ -131,13 +137,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("domains")
|
||||
domains := ctx.StringSlice(flgDomains)
|
||||
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, ".crt")
|
||||
certificates, err := certsStorage.ReadCertificate(domain, certExt)
|
||||
if err != nil {
|
||||
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
@ -145,12 +151,8 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
|||
cert := certificates[0]
|
||||
|
||||
var ariRenewalTime *time.Time
|
||||
if ctx.Bool("ari-enable") {
|
||||
if len(certificates) < 2 {
|
||||
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
|
||||
} else {
|
||||
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
|
||||
}
|
||||
if ctx.Bool(flgARIEnable) {
|
||||
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
|
||||
if ariRenewalTime != nil {
|
||||
now := time.Now().UTC()
|
||||
// Figure out if we need to sleep before renewing.
|
||||
|
@ -161,7 +163,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
|||
}
|
||||
}
|
||||
|
||||
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) {
|
||||
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -172,8 +174,8 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
|||
certDomains := certcrypto.ExtractDomains(cert)
|
||||
|
||||
var privateKey crypto.PrivateKey
|
||||
if ctx.Bool("reuse-key") {
|
||||
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
|
||||
if ctx.Bool(flgReuseKey) {
|
||||
keyBytes, errR := certsStorage.ReadFile(domain, keyExt)
|
||||
if errR != nil {
|
||||
log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, errR)
|
||||
}
|
||||
|
@ -186,7 +188,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("no-random-sleep") {
|
||||
if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool(flgNoRandomSleep) {
|
||||
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472
|
||||
const jitter = 8 * time.Minute
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
@ -199,12 +201,19 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
|||
request := certificate.ObtainRequest{
|
||||
Domains: merge(certDomains, domains),
|
||||
PrivateKey: privateKey,
|
||||
MustStaple: ctx.Bool("must-staple"),
|
||||
NotBefore: getTime(ctx, "not-before"),
|
||||
NotAfter: getTime(ctx, "not-after"),
|
||||
MustStaple: ctx.Bool(flgMustStaple),
|
||||
NotBefore: getTime(ctx, flgNotBefore),
|
||||
NotAfter: getTime(ctx, flgNotAfter),
|
||||
Bundle: bundle,
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
certRes, err := client.Certificate.Obtain(request)
|
||||
|
@ -214,39 +223,26 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
|||
|
||||
certsStorage.SaveResource(certRes)
|
||||
|
||||
if ariRenewalTime != nil {
|
||||
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
|
||||
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
|
||||
Cert: certificates[0],
|
||||
Issuer: certificates[1],
|
||||
HashName: ctx.String("ari-hash-name"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
|
||||
}
|
||||
}
|
||||
addPathToMetadata(meta, domain, certRes, certsStorage)
|
||||
|
||||
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)
|
||||
return launchHook(ctx.String(flgRenewHook), meta)
|
||||
}
|
||||
|
||||
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
||||
csr, err := readCSRFile(ctx.String("csr"))
|
||||
csr, err := readCSRFile(ctx.String(flgCSR))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
domain := csr.Subject.CommonName
|
||||
domain, err := certcrypto.GetCSRMainDomain(csr)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
// load the cert resource from files.
|
||||
// We store the certificate, private key and metadata in different files
|
||||
// as web servers would not be able to work with a combined file.
|
||||
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
||||
certificates, err := certsStorage.ReadCertificate(domain, certExt)
|
||||
if err != nil {
|
||||
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
@ -254,12 +250,8 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
|||
cert := certificates[0]
|
||||
|
||||
var ariRenewalTime *time.Time
|
||||
if ctx.Bool("ari-enable") {
|
||||
if len(certificates) < 2 {
|
||||
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
|
||||
} else {
|
||||
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
|
||||
}
|
||||
if ctx.Bool(flgARIEnable) {
|
||||
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
|
||||
if ariRenewalTime != nil {
|
||||
now := time.Now().UTC()
|
||||
// Figure out if we need to sleep before renewing.
|
||||
|
@ -270,7 +262,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
|||
}
|
||||
}
|
||||
|
||||
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) {
|
||||
if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -280,11 +272,18 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
|||
|
||||
request := certificate.ObtainForCSRRequest{
|
||||
CSR: csr,
|
||||
NotBefore: getTime(ctx, "not-before"),
|
||||
NotAfter: getTime(ctx, "not-after"),
|
||||
NotBefore: getTime(ctx, flgNotBefore),
|
||||
NotAfter: getTime(ctx, flgNotAfter),
|
||||
Bundle: bundle,
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
certRes, err := client.Certificate.ObtainForCSR(request)
|
||||
|
@ -294,23 +293,9 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
|||
|
||||
certsStorage.SaveResource(certRes)
|
||||
|
||||
if ariRenewalTime != nil {
|
||||
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
|
||||
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
|
||||
Cert: certificates[0],
|
||||
Issuer: certificates[1],
|
||||
HashName: ctx.String("ari-hash-name"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
|
||||
}
|
||||
}
|
||||
addPathToMetadata(meta, domain, certRes, certsStorage)
|
||||
|
||||
meta[renewEnvCertDomain] = domain
|
||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
||||
|
||||
return launchHook(ctx.String("renew-hook"), meta)
|
||||
return launchHook(ctx.String(flgRenewHook), meta)
|
||||
}
|
||||
|
||||
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
||||
|
@ -331,28 +316,24 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
|||
}
|
||||
|
||||
// getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint.
|
||||
func getARIRenewalTime(ctx *cli.Context, cert, issuer *x509.Certificate, domain string, client *lego.Client) *time.Time {
|
||||
func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
|
||||
if cert.IsCA {
|
||||
log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain)
|
||||
}
|
||||
|
||||
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{
|
||||
Cert: cert,
|
||||
Issuer: issuer,
|
||||
HashName: ctx.String("ari-hash-name"),
|
||||
})
|
||||
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{Cert: cert})
|
||||
if err != nil {
|
||||
if errors.Is(err, api.ErrNoARI) {
|
||||
// The server does not advertise a renewal info endpoint.
|
||||
log.Warnf("[%s] acme: %w", domain, err)
|
||||
log.Warnf("[%s] acme: %v", domain, err)
|
||||
return nil
|
||||
}
|
||||
log.Warnf("[%s] acme: calling renewal info endpoint: %w", domain, err)
|
||||
log.Warnf("[%s] acme: calling renewal info endpoint: %v", domain, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration("ari-wait-to-renew-duration"))
|
||||
renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration(flgARIWaitToRenewDuration))
|
||||
if renewalTime == nil {
|
||||
log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain)
|
||||
return nil
|
||||
|
@ -366,6 +347,24 @@ func getARIRenewalTime(ctx *cli.Context, cert, issuer *x509.Certificate, domain
|
|||
return renewalTime
|
||||
}
|
||||
|
||||
func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) {
|
||||
meta[renewEnvCertDomain] = domain
|
||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, certExt)
|
||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, keyExt)
|
||||
|
||||
if certRes.IssuerCertificate != nil {
|
||||
meta[renewEnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, issuerExt)
|
||||
}
|
||||
|
||||
if certsStorage.pem {
|
||||
meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, pemExt)
|
||||
}
|
||||
|
||||
if certsStorage.pfx {
|
||||
meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, pfxExt)
|
||||
}
|
||||
}
|
||||
|
||||
func merge(prevDomains, nextDomains []string) []string {
|
||||
for _, next := range nextDomains {
|
||||
var found bool
|
||||
|
|
|
@ -48,7 +48,6 @@ func Test_merge(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -108,7 +107,6 @@ func Test_needRenewal(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
actual := needRenewal(test.x509Cert, "foo.com", test.days)
|
||||
|
||||
|
|
|
@ -6,6 +6,12 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Flag names.
|
||||
const (
|
||||
flgKeep = "keep"
|
||||
flgReason = "reason"
|
||||
)
|
||||
|
||||
func createRevoke() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "revoke",
|
||||
|
@ -13,12 +19,12 @@ func createRevoke() *cli.Command {
|
|||
Action: revoke,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "keep",
|
||||
Name: flgKeep,
|
||||
Aliases: []string{"k"},
|
||||
Usage: "Keep the certificates after the revocation instead of archiving them.",
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "reason",
|
||||
Name: flgReason,
|
||||
Usage: "Identifies the reason for the certificate revocation." +
|
||||
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
|
||||
" Valid values are:" +
|
||||
|
@ -41,15 +47,15 @@ func revoke(ctx *cli.Context) error {
|
|||
certsStorage := NewCertificatesStorage(ctx)
|
||||
certsStorage.CreateRootFolder()
|
||||
|
||||
for _, domain := range ctx.StringSlice("domains") {
|
||||
for _, domain := range ctx.StringSlice(flgDomains) {
|
||||
log.Printf("Trying to revoke certificate for domain %s", domain)
|
||||
|
||||
certBytes, err := certsStorage.ReadFile(domain, ".crt")
|
||||
certBytes, err := certsStorage.ReadFile(domain, certExt)
|
||||
if err != nil {
|
||||
log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
reason := ctx.Uint("reason")
|
||||
reason := ctx.Uint(flgReason)
|
||||
|
||||
err = client.Certificate.RevokeWithReason(certBytes, &reason)
|
||||
if err != nil {
|
||||
|
@ -58,7 +64,7 @@ func revoke(ctx *cli.Context) error {
|
|||
|
||||
log.Println("Certificate was revoked.")
|
||||
|
||||
if ctx.Bool("keep") {
|
||||
if ctx.Bool(flgKeep) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,25 @@ 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("domains")) > 0
|
||||
hasCsr := len(ctx.String("csr")) > 0
|
||||
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
|
||||
hasCsr := ctx.String(flgCSR) != ""
|
||||
if hasDomains && hasCsr {
|
||||
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
|
||||
}
|
||||
|
@ -33,35 +44,35 @@ func createRun() *cli.Command {
|
|||
Action: run,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-bundle",
|
||||
Name: flgNoBundle,
|
||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "must-staple",
|
||||
Name: flgMustStaple,
|
||||
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: "not-before",
|
||||
Name: flgNotBefore,
|
||||
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||
Layout: time.RFC3339,
|
||||
},
|
||||
&cli.TimestampFlag{
|
||||
Name: "not-after",
|
||||
Name: flgNotAfter,
|
||||
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||
Layout: time.RFC3339,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "preferred-chain",
|
||||
Name: flgPreferredChain,
|
||||
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: "always-deactivate-authorizations",
|
||||
Name: flgAlwaysDeactivateAuthorizations,
|
||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "run-hook",
|
||||
Name: flgRunHook,
|
||||
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
||||
},
|
||||
},
|
||||
|
@ -113,19 +124,16 @@ 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"),
|
||||
}
|
||||
|
||||
return launchHook(ctx.String("run-hook"), meta)
|
||||
addPathToMetadata(meta, cert.Domain, cert, certsStorage)
|
||||
|
||||
return launchHook(ctx.String(flgRunHook), meta)
|
||||
}
|
||||
|
||||
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
|
||||
// Check for a global accept override
|
||||
if ctx.Bool("accept-tos") {
|
||||
if ctx.Bool(flgAcceptTOS) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -157,12 +165,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("eab") {
|
||||
kid := ctx.String("kid")
|
||||
hmacEncoded := ctx.String("hmac")
|
||||
if ctx.Bool(flgEAB) {
|
||||
kid := ctx.String(flgKID)
|
||||
hmacEncoded := ctx.String(flgHMAC)
|
||||
|
||||
if kid == "" || hmacEncoded == "" {
|
||||
log.Fatalf("Requires arguments --kid and --hmac.")
|
||||
log.Fatalf("Requires arguments --%s and --%s.", flgKID, flgHMAC)
|
||||
}
|
||||
|
||||
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
|
@ -176,25 +184,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("no-bundle")
|
||||
bundle := !ctx.Bool(flgNoBundle)
|
||||
|
||||
domains := ctx.StringSlice("domains")
|
||||
domains := ctx.StringSlice(flgDomains)
|
||||
if len(domains) > 0 {
|
||||
// obtain a certificate, generating a new private key
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
Bundle: bundle,
|
||||
MustStaple: ctx.Bool("must-staple"),
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||
MustStaple: ctx.Bool(flgMustStaple),
|
||||
PreferredChain: ctx.String(flgPreferredChain),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
|
||||
}
|
||||
|
||||
notBefore := ctx.Timestamp("not-before")
|
||||
notBefore := ctx.Timestamp(flgNotBefore)
|
||||
if notBefore != nil {
|
||||
request.NotBefore = *notBefore
|
||||
}
|
||||
|
||||
notAfter := ctx.Timestamp("not-after")
|
||||
notAfter := ctx.Timestamp(flgNotAfter)
|
||||
if notAfter != nil {
|
||||
request.NotAfter = *notAfter
|
||||
}
|
||||
|
@ -203,7 +211,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
|
|||
}
|
||||
|
||||
// read the CSR
|
||||
csr, err := readCSRFile(ctx.String("csr"))
|
||||
csr, err := readCSRFile(ctx.String(flgCSR))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -211,11 +219,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, "not-before"),
|
||||
NotAfter: getTime(ctx, "not-after"),
|
||||
NotBefore: getTime(ctx, flgNotBefore),
|
||||
NotAfter: getTime(ctx, flgNotAfter),
|
||||
Bundle: bundle,
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
||||
PreferredChain: ctx.String(flgPreferredChain),
|
||||
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
|
||||
}
|
||||
|
||||
return client.Certificate.ObtainForCSR(request)
|
||||
|
|
141
cmd/flags.go
141
cmd/flags.go
|
@ -1,145 +1,218 @@
|
|||
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"
|
||||
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: "domains",
|
||||
Name: flgDomains,
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Add a domain to the process. Can be specified multiple times.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "server",
|
||||
Name: flgServer,
|
||||
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: "accept-tos",
|
||||
Name: flgAcceptTOS,
|
||||
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: "email",
|
||||
Name: flgEmail,
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Email used for registration and recovery contact.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "csr",
|
||||
Name: flgCSR,
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Certificate signing request filename, if an external CSR is to be used.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "eab",
|
||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||
Name: flgEAB,
|
||||
EnvVars: []string{"LEGO_EAB"},
|
||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "kid",
|
||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||
Name: flgKID,
|
||||
EnvVars: []string{"LEGO_EAB_KID"},
|
||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "hmac",
|
||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||
Name: flgHMAC,
|
||||
EnvVars: []string{"LEGO_EAB_HMAC"},
|
||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "key-type",
|
||||
Name: flgKeyType,
|
||||
Aliases: []string{"k"},
|
||||
Value: "ec256",
|
||||
Usage: "Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "filename",
|
||||
Name: flgFilename,
|
||||
Usage: "(deprecated) Filename of the generated certificate.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Name: flgPath,
|
||||
EnvVars: []string{"LEGO_PATH"},
|
||||
Usage: "Directory to use for storing the data.",
|
||||
Value: defaultPath,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "http",
|
||||
Name: flgHTTP,
|
||||
Usage: "Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http.port",
|
||||
Name: flgHTTPPort,
|
||||
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: "http.proxy-header",
|
||||
Name: flgHTTPProxyHeader,
|
||||
Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.",
|
||||
Value: "Host",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http.webroot",
|
||||
Name: flgHTTPWebroot,
|
||||
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: "http.memcached-host",
|
||||
Name: flgHTTPMemcachedHost,
|
||||
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.BoolFlag{
|
||||
Name: "tls",
|
||||
Name: flgTLS,
|
||||
Usage: "Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tls.port",
|
||||
Name: flgTLSPort,
|
||||
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: "dns",
|
||||
Name: flgDNS,
|
||||
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: "dns.disable-cp",
|
||||
Name: flgDNSDisableCP,
|
||||
Usage: fmt.Sprintf("(deprecated) use %s instead.", flgDNSPropagationDisableANS),
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flgDNSPropagationDisableANS,
|
||||
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: "dns.resolvers",
|
||||
Name: flgDNSResolvers,
|
||||
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: "http-timeout",
|
||||
Name: flgHTTPTimeout,
|
||||
Usage: "Set the HTTP timeout value to a specific value in seconds.",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "dns-timeout",
|
||||
Name: flgDNSTimeout,
|
||||
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: "pem",
|
||||
Name: flgPEM,
|
||||
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "pfx",
|
||||
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
|
||||
Name: flgPFX,
|
||||
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
|
||||
EnvVars: []string{"LEGO_PFX"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pfx.pass",
|
||||
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
||||
Value: pkcs12.DefaultPassword,
|
||||
Name: flgPFXPass,
|
||||
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: "cert.timeout",
|
||||
Name: flgCertTimeout,
|
||||
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",
|
||||
Name: flgUserAgent,
|
||||
Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli",
|
||||
},
|
||||
}
|
||||
|
|
23
cmd/setup.go
23
cmd/setup.go
|
@ -35,16 +35,17 @@ 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("server")
|
||||
config.CADirURL = ctx.String(flgServer)
|
||||
|
||||
config.Certificate = lego.CertificateConfig{
|
||||
KeyType: keyType,
|
||||
Timeout: time.Duration(ctx.Int("cert.timeout")) * time.Second,
|
||||
KeyType: keyType,
|
||||
Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second,
|
||||
OverallRequestLimit: ctx.Int(flgOverallRequestLimit),
|
||||
}
|
||||
config.UserAgent = getUserAgent(ctx)
|
||||
|
||||
if ctx.IsSet("http-timeout") {
|
||||
config.HTTPClient.Timeout = time.Duration(ctx.Int("http-timeout")) * time.Second
|
||||
if ctx.IsSet(flgHTTPTimeout) {
|
||||
config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second
|
||||
}
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
|
@ -52,8 +53,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("eab") {
|
||||
log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
|
||||
if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) {
|
||||
log.Fatalf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC)
|
||||
}
|
||||
|
||||
return client
|
||||
|
@ -61,7 +62,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("key-type")
|
||||
keyType := ctx.String(flgKeyType)
|
||||
switch strings.ToUpper(keyType) {
|
||||
case "RSA2048":
|
||||
return certcrypto.RSA2048
|
||||
|
@ -82,15 +83,15 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
|||
}
|
||||
|
||||
func getEmail(ctx *cli.Context) string {
|
||||
email := ctx.String("email")
|
||||
email := ctx.String(flgEmail)
|
||||
if email == "" {
|
||||
log.Fatal("You have to pass an account (email address) to the program using --email or -m")
|
||||
log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail)
|
||||
}
|
||||
return email
|
||||
}
|
||||
|
||||
func getUserAgent(ctx *cli.Context) string {
|
||||
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String("user-agent"), ctx.App.Version))
|
||||
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version))
|
||||
}
|
||||
|
||||
func createNonExistingFolder(path string) error {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -13,52 +14,63 @@ import (
|
|||
"github.com/go-acme/lego/v4/log"
|
||||
"github.com/go-acme/lego/v4/providers/dns"
|
||||
"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("http") && !ctx.Bool("tls") && !ctx.IsSet("dns") {
|
||||
log.Fatal("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
|
||||
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") {
|
||||
if ctx.Bool(flgHTTP) {
|
||||
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Bool("tls") {
|
||||
if ctx.Bool(flgTLS) {
|
||||
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSet("dns") {
|
||||
setupDNS(ctx, client)
|
||||
if ctx.IsSet(flgDNS) {
|
||||
err := setupDNS(ctx, client)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo // the complexity is expected.
|
||||
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
||||
switch {
|
||||
case ctx.IsSet("http.webroot"):
|
||||
ps, err := webroot.NewHTTPProvider(ctx.String("http.webroot"))
|
||||
case ctx.IsSet(flgHTTPWebroot):
|
||||
ps, err := webroot.NewHTTPProvider(ctx.String(flgHTTPWebroot))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ps
|
||||
case ctx.IsSet("http.memcached-host"):
|
||||
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice("http.memcached-host"))
|
||||
case ctx.IsSet(flgHTTPMemcachedHost):
|
||||
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ps
|
||||
case ctx.IsSet("http.port"):
|
||||
iface := ctx.String("http.port")
|
||||
case ctx.IsSet(flgHTTPS3Bucket):
|
||||
ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ps
|
||||
case ctx.IsSet(flgHTTPPort):
|
||||
iface := ctx.String(flgHTTPPort)
|
||||
if !strings.Contains(iface, ":") {
|
||||
log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
|
||||
log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgHTTPPort)
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
|
@ -67,13 +79,13 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
|||
}
|
||||
|
||||
srv := http01.NewProviderServer(host, port)
|
||||
if header := ctx.String("http.proxy-header"); header != "" {
|
||||
if header := ctx.String(flgHTTPProxyHeader); header != "" {
|
||||
srv.SetProxyHeader(header)
|
||||
}
|
||||
return srv
|
||||
case ctx.Bool("http"):
|
||||
case ctx.Bool(flgHTTP):
|
||||
srv := http01.NewProviderServer("", "")
|
||||
if header := ctx.String("http.proxy-header"); header != "" {
|
||||
if header := ctx.String(flgHTTPProxyHeader); header != "" {
|
||||
srv.SetProxyHeader(header)
|
||||
}
|
||||
return srv
|
||||
|
@ -85,10 +97,10 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
|||
|
||||
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
||||
switch {
|
||||
case ctx.IsSet("tls.port"):
|
||||
iface := ctx.String("tls.port")
|
||||
case ctx.IsSet(flgTLSPort):
|
||||
iface := ctx.String(flgTLSPort)
|
||||
if !strings.Contains(iface, ":") {
|
||||
log.Fatalf("The --tls switch only accepts interface:port or :port for its argument.")
|
||||
log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgTLSPort)
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
|
@ -97,7 +109,7 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
|||
}
|
||||
|
||||
return tlsalpn01.NewProviderServer(host, port)
|
||||
case ctx.Bool("tls"):
|
||||
case ctx.Bool(flgTLS):
|
||||
return tlsalpn01.NewProviderServer("", "")
|
||||
default:
|
||||
log.Fatal("Invalid HTTP challenge options.")
|
||||
|
@ -105,22 +117,62 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
|||
}
|
||||
}
|
||||
|
||||
func setupDNS(ctx *cli.Context, client *lego.Client) {
|
||||
provider, err := dns.NewDNSChallengeProviderByName(ctx.String("dns"))
|
||||
func setupDNS(ctx *cli.Context, client *lego.Client) error {
|
||||
err := checkPropagationExclusiveOptions(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
servers := ctx.StringSlice("dns.resolvers")
|
||||
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)
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider,
|
||||
dns01.CondOption(len(servers) > 0,
|
||||
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)),
|
||||
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)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func checkPropagationExclusiveOptions(ctx *cli.Context) error {
|
||||
if ctx.IsSet(flgDNSDisableCP) {
|
||||
log.Println("The flag '%s' is deprecated use '%s' instead.", flgDNSDisableCP, flgDNSPropagationDisableANS)
|
||||
}
|
||||
|
||||
if (isSetBool(ctx, flgDNSDisableCP) || isSetBool(ctx, flgDNSPropagationDisableANS)) && ctx.IsSet(flgDNSPropagationWait) {
|
||||
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationDisableANS, flgDNSPropagationWait)
|
||||
}
|
||||
|
||||
if isSetBool(ctx, flgDNSPropagationRNS) && ctx.IsSet(flgDNSPropagationWait) {
|
||||
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationRNS, flgDNSPropagationWait)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSetBool(ctx *cli.Context, name string) bool {
|
||||
return ctx.IsSet(name) && ctx.Bool(name)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cmd
|
||||
// Code generated by 'make generate-dns'; DO NOT EDIT.
|
||||
|
||||
// CODE GENERATED AUTOMATICALLY
|
||||
// THIS FILE MUST NOT BE EDITED BY HAND
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -31,13 +30,16 @@ func allDNSCodes() string {
|
|||
"clouddns",
|
||||
"cloudflare",
|
||||
"cloudns",
|
||||
"cloudru",
|
||||
"cloudxns",
|
||||
"conoha",
|
||||
"constellix",
|
||||
"cpanel",
|
||||
"derak",
|
||||
"desec",
|
||||
"designate",
|
||||
"digitalocean",
|
||||
"directadmin",
|
||||
"dnshomede",
|
||||
"dnsimple",
|
||||
"dnsmadeeasy",
|
||||
|
@ -65,7 +67,9 @@ func allDNSCodes() string {
|
|||
"hetzner",
|
||||
"hostingde",
|
||||
"hosttech",
|
||||
"httpnet",
|
||||
"httpreq",
|
||||
"huaweicloud",
|
||||
"hurricane",
|
||||
"hyperone",
|
||||
"ibmcloud",
|
||||
|
@ -81,11 +85,15 @@ func allDNSCodes() string {
|
|||
"joker",
|
||||
"liara",
|
||||
"lightsail",
|
||||
"limacity",
|
||||
"linode",
|
||||
"liquidweb",
|
||||
"loopia",
|
||||
"luadns",
|
||||
"mailinabox",
|
||||
"metaname",
|
||||
"mijnhost",
|
||||
"mittwald",
|
||||
"mydnsjp",
|
||||
"mythicbeasts",
|
||||
"namecheap",
|
||||
|
@ -115,7 +123,10 @@ func allDNSCodes() string {
|
|||
"sakuracloud",
|
||||
"scaleway",
|
||||
"selectel",
|
||||
"selectelv2",
|
||||
"selfhostde",
|
||||
"servercow",
|
||||
"shellrent",
|
||||
"simply",
|
||||
"sonic",
|
||||
"stackpath",
|
||||
|
@ -130,9 +141,11 @@ func allDNSCodes() string {
|
|||
"vkcloud",
|
||||
"vscale",
|
||||
"vultr",
|
||||
"webnames",
|
||||
"websupport",
|
||||
"wedos",
|
||||
"yandex",
|
||||
"yandex360",
|
||||
"yandexcloud",
|
||||
"zoneee",
|
||||
"zonomi",
|
||||
|
@ -298,24 +311,28 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
|
||||
case "azuredns":
|
||||
// generated from: providers/dns/azuredns/azuredns.toml
|
||||
ew.writeln(`Configuration for AzureDNS.`)
|
||||
ew.writeln(`Configuration for Azure DNS.`)
|
||||
ew.writeln(`Code: 'azuredns'`)
|
||||
ew.writeln(`Since: 'v0.1.0'`)
|
||||
ew.writeln(`Since: 'v4.13.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`)
|
||||
|
||||
|
@ -360,6 +377,7 @@ 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()
|
||||
|
@ -486,10 +504,10 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
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(` - "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()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudflare`)
|
||||
|
@ -516,6 +534,29 @@ 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.`)
|
||||
|
@ -581,6 +622,30 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/constellix`)
|
||||
|
||||
case "cpanel":
|
||||
// generated from: providers/dns/cpanel/cpanel.toml
|
||||
ew.writeln(`Configuration for CPanel/WHM.`)
|
||||
ew.writeln(`Code: 'cpanel'`)
|
||||
ew.writeln(`Since: 'v4.16.0'`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "CPANEL_BASE_URL": API server URL`)
|
||||
ew.writeln(` - "CPANEL_TOKEN": API token`)
|
||||
ew.writeln(` - "CPANEL_USERNAME": username`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "CPANEL_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "CPANEL_MODE": use cpanel API or WHM API (Default: cpanel)`)
|
||||
ew.writeln(` - "CPANEL_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "CPANEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "CPANEL_REGION": The region`)
|
||||
ew.writeln(` - "CPANEL_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cpanel`)
|
||||
|
||||
case "derak":
|
||||
// generated from: providers/dns/derak/derak.toml
|
||||
ew.writeln(`Configuration for Derak Cloud.`)
|
||||
|
@ -645,6 +710,7 @@ 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)`)
|
||||
|
||||
|
@ -672,6 +738,29 @@ 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.`)
|
||||
|
@ -683,6 +772,12 @@ 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`)
|
||||
|
||||
|
@ -935,6 +1030,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "EFFICIENTIP_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "EFFICIENTIP_INSECURE_SKIP_VERIFY": Whether or not to verify EfficientIP API certificate`)
|
||||
ew.writeln(` - "EFFICIENTIP_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "EFFICIENTIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "EFFICIENTIP_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
@ -986,7 +1082,6 @@ 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`)
|
||||
|
@ -1045,7 +1140,8 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "GANDIV5_API_KEY": API key`)
|
||||
ew.writeln(` - "GANDIV5_API_KEY": API key (Deprecated)`)
|
||||
ew.writeln(` - "GANDIV5_PERSONAL_ACCESS_TOKEN": Personal Access Token`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
@ -1076,6 +1172,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "GCE_ZONE_ID": Allows to skip the automatic detection of the zone`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)
|
||||
|
@ -1223,6 +1320,27 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hosttech`)
|
||||
|
||||
case "httpnet":
|
||||
// generated from: providers/dns/httpnet/httpnet.toml
|
||||
ew.writeln(`Configuration for http.net.`)
|
||||
ew.writeln(`Code: 'httpnet'`)
|
||||
ew.writeln(`Since: 'v4.15.0'`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "HTTPNET_API_KEY": API key`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "HTTPNET_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "HTTPNET_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "HTTPNET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "HTTPNET_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "HTTPNET_ZONE_NAME": Zone name in ACE format`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/httpnet`)
|
||||
|
||||
case "httpreq":
|
||||
// generated from: providers/dns/httpreq/httpreq.toml
|
||||
ew.writeln(`Configuration for HTTP request.`)
|
||||
|
@ -1245,6 +1363,28 @@ 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.`)
|
||||
|
@ -1256,6 +1396,12 @@ 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`)
|
||||
|
||||
|
@ -1286,7 +1432,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "SOFTLAYER_API_KEY": Classic Infrastructure API key`)
|
||||
ew.writeln(` - "SOFTLAYER_USERNAME": User name (IBM Cloud is <accountID>_<emailAddress>)`)
|
||||
ew.writeln(` - "SOFTLAYER_USERNAME": Username (IBM Cloud is <accountID>_<emailAddress>)`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
@ -1465,7 +1611,6 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "IPV64_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "IPV64_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "IPV64_SEQUENCE_INTERVAL": Time between sequential requests`)
|
||||
ew.writeln(` - "IPV64_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
|
@ -1557,6 +1702,27 @@ 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).`)
|
||||
|
@ -1585,17 +1751,17 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "LIQUID_WEB_PASSWORD": Storm API Password`)
|
||||
ew.writeln(` - "LIQUID_WEB_USERNAME": Storm API Username`)
|
||||
ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`)
|
||||
ew.writeln(` - "LWAPI_PASSWORD": Liquid Web API Password`)
|
||||
ew.writeln(` - "LWAPI_USERNAME": Liquid Web API Username`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "LIQUID_WEB_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
|
||||
ew.writeln(` - "LIQUID_WEB_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "LIQUID_WEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "LIQUID_WEB_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "LIQUID_WEB_URL": Storm API endpoint`)
|
||||
ew.writeln(` - "LWAPI_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
|
||||
ew.writeln(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "LWAPI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "LWAPI_URL": Liquid Web API endpoint`)
|
||||
ew.writeln(` - "LWAPI_ZONE": DNS Zone`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`)
|
||||
|
@ -1643,6 +1809,26 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/luadns`)
|
||||
|
||||
case "mailinabox":
|
||||
// generated from: providers/dns/mailinabox/mailinabox.toml
|
||||
ew.writeln(`Configuration for Mail-in-a-Box.`)
|
||||
ew.writeln(`Code: 'mailinabox'`)
|
||||
ew.writeln(`Since: 'v4.16.0'`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "MAILINABOX_BASE_URL": Base API URL (ex: https://box.example.com)`)
|
||||
ew.writeln(` - "MAILINABOX_EMAIL": User email`)
|
||||
ew.writeln(` - "MAILINABOX_PASSWORD": User password`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/mailinabox`)
|
||||
|
||||
case "metaname":
|
||||
// generated from: providers/dns/metaname/metaname.toml
|
||||
ew.writeln(`Configuration for Metaname.`)
|
||||
|
@ -1663,6 +1849,48 @@ 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.`)
|
||||
|
@ -1983,6 +2211,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests`)
|
||||
ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
|
@ -1996,9 +2225,12 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "OVH_APPLICATION_KEY": Application key`)
|
||||
ew.writeln(` - "OVH_APPLICATION_SECRET": Application secret`)
|
||||
ew.writeln(` - "OVH_CONSUMER_KEY": Consumer key`)
|
||||
ew.writeln(` - "OVH_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_ENDPOINT": Endpoint URL (ovh-eu or ovh-ca)`)
|
||||
ew.writeln()
|
||||
|
||||
|
@ -2024,6 +2256,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "PDNS_API_VERSION": Skip API version autodetection and use the provided version number.`)
|
||||
ew.writeln(` - "PDNS_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "PDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
|
@ -2133,6 +2366,8 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "REGRU_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "REGRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "REGRU_TLS_CERT": authentication certificate`)
|
||||
ew.writeln(` - "REGRU_TLS_KEY": authentication private key`)
|
||||
ew.writeln(` - "REGRU_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
|
@ -2198,6 +2433,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln(` - "AWS_REGION": Managed by the AWS client ('AWS_REGION_FILE' is not supported)`)
|
||||
ew.writeln(` - "AWS_SDK_LOAD_CONFIG": Managed by the AWS client. Retrieve the region from the CLI config file ('AWS_SDK_LOAD_CONFIG_FILE' is not supported)`)
|
||||
ew.writeln(` - "AWS_SECRET_ACCESS_KEY": Managed by the AWS client. Secret access key ('AWS_SECRET_ACCESS_KEY_FILE' is not supported, use 'AWS_SHARED_CREDENTIALS_FILE' instead)`)
|
||||
ew.writeln(` - "AWS_WAIT_FOR_RECORD_SETS_CHANGED": Wait for changes to be INSYNC (it can be unstable)`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
@ -2259,14 +2495,15 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "SCALEWAY_API_TOKEN": API token`)
|
||||
ew.writeln(` - "SCALEWAY_PROJECT_ID": Project to use (optional)`)
|
||||
ew.writeln(` - "SCW_PROJECT_ID": Project to use (optional)`)
|
||||
ew.writeln(` - "SCW_SECRET_KEY": Secret key`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "SCALEWAY_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "SCALEWAY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "SCALEWAY_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
ew.writeln(` - "SCW_ACCESS_KEY": Access key`)
|
||||
ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/scaleway`)
|
||||
|
@ -2292,6 +2529,52 @@ 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.`)
|
||||
|
@ -2313,6 +2596,27 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/servercow`)
|
||||
|
||||
case "shellrent":
|
||||
// generated from: providers/dns/shellrent/shellrent.toml
|
||||
ew.writeln(`Configuration for Shellrent.`)
|
||||
ew.writeln(`Code: 'shellrent'`)
|
||||
ew.writeln(`Since: 'v4.16.0'`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "SHELLRENT_TOKEN": Token`)
|
||||
ew.writeln(` - "SHELLRENT_USERNAME": Username`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "SHELLRENT_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "SHELLRENT_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "SHELLRENT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "SHELLRENT_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/shellrent`)
|
||||
|
||||
case "simply":
|
||||
// generated from: providers/dns/simply/simply.toml
|
||||
ew.writeln(`Configuration for Simply.com.`)
|
||||
|
@ -2613,6 +2917,26 @@ func displayDNSHelp(w io.Writer, name string) error {
|
|||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`)
|
||||
|
||||
case "webnames":
|
||||
// generated from: providers/dns/webnames/webnames.toml
|
||||
ew.writeln(`Configuration for Webnames.`)
|
||||
ew.writeln(`Code: 'webnames'`)
|
||||
ew.writeln(`Since: 'v4.15.0'`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "WEBNAMES_API_KEY": Domain API key`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout`)
|
||||
ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||
ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
||||
ew.writeln(` - "WEBNAMES_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||
|
||||
ew.writeln()
|
||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`)
|
||||
|
||||
case "websupport":
|
||||
// generated from: providers/dns/websupport/websupport.toml
|
||||
ew.writeln(`Configuration for Websupport.`)
|
||||
|
@ -2676,6 +3000,27 @@ 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.`)
|
||||
|
@ -2685,7 +3030,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 inforamtion about iam token of serivce account with 'dns.admin' permissions`)
|
||||
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()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
.PHONY: default clean hugo hugo-build
|
||||
|
||||
default: hugo
|
||||
default: clean hugo
|
||||
|
||||
clean:
|
||||
rm -rf public/
|
||||
|
||||
|
||||
hugo-build: clean hugo-themes
|
||||
hugo-build: clean
|
||||
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
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
---
|
||||
title: "Welcome"
|
||||
title: "Lego"
|
||||
date: 2019-03-03T16:39:46+01:00
|
||||
draft: false
|
||||
chapter: true
|
||||
chapter: false
|
||||
---
|
||||
|
||||
# Lego
|
||||
|
||||
Let's Encrypt client and ACME library written in Go.
|
||||
|
||||
## Features
|
||||
|
@ -25,7 +23,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
|
||||
|
|
|
@ -15,7 +15,7 @@ The environment variables can reference a value.
|
|||
|
||||
Here is an example bash command using the Cloudflare DNS provider:
|
||||
|
||||
```console
|
||||
```bash
|
||||
$ 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:
|
||||
|
||||
```console
|
||||
```bash
|
||||
$ cat /the/path/to/my/key
|
||||
b9841238feb177a84330febba8a83208921177bffe733
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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/doc-detail/42875.htm)
|
||||
- [API documentation](https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records)
|
||||
- [Go client](https://github.com/aliyun/alibaba-cloud-sdk-go)
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: "AzureDNS"
|
||||
title: "Azure DNS"
|
||||
date: 2019-03-03T16:39:46+01:00
|
||||
draft: false
|
||||
slug: azuredns
|
||||
dnsprovider:
|
||||
since: "v0.1.0"
|
||||
since: "v4.13.0"
|
||||
code: "azuredns"
|
||||
url: "https://azure.microsoft.com/services/dns/"
|
||||
---
|
||||
|
@ -14,33 +14,50 @@ dnsprovider:
|
|||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
|
||||
|
||||
Configuration for [AzureDNS](https://azure.microsoft.com/services/dns/).
|
||||
Configuration for [Azure DNS](https://azure.microsoft.com/services/dns/).
|
||||
|
||||
|
||||
<!--more-->
|
||||
|
||||
- Code: `azuredns`
|
||||
- Since: v0.1.0
|
||||
- Since: v4.13.0
|
||||
|
||||
|
||||
Here is an example bash command using the AzureDNS provider:
|
||||
Here is an example bash command using the Azure DNS 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
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
@ -50,62 +67,168 @@ 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
|
||||
|
||||
Azure Credentials are automatically detected in the following locations and prioritized in the following order:
|
||||
Several authentication methods can be used to authenticate against Azure DNS API.
|
||||
|
||||
### Default Azure Credentials (default option)
|
||||
|
||||
Default Azure Credentials automatically detects in the following locations and prioritized in the following order:
|
||||
|
||||
1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`
|
||||
2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH`
|
||||
3. Workload identity for resources hosted in Azure environment (see below)
|
||||
4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI
|
||||
4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI
|
||||
|
||||
Link:
|
||||
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### Service Discovery
|
||||
|
||||
Lego automatically finds all visible Azure (private) DNS zones using [Azure ResourceGraph query](https://learn.microsoft.com/en-us/azure/governance/resource-graph/).
|
||||
This can be limited by specifying environment variable `AZURE_SUBSCRIPTION_ID` and/or `AZURE_RESOURCE_GROUP` which limits the
|
||||
DNS zones to only a subscription or to one resourceGroup.
|
||||
|
||||
Additionally environment variable `AZURE_SERVICEDISCOVERY_FILTER` can be used to filter DNS zones with an addition Kusto filter eg:
|
||||
|
||||
```
|
||||
resources
|
||||
| where type =~ "microsoft.network/dnszones"
|
||||
| ${AZURE_SERVICEDISCOVERY_FILTER}
|
||||
| project subscriptionId, resourceGroup, name
|
||||
```
|
||||
|
||||
|
||||
#### Client secret
|
||||
|
||||
The Azure Credentials can be configured using the following environment variables:
|
||||
* AZURE_CLIENT_ID = "Client ID"
|
||||
* AZURE_CLIENT_SECRET = "Client secret"
|
||||
* AZURE_TENANT_ID = "Tenant ID"
|
||||
|
||||
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
|
||||
|
||||
#### Client certificate
|
||||
|
||||
The Azure Credentials can be configured using the following environment variables:
|
||||
* AZURE_CLIENT_ID = "Client ID"
|
||||
* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
|
||||
* AZURE_TENANT_ID = "Tenant ID"
|
||||
|
||||
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
|
||||
|
||||
### Workload identity
|
||||
|
||||
#### Azure Managed Identity
|
||||
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
|
||||
|
||||
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. \
|
||||
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 fedreated 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 federated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account.
|
||||
|
||||
Link :
|
||||
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
|
||||
|
||||
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`.
|
||||
|
||||
### Azure Managed Identity
|
||||
|
||||
#### Azure Managed Identity (with Azure workload)
|
||||
|
||||
The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets.
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,10 +59,11 @@ 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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 |
|
||||
| `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 |
|
||||
| `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) |
|
||||
|
||||
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 need to resolve a domain name to an internal Zone ID in order to manipulate DNS entries.
|
||||
It also needs 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:
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
72
docs/content/dns/zz_gen_cloudru.md
Normal file
72
docs/content/dns/zz_gen_cloudru.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
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. -->
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
83
docs/content/dns/zz_gen_cpanel.md
Normal file
83
docs/content/dns/zz_gen_cpanel.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
title: "CPanel/WHM"
|
||||
date: 2019-03-03T16:39:46+01:00
|
||||
draft: false
|
||||
slug: cpanel
|
||||
dnsprovider:
|
||||
since: "v4.16.0"
|
||||
code: "cpanel"
|
||||
url: "https://cpanel.net/"
|
||||
---
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/cpanel/cpanel.toml -->
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
|
||||
|
||||
Configuration for [CPanel/WHM](https://cpanel.net/).
|
||||
|
||||
|
||||
<!--more-->
|
||||
|
||||
- Code: `cpanel`
|
||||
- Since: v4.16.0
|
||||
|
||||
|
||||
Here is an example bash command using the CPanel/WHM provider:
|
||||
|
||||
```bash
|
||||
### CPANEL (default)
|
||||
|
||||
CPANEL_USERNAME = "yyyy"
|
||||
CPANEL_TOKEN = "xxxx"
|
||||
CPANEL_BASE_URL = "https://example.com:2083" \
|
||||
lego --email you@example.com --dns cpanel --domains my.example.org run
|
||||
|
||||
## WHM
|
||||
|
||||
CPANEL_MODE = whm
|
||||
CPANEL_USERNAME = "yyyy"
|
||||
CPANEL_TOKEN = "xxxx"
|
||||
CPANEL_BASE_URL = "https://example.com:2087" \
|
||||
lego --email you@example.com --dns cpanel --domains my.example.org run
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Credentials
|
||||
|
||||
| Environment Variable Name | Description |
|
||||
|-----------------------|-------------|
|
||||
| `CPANEL_BASE_URL` | API server URL |
|
||||
| `CPANEL_TOKEN` | API token |
|
||||
| `CPANEL_USERNAME` | username |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
## Additional Configuration
|
||||
|
||||
| Environment Variable Name | Description |
|
||||
|--------------------------------|-------------|
|
||||
| `CPANEL_HTTP_TIMEOUT` | API request timeout |
|
||||
| `CPANEL_MODE` | use cpanel API or WHM API (Default: cpanel) |
|
||||
| `CPANEL_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||
| `CPANEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||
| `CPANEL_REGION` | The region |
|
||||
| `CPANEL_TTL` | The TTL of the TXT record used for the DNS challenge |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
|
||||
|
||||
## More information
|
||||
|
||||
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/cpanel/cpanel.toml -->
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,11 +77,12 @@ 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
|
||||
|
||||
|
@ -98,6 +99,10 @@ For more information, you can read about the different methods of authentication
|
|||
- [Keystone username/password](https://docs.openstack.org/keystone/latest/user/supported_clients.html)
|
||||
- [Keystone application credentials](https://docs.openstack.org/keystone/latest/user/application_credentials.html)
|
||||
|
||||
Public cloud providers with support for Designate:
|
||||
|
||||
- [Fuga Cloud](https://fuga.cloud/)
|
||||
|
||||
|
||||
|
||||
## More information
|
||||
|
|
|
@ -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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
72
docs/content/dns/zz_gen_directadmin.md
Normal file
72
docs/content/dns/zz_gen_directadmin.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
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. -->
|
|
@ -43,9 +43,20 @@ lego --email you@example.com --dns dnshomede --domains my.example.org --domains
|
|||
| `DNSHOMEDE_CREDENTIALS` | Comma-separated list of domain:password credential pairs |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
## Additional Configuration
|
||||
|
||||
| Environment Variable Name | Description |
|
||||
|--------------------------------|-------------|
|
||||
| `DNSHOMEDE_HTTP_TIMEOUT` | API request timeout |
|
||||
| `DNSHOMEDE_POLLING_INTERVAL` | Time between DNS propagation checks |
|
||||
| `DNSHOMEDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation; defaults to 300s (5 minutes) |
|
||||
| `DNSHOMEDE_SEQUENCE_INTERVAL` | Time between sequential requests |
|
||||
|
||||
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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns dnsimple --domains my.example.org run
|
|||
| `DNSIMPLE_OAUTH_TOKEN` | OAuth 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" >}}).
|
|||
| `DNSIMPLE_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
|
||||
|
||||
|
@ -61,7 +61,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
if `DNSIMPLE_BASE_URL` is not defined or empty, the production URL is used by default.
|
||||
|
||||
While you can manage DNS records in the [DNSimple Sandbox environment](https://developer.dnsimple.com/sandbox/),
|
||||
DNS records will not resolve and you will not be able to satisfy the ACME DNS challenge.
|
||||
DNS records will not resolve, and you will not be able to satisfy the ACME DNS challenge.
|
||||
|
||||
To authenticate you need to provide a valid API token.
|
||||
HTTP Basic Authentication is intentionally not supported.
|
||||
|
@ -69,7 +69,7 @@ HTTP Basic Authentication is intentionally not supported.
|
|||
### API tokens
|
||||
|
||||
You can [generate a new API token](https://support.dnsimple.com/articles/api-access-token/) from your account page.
|
||||
Only Account API tokens are supported, if you try to use an User API token you will receive an error message.
|
||||
Only Account API tokens are supported, if you try to use a User API token you will receive an error message.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ lego --email you@example.com --dns dnsmadeeasy --domains my.example.org run
|
|||
| `DNSMADEEASY_API_SECRET` | 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
|
||||
|
@ -56,7 +56,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
| `DNSMADEEASY_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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns dnspod --domains my.example.org run
|
|||
| `DNSPOD_API_KEY` | The user 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" >}}).
|
|||
| `DNSPOD_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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns dode --domains my.example.org run
|
|||
| `DODE_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,14 +54,14 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
| `DODE_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.do.de/wiki/LetsEncrypt_-_Entwickler)
|
||||
- [API documentation](https://www.do.de/wiki/freie-ssl-tls-zertifikate-ueber-acme/)
|
||||
|
||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||
<!-- providers/dns/dode/dode.toml -->
|
||||
|
|
|
@ -42,7 +42,7 @@ lego --email example@example.com --dns domeneshop --domains example.com run
|
|||
| `DOMENESHOP_API_TOKEN` | API token |
|
||||
|
||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||
|
||||
|
||||
## Additional Configuration
|
||||
|
@ -54,7 +54,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
| `DOMENESHOP_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" %}}).
|
||||
|
||||
### API credentials
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns dreamhost --domains my.example.org run
|
|||
| `DREAMHOST_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
|
||||
|
@ -53,7 +53,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
|||
| `DREAMHOST_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" %}}).
|
||||
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue