Compare commits
193 commits
tcl/master
...
feature/ht
Author | SHA1 | Date | |
---|---|---|---|
256957db5c | |||
10ccc57587 | |||
597d147c7d | |||
61ce76f648 | |||
9ff9d5be25 | |||
254983fbe2 | |||
d8c8aba312 | |||
30563a0fb1 | |||
23e60f1e98 | |||
bb87c097ba | |||
|
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 |
611 changed files with 19463 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.
|
description: Create a report to help us improve.
|
||||||
labels: [bug]
|
labels: [bug]
|
||||||
body:
|
body:
|
||||||
|
@ -42,6 +42,7 @@ body:
|
||||||
- Through Caddy
|
- Through Caddy
|
||||||
- Through Terraform ACME provider
|
- Through Terraform ACME provider
|
||||||
- Through Bitnami
|
- Through Bitnami
|
||||||
|
- Through Zoraxy
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +1,8 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Questions
|
- name: ❓ Questions
|
||||||
url: https://github.com/go-acme/lego/discussions
|
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!
|
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/
|
url: https://go-acme.github.io/lego/
|
||||||
about: Please take a look to our documentation.
|
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.
|
description: Suggest an idea for this project.
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
@ -21,6 +21,7 @@ body:
|
||||||
- Through Caddy
|
- Through Caddy
|
||||||
- Through Terraform ACME provider
|
- Through Terraform ACME provider
|
||||||
- Through Bitnami
|
- Through Bitnami
|
||||||
|
- Through Zoraxy
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
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.
|
description: Request for the support of a new DNS provider.
|
||||||
title: "Support for provider: <put the name of your provider>"
|
title: "Support for provider: <put the name of your provider>"
|
||||||
labels: [enhancement, new-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
|
name: Build and deploy documentation
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GO_VERSION: '1.20'
|
GO_VERSION: stable
|
||||||
HUGO_VERSION: 0.101.0
|
HUGO_VERSION: 0.131.0
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
steps:
|
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
|
# https://github.com/marketplace/actions/checkout
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Generate DNS docs
|
||||||
run: make generate-dns
|
run: make generate-dns
|
||||||
|
|
||||||
- name: Install Hugo
|
- name: Install Hugo
|
||||||
run: |
|
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
|
sudo dpkg -i /tmp/hugo.deb
|
||||||
|
|
||||||
- name: Build Documentation
|
- name: Build Documentation
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/github-pages
|
# https://github.com/marketplace/actions/github-pages
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: crazy-max/ghaction-github-pages@v3
|
uses: crazy-max/ghaction-github-pages@v4
|
||||||
with:
|
with:
|
||||||
target_branch: gh-pages
|
target_branch: gh-pages
|
||||||
build_dir: docs/public
|
build_dir: docs/public
|
||||||
|
|
30
.github/workflows/go-cross.yml
vendored
30
.github/workflows/go-cross.yml
vendored
|
@ -16,37 +16,19 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [ '1.19', '1.20', 1.x ]
|
go-version: [ oldstable, stable ]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
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
|
# https://github.com/marketplace/actions/checkout
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/cache
|
# https://github.com/marketplace/actions/setup-go-environment
|
||||||
- name: Cache Go modules
|
- name: Set up Go ${{ matrix.go-version }}
|
||||||
uses: actions/cache@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
# In order:
|
go-version: ${{ matrix.go-version }}
|
||||||
# * Module download cache
|
|
||||||
# * Build cache (Linux)
|
|
||||||
# * Build cache (Mac)
|
|
||||||
# * Build cache (Windows)
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
~/.cache/go-build
|
|
||||||
~/Library/Caches/go-build
|
|
||||||
%LocalAppData%\go-build
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v -cover ./...
|
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
|
name: Main Process
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GO_VERSION: '1.20'
|
GO_VERSION: stable
|
||||||
GOLANGCI_LINT_VERSION: v1.53.1
|
GOLANGCI_LINT_VERSION: v1.60.1
|
||||||
HUGO_VERSION: 0.54.0
|
HUGO_VERSION: 0.131.0
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
LEGO_E2E_TESTS: CI
|
LEGO_E2E_TESTS: CI
|
||||||
MEMCACHED_HOSTS: localhost:11211
|
MEMCACHED_HOSTS: localhost:11211
|
||||||
|
|
||||||
steps:
|
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
|
# https://github.com/marketplace/actions/checkout
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/cache
|
# https://github.com/marketplace/actions/setup-go-environment
|
||||||
- name: Cache Go modules
|
- name: Set up Go ${{ env.GO_VERSION }}
|
||||||
uses: actions/cache@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
go-version: ${{ env.GO_VERSION }}
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Check and get dependencies
|
- name: Check and get dependencies
|
||||||
run: |
|
run: |
|
||||||
|
@ -55,10 +46,10 @@ jobs:
|
||||||
golangci-lint --version
|
golangci-lint --version
|
||||||
|
|
||||||
- name: Install Pebble
|
- 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
|
- 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
|
- name: Set up a Memcached server
|
||||||
uses: niden/actions-memcached@v7
|
uses: niden/actions-memcached@v7
|
||||||
|
@ -78,7 +69,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install Hugo
|
- name: Install Hugo
|
||||||
run: |
|
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
|
sudo dpkg -i /tmp/hugo.deb
|
||||||
|
|
||||||
- name: Build Documentation
|
- name: Build Documentation
|
||||||
|
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
|
@ -11,10 +11,16 @@ jobs:
|
||||||
name: Release version
|
name: Release version
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GO_VERSION: '1.20'
|
GO_VERSION: stable
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
steps:
|
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
|
# https://github.com/marketplace/actions/free-disk-space-ubuntu
|
||||||
- name: Free Disk Space
|
- name: Free Disk Space
|
||||||
|
@ -32,12 +38,12 @@ jobs:
|
||||||
swap-storage: false
|
swap-storage: false
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go ${{ env.GO_VERSION }}
|
- name: Set up Go ${{ env.GO_VERSION }}
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
@ -47,17 +53,21 @@ jobs:
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
|
run: echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
|
||||||
|
|
||||||
|
- name: Install snapcraft
|
||||||
|
run: sudo snap install snapcraft --classic
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
# https://goreleaser.com/ci/actions/
|
# https://goreleaser.com/ci/actions/
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release -p 1 --clean --timeout=90m
|
args: release -p 1 --clean --timeout=90m
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}
|
||||||
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
skip-files: []
|
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
check-shadowing: true
|
enable:
|
||||||
|
- shadow
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 12
|
min-complexity: 12
|
||||||
maligned:
|
|
||||||
suggest-new: true
|
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 3
|
min-len: 3
|
||||||
min-occurrences: 3
|
min-occurrences: 3
|
||||||
|
@ -88,20 +86,20 @@ linters-settings:
|
||||||
disabled: true
|
disabled: true
|
||||||
- name: unreachable-code
|
- name: unreachable-code
|
||||||
- name: redefines-builtin-id
|
- name: redefines-builtin-id
|
||||||
|
testifylint:
|
||||||
|
disable:
|
||||||
|
- require-error
|
||||||
|
- go-require
|
||||||
|
perfsprint:
|
||||||
|
err-error: true
|
||||||
|
errorf: true
|
||||||
|
sprintf1: true
|
||||||
|
strconcat: false
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
disable:
|
disable:
|
||||||
- deadcode # deprecated
|
- gomnd # deprecated
|
||||||
- exhaustivestruct # deprecated
|
|
||||||
- golint # deprecated
|
|
||||||
- ifshort # deprecated
|
|
||||||
- interfacer # deprecated
|
|
||||||
- maligned # deprecated
|
|
||||||
- nosnakecase # deprecated
|
|
||||||
- scopelint # deprecated
|
|
||||||
- structcheck # deprecated
|
|
||||||
- varcheck # deprecated
|
|
||||||
- cyclop # duplicate of gocyclo
|
- cyclop # duplicate of gocyclo
|
||||||
- sqlclosecheck # not relevant (SQL)
|
- sqlclosecheck # not relevant (SQL)
|
||||||
- rowserrcheck # not relevant (SQL)
|
- rowserrcheck # not relevant (SQL)
|
||||||
|
@ -111,13 +109,13 @@ linters:
|
||||||
- dupl # not relevant
|
- dupl # not relevant
|
||||||
- prealloc # too many false-positive
|
- prealloc # too many false-positive
|
||||||
- bodyclose # too many false-positive
|
- bodyclose # too many false-positive
|
||||||
- gomnd
|
- mnd
|
||||||
- testpackage # not relevant
|
- testpackage # not relevant
|
||||||
- tparallel # not relevant
|
- tparallel # not relevant
|
||||||
- paralleltest # not relevant
|
- paralleltest # not relevant
|
||||||
- nestif # too many false-positive
|
- nestif # too many false-positive
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
- goerr113 # not relevant
|
- err113 # not relevant
|
||||||
- nlreturn # not relevant
|
- nlreturn # not relevant
|
||||||
- wsl # not relevant
|
- wsl # not relevant
|
||||||
- exhaustive # not relevant
|
- exhaustive # not relevant
|
||||||
|
@ -137,10 +135,13 @@ linters:
|
||||||
- nonamedreturns
|
- nonamedreturns
|
||||||
- musttag # false-positive https://github.com/junk1tm/musttag/issues/17
|
- musttag # false-positive https://github.com/junk1tm/musttag/issues/17
|
||||||
- gosmopolitan # not relevant
|
- 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:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
max-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
exclude:
|
exclude:
|
||||||
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
- '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
|
- funlen
|
||||||
- goconst
|
- goconst
|
||||||
- maintidx
|
- maintidx
|
||||||
|
- path: (.+)_test.go
|
||||||
|
text: 'Error return value of `fmt.Fprintln` is not checked'
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
- path: providers/dns/dns_providers.go
|
- path: providers/dns/dns_providers.go
|
||||||
linters:
|
linters:
|
||||||
- gocyclo
|
- gocyclo
|
||||||
|
@ -181,6 +186,8 @@ issues:
|
||||||
text: load is a global variable
|
text: load is a global variable
|
||||||
- path: 'providers/dns/([\d\w]+/)*[\d\w]+_test.go'
|
- path: 'providers/dns/([\d\w]+/)*[\d\w]+_test.go'
|
||||||
text: 'envTest is a global variable'
|
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
|
- path: providers/dns/namecheap/namecheap_test.go
|
||||||
text: 'testCases is a global variable'
|
text: 'testCases is a global variable'
|
||||||
- path: providers/dns/acmedns/acmedns_test.go
|
- path: providers/dns/acmedns/acmedns_test.go
|
||||||
|
@ -221,5 +228,18 @@ issues:
|
||||||
- path: providers/dns/hosttech/internal/client_test.go
|
- path: providers/dns/hosttech/internal/client_test.go
|
||||||
text: 'Duplicate words \(0\) found'
|
text: 'Duplicate words \(0\) found'
|
||||||
- path: cmd/cmd_renew.go
|
- 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
|
- goos: openbsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '(?i)^chore:'
|
||||||
|
- '(?i)^Detach v[\d|.]+'
|
||||||
|
- '(?i)^Prepare release v[\d|.]+'
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: lego
|
- id: lego
|
||||||
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}'
|
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.revision={{.FullCommit}}'
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
- '--label=org.opencontainers.image.version={{.Version}}'
|
||||||
- '--platform=linux/arm/v7'
|
- '--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
|
# 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
|
## [v4.13.2] - 2023-07-21
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** servercow: fix regression
|
- **[dnsprovider]** servercow: fix regression
|
||||||
|
|
||||||
## [v4.13.1] - 2023-07-20
|
## [v4.13.1] - 2023-07-20
|
||||||
|
|
||||||
### Added:
|
### Added
|
||||||
|
|
||||||
- **[dnsprovider]** Add DNS provider for IPv64
|
- **[dnsprovider]** Add DNS provider for IPv64
|
||||||
- **[dnsprovider]** Add DNS provider for Metaname
|
- **[dnsprovider]** Add DNS provider for Metaname
|
||||||
- **[dnsprovider]** Add DNS provider for RcodeZero
|
- **[dnsprovider]** Add DNS provider for RcodeZero
|
||||||
|
@ -15,10 +230,12 @@
|
||||||
- **[dnsprovider]** azure: new implementation based on the new API client
|
- **[dnsprovider]** azure: new implementation based on the new API client
|
||||||
- **[lib]** Experimental option to force DNS queries to use TCP
|
- **[lib]** Experimental option to force DNS queries to use TCP
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
|
|
||||||
- **[dnsprovider]** cloudflare: update api client to v0.70.0
|
- **[dnsprovider]** cloudflare: update api client to v0.70.0
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider,cname]** fix: ensure case-insensitive comparison of CNAME records
|
- **[dnsprovider,cname]** fix: ensure case-insensitive comparison of CNAME records
|
||||||
- **[cli]** fix: list command
|
- **[cli]** fix: list command
|
||||||
- **[lib]** fix: ARI explanationURL
|
- **[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
|
## [v4.12.2] - 2023-06-19
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** dnsmadeeasy: fix DeleteRecord
|
- **[dnsprovider]** dnsmadeeasy: fix DeleteRecord
|
||||||
- **[lib]** fix: read status code from response
|
- **[lib]** fix: read status code from response
|
||||||
|
|
||||||
## [v4.12.1] - 2023-06-06
|
## [v4.12.1] - 2023-06-06
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** pdns: fix record value
|
- **[dnsprovider]** pdns: fix record value
|
||||||
|
|
||||||
## [v4.12.0] - 2023-05-28
|
## [v4.12.0] - 2023-05-28
|
||||||
|
|
||||||
### Added:
|
### Added
|
||||||
|
|
||||||
- **[lib,cli]** Initial ACME Renewal Info (ARI) Implementation
|
- **[lib,cli]** Initial ACME Renewal Info (ARI) Implementation
|
||||||
- **[dnsprovider]** Add DNS provider for Derak Cloud
|
- **[dnsprovider]** Add DNS provider for Derak Cloud
|
||||||
- **[dnsprovider]** route53: pass ExternalID property to STS:AssumeRole API operation
|
- **[dnsprovider]** route53: pass ExternalID property to STS:AssumeRole API operation
|
||||||
- **[lib,cli]** Support custom duration for certificate
|
- **[lib,cli]** Support custom duration for certificate
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
|
|
||||||
- **[dnsprovider]** Refactor DNS provider and client implementations
|
- **[dnsprovider]** Refactor DNS provider and client implementations
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used
|
- **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used
|
||||||
- **[cli]** fix: archive only domain-related files on revoke
|
- **[cli]** fix: archive only domain-related files on revoke
|
||||||
|
|
||||||
## [v4.11.0] - 2023-05-02
|
## [v4.11.0] - 2023-05-02
|
||||||
|
|
||||||
### Added:
|
### Added
|
||||||
|
|
||||||
- **[lib]** Support for certificate with raw IP SAN (RFC8738)
|
- **[lib]** Support for certificate with raw IP SAN (RFC8738)
|
||||||
- **[dnsprovider]** Add Brandit.com as DNS provider
|
- **[dnsprovider]** Add Brandit.com as DNS provider
|
||||||
- **[dnsprovider]** Add DNS provider for Bunny
|
- **[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 Google Domains as DNS provider
|
||||||
- **[dnsprovider]** Add DNS provider for Plesk
|
- **[dnsprovider]** Add DNS provider for Plesk
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
|
|
||||||
- **[cli]** feat: add LEGO_CERT_PEM_PATH and LEGO_CERT_PFX_PATH to run hook
|
- **[cli]** feat: add LEGO_CERT_PEM_PATH and LEGO_CERT_PFX_PATH to run hook
|
||||||
- **[lib,cli]** feat: add RSA 3072
|
- **[lib,cli]** feat: add RSA 3072
|
||||||
- **[dnsprovider]** gcloud: update google APIs to latest version
|
- **[dnsprovider]** gcloud: update google APIs to latest version
|
||||||
- **[lib,dnsprovider,cname]** chore: replace GetRecord by GetChallengeInfo
|
- **[lib,dnsprovider,cname]** chore: replace GetRecord by GetChallengeInfo
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** rimuhosting: fix API base URL
|
- **[dnsprovider]** rimuhosting: fix API base URL
|
||||||
|
|
||||||
## [v4.10.2] - 2023-02-26
|
## [v4.10.2] - 2023-02-26
|
||||||
|
@ -78,26 +303,30 @@ Fix Docker image builds.
|
||||||
|
|
||||||
## [v4.10.1] - 2023-02-25
|
## [v4.10.1] - 2023-02-25
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider,cname]** acmedns: fix CNAME support
|
- **[dnsprovider,cname]** acmedns: fix CNAME support
|
||||||
- **[dnsprovider]** dynu: fix subdomain support
|
- **[dnsprovider]** dynu: fix subdomain support
|
||||||
|
|
||||||
## [v4.10.0] - 2023-02-10
|
## [v4.10.0] - 2023-02-10
|
||||||
|
|
||||||
### Added:
|
### Added
|
||||||
|
|
||||||
- **[dnsprovider]** Add DNS provider for dnsHome.de
|
- **[dnsprovider]** Add DNS provider for dnsHome.de
|
||||||
- **[dnsprovider]** Add DNS provider for Liara
|
- **[dnsprovider]** Add DNS provider for Liara
|
||||||
- **[dnsprovider]** Add DNS provider for UltraDNS
|
- **[dnsprovider]** Add DNS provider for UltraDNS
|
||||||
- **[dnsprovider]** Add DNS provider for Websupport
|
- **[dnsprovider]** Add DNS provider for Websupport
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
|
|
||||||
- **[dnsprovider]** ibmcloud: add support for subdomains
|
- **[dnsprovider]** ibmcloud: add support for subdomains
|
||||||
- **[dnsprovider]** infomaniak: CNAME support
|
- **[dnsprovider]** infomaniak: CNAME support
|
||||||
- **[dnsprovider]** namesilo: add cleanup before add a DNS record
|
- **[dnsprovider]** namesilo: add cleanup before add a DNS record
|
||||||
- **[dnsprovider]** route53: Allow static credentials to be supplied
|
- **[dnsprovider]** route53: Allow static credentials to be supplied
|
||||||
- **[dnsprovider]** tencentcloud: support punycode domain
|
- **[dnsprovider]** tencentcloud: support punycode domain
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** alidns: filter on record type
|
- **[dnsprovider]** alidns: filter on record type
|
||||||
- **[dnsprovider]** arvancloud: replace arvancloud.com by arvancloud.ir
|
- **[dnsprovider]** arvancloud: replace arvancloud.com by arvancloud.ir
|
||||||
- **[dnsprovider]** hetzner: improve zone ID detection
|
- **[dnsprovider]** hetzner: improve zone ID detection
|
||||||
|
@ -107,12 +336,12 @@ Fix Docker image builds.
|
||||||
|
|
||||||
## [v4.9.1] - 2022-11-25
|
## [v4.9.1] - 2022-11-25
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
-
|
-
|
||||||
- **[lib,cname]** cname: add log about CNAME entries
|
- **[lib,cname]** cname: add log about CNAME entries
|
||||||
- **[dnsprovider]** regru: improve error handling
|
- **[dnsprovider]** regru: improve error handling
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
-
|
-
|
||||||
- **[dnsprovider,cname]** fix CNAME support for multiple DNS providers
|
- **[dnsprovider,cname]** fix CNAME support for multiple DNS providers
|
||||||
- **[dnsprovider,cname]** duckdns: fix CNAME support
|
- **[dnsprovider,cname]** duckdns: fix CNAME support
|
||||||
|
@ -122,7 +351,7 @@ Fix Docker image builds.
|
||||||
|
|
||||||
## [v4.9.0] - 2022-10-03
|
## [v4.9.0] - 2022-10-03
|
||||||
|
|
||||||
### Added:
|
### Added
|
||||||
|
|
||||||
- **[dnsprovider]** Add DNS provider for CIVO
|
- **[dnsprovider]** Add DNS provider for CIVO
|
||||||
- **[dnsprovider]** Add DNS provider for VK Cloud
|
- **[dnsprovider]** Add DNS provider for VK Cloud
|
||||||
|
@ -131,7 +360,7 @@ Fix Docker image builds.
|
||||||
- **[dnsprovider]** loopia: add configurable API endpoint
|
- **[dnsprovider]** loopia: add configurable API endpoint
|
||||||
- **[dnsprovider]** pdns: notify secondary servers after updates
|
- **[dnsprovider]** pdns: notify secondary servers after updates
|
||||||
|
|
||||||
### Changed:
|
### Changed
|
||||||
|
|
||||||
- **[dnsprovider]** allinkl: removed deprecated sha1 hashing
|
- **[dnsprovider]** allinkl: removed deprecated sha1 hashing
|
||||||
- **[dnsprovider]** auroradns: update authentification
|
- **[dnsprovider]** auroradns: update authentification
|
||||||
|
@ -144,7 +373,8 @@ Fix Docker image builds.
|
||||||
- **[lib,cname]** add recursive CNAME lookup support
|
- **[lib,cname]** add recursive CNAME lookup support
|
||||||
- **[lib]** Remove embedded issuer certificates from issued certificate if bundle is false
|
- **[lib]** Remove embedded issuer certificates from issued certificate if bundle is false
|
||||||
|
|
||||||
### Fixed:
|
### Fixed
|
||||||
|
|
||||||
- **[dnsprovider]** luadns: fix cname support
|
- **[dnsprovider]** luadns: fix cname support
|
||||||
- **[dnsprovider]** njalla: fix record id unmarshal error
|
- **[dnsprovider]** njalla: fix record id unmarshal error
|
||||||
- **[dnsprovider]** tencentcloud: fix subdomain 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]** azure: Allow for the use of MSI
|
||||||
- **[dnsprovider]** constellix: improve challenge.
|
- **[dnsprovider]** constellix: improve challenge.
|
||||||
- **[dnsprovider]** godaddy: allow parallel solve.
|
- **[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
|
- **[dnsprovider]** transip: updated the client to v6
|
||||||
|
|
||||||
### Fixed:
|
### 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 Constellix
|
||||||
- **[dnsprovider]** Add DNS provider for Servercow.
|
- **[dnsprovider]** Add DNS provider for Servercow.
|
||||||
- **[dnsprovider]** Add DNS provider for Scaleway
|
- **[dnsprovider]** Add DNS provider for Scaleway
|
||||||
- **[cli]** Add "LEGO_PATH" environment variable
|
- **[cli]** Add "LEGO_PATH" environment variable
|
||||||
|
|
||||||
### Changed:
|
### Changed:
|
||||||
|
|
||||||
|
@ -522,7 +752,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
|
||||||
### Fixed:
|
### Fixed:
|
||||||
|
|
||||||
- **[dnsprovider]** zoneee: fix subdomains.
|
- **[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.
|
- **[dnsprovider]** dnspod: update lib.
|
||||||
- **[lib]** crypto: Treat CommonName as optional
|
- **[lib]** crypto: Treat CommonName as optional
|
||||||
- **[lib]** chore: update cenkalti/backoff to v4.
|
- **[lib]** chore: update cenkalti/backoff to v4.
|
||||||
|
@ -547,7 +777,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
|
||||||
|
|
||||||
### Changed:
|
### Changed:
|
||||||
- **[dnsprovider]** httpreq: Allow use environment vars from a `_FILE` file
|
- **[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
|
- **[lib]** Expose more SOA fields found by dns01.FindZoneByFqdn
|
||||||
|
|
||||||
### Fixed:
|
### Fixed:
|
||||||
|
@ -575,7 +805,7 @@ Cancelled due to a CI issue, replaced by v4.5.2.
|
||||||
|
|
||||||
## [v3.0.1] - 2019-08-14
|
## [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
|
## [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]** Adds `Remove` for challenges
|
||||||
- **[lib]** Add version to xenolf-acme in User-Agent.
|
- **[lib]** Add version to xenolf-acme in User-Agent.
|
||||||
- **[dnsprovider]** The ability for a DNS provider to solve the challenge sequentially
|
- **[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 Vscale
|
||||||
- **[dnsprovider]** Add DNS Provider for TransIP
|
- **[dnsprovider]** Add DNS Provider for TransIP
|
||||||
- **[dnsprovider]** Add DNS Provider for inwx
|
- **[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:
|
### Added:
|
||||||
- lib: A new DNS provider for OTC.
|
- 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 `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.
|
- lib: The `GCE_SERVICE_ACCOUNT_FILE` environment variable to specify a service account file for the Google Cloud DNS provider.
|
||||||
|
|
||||||
### Fixed:
|
### 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 `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.
|
- 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 `--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.
|
- 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.
|
- lib: Added a memcached provider for the HTTP challenge.
|
||||||
- CLI: The `--memcached-host` flag. This allows to use memcached for challenge storage.
|
- 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.
|
- lib: The library will now skip challenge solving if a valid Authz already exists.
|
||||||
|
|
||||||
### Removed:
|
### 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:
|
### Fixed:
|
||||||
- lib: Fix a problem with the Route53 provider where it was possible the verification was published to a private zone.
|
- 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: 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.
|
- 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.
|
- 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:
|
### Changed:
|
||||||
- lib: NewClient does no longer accept the optPort parameter
|
- 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: 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: 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.
|
- 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.
|
- 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.
|
- 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
|
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.
|
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)
|
- 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 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension
|
||||||
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses
|
- Support [RFC 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
|
- Register with CA
|
||||||
- Obtain certificates, both from scratch or with an existing CSR
|
- Obtain certificates, both from scratch or with an existing CSR
|
||||||
- Renew certificates
|
- 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/) |
|
| [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/) |
|
| [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/) |
|
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
||||||
| [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/) |
|
| [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/) |
|
| [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/) |
|
| [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/) |
|
| [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/) |
|
| [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 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/) | |
|
| [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 -->
|
<!-- 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)
|
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||||
location := getLocation(resp)
|
location := getLocation(resp)
|
||||||
|
|
||||||
if len(location) > 0 {
|
if location != "" {
|
||||||
a.core.jws.SetKid(location)
|
a.core.jws.SetKid(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ func (n *Manager) getNonce() (string, error) {
|
||||||
return GetFromResponse(resp)
|
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) {
|
func GetFromResponse(resp *http.Response) (string, error) {
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return "", errors.New("nil response")
|
return "", errors.New("nil response")
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
"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.
|
// JWS Represents a JWS.
|
||||||
|
|
|
@ -119,6 +119,14 @@ func (d *Doer) formatUserAgent() string {
|
||||||
|
|
||||||
func checkError(req *http.Request, resp *http.Response) error {
|
func checkError(req *http.Request, resp *http.Response) error {
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests {
|
||||||
|
return acme.TooManyRequestsError{
|
||||||
|
StatusCode: resp.StatusCode,
|
||||||
|
Method: req.Method,
|
||||||
|
URL: req.URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
||||||
|
|
|
@ -5,10 +5,10 @@ package sender
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ourUserAgent is the User-Agent of this underlying library package.
|
// 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.
|
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||||
// values: detach|release
|
// values: detach|release
|
||||||
// NOTE: Update this with each tagged release.
|
// NOTE: Update this with each tagged release.
|
||||||
ourUserAgentComment = "detach"
|
ourUserAgentComment = "release"
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,6 +13,10 @@ import (
|
||||||
type OrderOptions struct {
|
type OrderOptions struct {
|
||||||
NotBefore time.Time
|
NotBefore time.Time
|
||||||
NotAfter 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
|
type OrderService service
|
||||||
|
@ -45,6 +49,10 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm
|
||||||
if !opts.NotBefore.IsZero() {
|
if !opts.NotBefore.IsZero() {
|
||||||
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.core.GetDirectory().RenewalInfo != "" {
|
||||||
|
orderReq.Replaces = opts.ReplacesCertID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/platform/tester"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -94,7 +94,6 @@ func TestOrderService_NewWithOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -112,7 +111,8 @@ func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jws, err := jose.ParseSigned(string(reqBody))
|
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
|
||||||
|
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ package api
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoARI is returned when the server does not advertise a renewal info endpoint.
|
// 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)
|
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 {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -71,12 +71,12 @@ type Meta struct {
|
||||||
|
|
||||||
// externalAccountRequired (optional, boolean):
|
// externalAccountRequired (optional, boolean):
|
||||||
// If this field is present and set to "true",
|
// 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.
|
// associating the new account with an external account.
|
||||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendedAccount a extended Account.
|
// ExtendedAccount an extended Account.
|
||||||
type ExtendedAccount struct {
|
type ExtendedAccount struct {
|
||||||
Account
|
Account
|
||||||
// Contains the value of the response header `Location`
|
// Contains the value of the response header `Location`
|
||||||
|
@ -91,7 +91,7 @@ type Account struct {
|
||||||
// The status of this account.
|
// The status of this account.
|
||||||
// Possible values are: "valid", "deactivated", and "revoked".
|
// Possible values are: "valid", "deactivated", and "revoked".
|
||||||
// The value "deactivated" should be used to indicate client-initiated deactivation
|
// 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"`
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
// contact (optional, array of string):
|
// contact (optional, array of string):
|
||||||
|
@ -181,6 +181,12 @@ type Order struct {
|
||||||
// certificate (optional, string):
|
// certificate (optional, string):
|
||||||
// A URL for the certificate that has been issued in response to this order
|
// A URL for the certificate that has been issued in response to this order
|
||||||
Certificate string `json:"certificate,omitempty"`
|
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.
|
// Authorization the ACME authorization object.
|
||||||
|
@ -321,7 +327,7 @@ type RenewalInfoResponse struct {
|
||||||
// SuggestedWindow contains two fields, start and end,
|
// 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.
|
// whose values are timestamps which bound the window of time in which the CA recommends renewing the certificate.
|
||||||
SuggestedWindow Window `json:"suggestedWindow"`
|
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,
|
// 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.
|
// or a page documenting which certificates are affected by a mass revocation event.
|
||||||
// Callers SHOULD provide this URL to their operator, if present.
|
// 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.
|
// 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 {
|
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"`
|
CertID string `json:"certID"`
|
||||||
// Replaced is required and indicates whether or not the client considers the certificate to have been replaced.
|
// 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,
|
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors types.
|
// Errors types.
|
||||||
|
@ -56,3 +57,14 @@ func (p ProblemDetails) Error() string {
|
||||||
type NonceError struct {
|
type NonceError struct {
|
||||||
*ProblemDetails
|
*ProblemDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TooManyRequestsError represents API rate limit violations reported by server.
|
||||||
|
type TooManyRequestsError struct {
|
||||||
|
StatusCode int
|
||||||
|
Method string
|
||||||
|
URL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e TooManyRequestsError) Error() string {
|
||||||
|
return fmt.Sprintf("too many requests: HTTP %d: %s (%s)", e.StatusCode, e.URL, e.Method)
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -84,11 +85,11 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
// 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.
|
// 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#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) {
|
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
keyBlockDER, _ := pem.Decode(key)
|
keyBlockDER, _ := pem.Decode(key)
|
||||||
if keyBlockDER == nil {
|
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") {
|
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)
|
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 {
|
func ExtractDomains(cert *x509.Certificate) []string {
|
||||||
var domains []string
|
var domains []string
|
||||||
if cert.Subject.CommonName != "" {
|
if cert.Subject.CommonName != "" {
|
||||||
|
@ -248,7 +269,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||||
|
|
||||||
// loop over the SubjectAltName DNS names
|
// loop over the SubjectAltName DNS names
|
||||||
for _, sanName := range csr.DNSNames {
|
for _, sanName := range csr.DNSNames {
|
||||||
if containsSAN(domains, sanName) {
|
if slices.Contains(domains, sanName) {
|
||||||
// Duplicate; skip this name
|
// Duplicate; skip this name
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -267,15 +288,6 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||||
return domains
|
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) {
|
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -39,14 +39,14 @@ func TestGenerateCSR(t *testing.T) {
|
||||||
expected expected
|
expected expected
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "without SAN",
|
desc: "without SAN (nil)",
|
||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
domain: "lego.acme",
|
domain: "lego.acme",
|
||||||
mustStaple: true,
|
mustStaple: true,
|
||||||
expected: expected{len: 245},
|
expected: expected{len: 245},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "without SAN",
|
desc: "without SAN (empty)",
|
||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
domain: "lego.acme",
|
domain: "lego.acme",
|
||||||
san: []string{},
|
san: []string{},
|
||||||
|
@ -86,7 +86,6 @@ func TestGenerateCSR(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,10 @@ import (
|
||||||
"github.com/go-acme/lego/v4/log"
|
"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) {
|
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
||||||
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
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 {
|
for _, authzURL := range order.Authorizations {
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
|
@ -35,13 +27,14 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
||||||
}
|
}
|
||||||
|
|
||||||
var responses []acme.Authorization
|
var responses []acme.Authorization
|
||||||
failures := make(obtainError)
|
|
||||||
for i := 0; i < len(order.Authorizations); i++ {
|
failures := newObtainError()
|
||||||
|
for range len(order.Authorizations) {
|
||||||
select {
|
select {
|
||||||
case res := <-resc:
|
case res := <-resc:
|
||||||
responses = append(responses, res)
|
responses = append(responses, res)
|
||||||
case err := <-errc:
|
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(resc)
|
||||||
close(errc)
|
close(errc)
|
||||||
|
|
||||||
// be careful to not return an empty failures map;
|
return responses, failures.Join()
|
||||||
// even if empty, they become non-nil error values
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return responses, failures
|
|
||||||
}
|
|
||||||
return responses, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
|
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
|
||||||
|
|
|
@ -22,6 +22,17 @@ import (
|
||||||
"golang.org/x/net/idna"
|
"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.
|
// maxBodySize is the maximum size of body that we will read.
|
||||||
const maxBodySize = 1024 * 1024
|
const maxBodySize = 1024 * 1024
|
||||||
|
|
||||||
|
@ -63,6 +74,10 @@ type ObtainRequest struct {
|
||||||
Bundle bool
|
Bundle bool
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
AlwaysDeactivateAuthorizations bool
|
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.
|
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
||||||
|
@ -79,6 +94,10 @@ type ObtainForCSRRequest struct {
|
||||||
Bundle bool
|
Bundle bool
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
AlwaysDeactivateAuthorizations bool
|
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 {
|
type resolver interface {
|
||||||
|
@ -88,6 +107,7 @@ type resolver interface {
|
||||||
type CertifierOptions struct {
|
type CertifierOptions struct {
|
||||||
KeyType certcrypto.KeyType
|
KeyType certcrypto.KeyType
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
OverallRequestLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certifier A service to obtain/renew/revoke certificates.
|
// Certifier A service to obtain/renew/revoke certificates.
|
||||||
|
@ -95,15 +115,23 @@ type Certifier struct {
|
||||||
core *api.Core
|
core *api.Core
|
||||||
resolver resolver
|
resolver resolver
|
||||||
options CertifierOptions
|
options CertifierOptions
|
||||||
|
overallRequestLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertifier creates a Certifier.
|
// NewCertifier creates a Certifier.
|
||||||
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
||||||
return &Certifier{
|
c := &Certifier{
|
||||||
core: core,
|
core: core,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
options: options,
|
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.
|
// Obtain tries to obtain a single certificate using all domains passed into it.
|
||||||
|
@ -126,6 +154,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||||
orderOpts := &api.OrderOptions{
|
orderOpts := &api.OrderOptions{
|
||||||
NotBefore: request.NotBefore,
|
NotBefore: request.NotBefore,
|
||||||
NotAfter: request.NotAfter,
|
NotAfter: request.NotAfter,
|
||||||
|
ReplacesCertID: request.ReplacesCertID,
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
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, ", "))
|
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)
|
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
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)
|
c.deactivateAuthorizations(order, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not return an empty failures map, because
|
return cert, failures.Join()
|
||||||
// it would still be a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||||
|
@ -196,6 +220,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
||||||
orderOpts := &api.OrderOptions{
|
orderOpts := &api.OrderOptions{
|
||||||
NotBefore: request.NotBefore,
|
NotBefore: request.NotBefore,
|
||||||
NotAfter: request.NotAfter,
|
NotAfter: request.NotAfter,
|
||||||
|
ReplacesCertID: request.ReplacesCertID,
|
||||||
}
|
}
|
||||||
|
|
||||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
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, ", "))
|
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)
|
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
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)
|
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not return an empty failures map,
|
return cert, failures.Join()
|
||||||
// because it would still be a non-nil error value
|
|
||||||
if len(failures) > 0 {
|
|
||||||
return cert, failures
|
|
||||||
}
|
|
||||||
return cert, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
|
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 := ""
|
||||||
commonName := domains[0]
|
if len(domains[0]) <= 64 {
|
||||||
|
commonName = domains[0]
|
||||||
|
}
|
||||||
|
|
||||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
// 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
|
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||||
// "identifiers" or "authorizations" elements in the returned order
|
// "identifiers" or "authorizations" elements in the returned order
|
||||||
// object.
|
// object.
|
||||||
san := []string{commonName}
|
|
||||||
|
var san []string
|
||||||
|
if commonName != "" {
|
||||||
|
san = append(san, commonName)
|
||||||
|
}
|
||||||
|
|
||||||
for _, auth := range order.Identifiers {
|
for _, auth := range order.Identifiers {
|
||||||
if auth.Value != commonName {
|
if auth.Value != commonName {
|
||||||
san = append(san, auth.Value)
|
san = append(san, auth.Value)
|
||||||
|
@ -284,15 +311,14 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commonName := domains[0]
|
|
||||||
certRes := &Resource{
|
certRes := &Resource{
|
||||||
Domain: commonName,
|
Domain: domains[0],
|
||||||
CertURL: respOrder.Certificate,
|
CertURL: respOrder.Certificate,
|
||||||
PrivateKey: privateKeyPem,
|
PrivateKey: privateKeyPem,
|
||||||
}
|
}
|
||||||
|
|
||||||
if respOrder.Status == acme.StatusValid {
|
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)
|
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
||||||
if errR != nil {
|
if errR != nil {
|
||||||
return nil, errR
|
return nil, errR
|
||||||
|
@ -608,8 +634,13 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Resource{
|
return &Resource{
|
||||||
Domain: x509Certs[0].Subject.CommonName,
|
Domain: domain,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
IssuerCertificate: issuer,
|
IssuerCertificate: issuer,
|
||||||
CertURL: url,
|
CertURL: url,
|
||||||
|
|
|
@ -1,27 +1,37 @@
|
||||||
package certificate
|
package certificate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// obtainError is returned when there are specific errors available per domain.
|
type obtainError struct {
|
||||||
type obtainError map[string]error
|
data map[string]error
|
||||||
|
}
|
||||||
|
|
||||||
func (e obtainError) Error() string {
|
func newObtainError() *obtainError {
|
||||||
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
return &obtainError{data: make(map[string]error)}
|
||||||
|
}
|
||||||
|
|
||||||
var domains []string
|
func (e *obtainError) Add(domain string, err error) {
|
||||||
for domain := range e {
|
e.data[domain] = err
|
||||||
domains = append(domains, domain)
|
}
|
||||||
|
|
||||||
|
func (e *obtainError) Join() error {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
sort.Strings(domains)
|
|
||||||
|
|
||||||
for _, domain := range domains {
|
if len(e.data) == 0 {
|
||||||
_, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain])
|
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 {
|
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
|
package certificate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
@ -19,15 +16,17 @@ import (
|
||||||
// RenewalInfoRequest contains the necessary renewal information.
|
// RenewalInfoRequest contains the necessary renewal information.
|
||||||
type RenewalInfoRequest struct {
|
type RenewalInfoRequest struct {
|
||||||
Cert *x509.Certificate
|
Cert *x509.Certificate
|
||||||
Issuer *x509.Certificate
|
|
||||||
// HashName must be the string representation of a crypto.Hash constant in the golang.org/x/crypto package (e.g. "SHA-256").
|
|
||||||
// The correct value depends on the algorithm expected by the ACME server's ARI implementation.
|
|
||||||
HashName string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
|
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
|
||||||
type RenewalInfoResponse struct {
|
type RenewalInfoResponse struct {
|
||||||
acme.RenewalInfoResponse
|
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.
|
// 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()
|
end := r.SuggestedWindow.End.UTC()
|
||||||
|
|
||||||
// Select a uniform random time within the suggested window.
|
// Select a uniform random time within the suggested window.
|
||||||
window := end.Sub(start)
|
rt := start
|
||||||
|
if window := end.Sub(start); window > 0 {
|
||||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||||
rt := start.Add(randomDuration)
|
rt = rt.Add(randomDuration)
|
||||||
|
}
|
||||||
|
|
||||||
// If the selected time is in the past, attempt renewal immediately.
|
// If the selected time is in the past, attempt renewal immediately.
|
||||||
if rt.Before(now) {
|
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
|
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari
|
||||||
func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error making certID: %w", err)
|
return nil, fmt.Errorf("error making certID: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -88,117 +89,43 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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.
|
// MakeARICertID constructs a certificate identifier as described in draft-ietf-acme-ari-03, section 4.1.
|
||||||
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
func MakeARICertID(leaf *x509.Certificate) (string, error) {
|
||||||
// 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) {
|
|
||||||
if leaf == nil {
|
if leaf == nil {
|
||||||
return "", fmt.Errorf("leaf certificate is nil")
|
return "", errors.New("leaf certificate is nil")
|
||||||
}
|
|
||||||
if issuer == nil {
|
|
||||||
return "", fmt.Errorf("issuer certificate is nil")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashFunc crypto.Hash
|
// Marshal the Serial Number into DER.
|
||||||
var oid asn1.ObjectIdentifier
|
der, err := asn1.Marshal(leaf.SerialNumber)
|
||||||
|
|
||||||
switch hashName {
|
|
||||||
// The following correlation of hashFunc to OID is copied from a private mapping in golang.org/x/crypto/ocsp:
|
|
||||||
// https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.8.0:ocsp/ocsp.go;l=156
|
|
||||||
case crypto.SHA1.String():
|
|
||||||
hashFunc = crypto.SHA1
|
|
||||||
oid = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
|
|
||||||
|
|
||||||
case crypto.SHA256.String():
|
|
||||||
hashFunc = crypto.SHA256
|
|
||||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
|
|
||||||
|
|
||||||
case crypto.SHA384.String():
|
|
||||||
hashFunc = crypto.SHA384
|
|
||||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2})
|
|
||||||
|
|
||||||
case crypto.SHA512.String():
|
|
||||||
hashFunc = crypto.SHA512
|
|
||||||
oid = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3})
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("hashName %q is not supported by this package", hashName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hashFunc.Available() {
|
|
||||||
// This should never happen.
|
|
||||||
return "", fmt.Errorf("hash function %q is not available on your platform", hashFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
var spki struct {
|
|
||||||
Algorithm pkix.AlgorithmIdentifier
|
|
||||||
PublicKey asn1.BitString
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &spki)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
h := hashFunc.New()
|
|
||||||
h.Write(spki.PublicKey.RightAlign())
|
|
||||||
issuerKeyHash := h.Sum(nil)
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
h.Write(issuer.RawSubject)
|
|
||||||
issuerNameHash := h.Sum(nil)
|
|
||||||
|
|
||||||
type certID struct {
|
|
||||||
HashAlgorithm pkix.AlgorithmIdentifier
|
|
||||||
IssuerNameHash []byte
|
|
||||||
IssuerKeyHash []byte
|
|
||||||
SerialNumber *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// DER-encode the CertID ASN.1 sequence [RFC6960].
|
|
||||||
certIDBytes, err := asn1.Marshal(certID{
|
|
||||||
HashAlgorithm: pkix.AlgorithmIdentifier{
|
|
||||||
Algorithm: oid,
|
|
||||||
},
|
|
||||||
IssuerNameHash: issuerNameHash,
|
|
||||||
IssuerKeyHash: issuerKeyHash,
|
|
||||||
SerialNumber: leaf.SerialNumber,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// base64url-encode [RFC4648] the bytes of the DER-encoded CertID ASN.1 sequence [RFC6960].
|
// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
|
||||||
encodedBytes := base64.URLEncoding.EncodeToString(certIDBytes)
|
// length, and value).
|
||||||
|
if len(der) < 3 {
|
||||||
|
return "", errors.New("invalid DER encoding of serial number")
|
||||||
|
}
|
||||||
|
|
||||||
// Any trailing '=' characters MUST be stripped.
|
// Extract only the integer bytes from the DER encoded Serial Number
|
||||||
return strings.TrimRight(encodedBytes, "="), nil
|
// 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
|
package certificate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,62 +11,28 @@ import (
|
||||||
"github.com/go-acme/lego/v4/acme/api"
|
"github.com/go-acme/lego/v4/acme/api"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/platform/tester"
|
"github.com/go-acme/lego/v4/platform/tester"
|
||||||
"github.com/go-jose/go-jose/v3"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ariLeafPEM = `-----BEGIN CERTIFICATE-----
|
ariLeafPEM = `-----BEGIN CERTIFICATE-----
|
||||||
MIIDMDCCAhigAwIBAgIIPqNFaGVEHxwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
MIIBQzCB66ADAgECAgUAh2VDITAKBggqhkjOPQQDAjAVMRMwEQYDVQQDEwpFeGFt
|
||||||
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MB4XDTIyMDMxNzE3NTEwOVoXDTI0MDQx
|
cGxlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBYxFDAS
|
||||||
NjE3NTEwOVowFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEB
|
BgNVBAMTC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeBZu
|
||||||
AQUAA4IBDwAwggEKAoIBAQCgm9K/c+il2Pf0f8qhgxn9SKqXq88cOm9ov9AVRbPA
|
7cbpAYNXZLbbh8rNIzuOoqOOtmxA1v7cRm//AwyMwWxyHz4zfwmBhcSrf47NUAFf
|
||||||
OWAAewqX2yUAwI4LZBGEgzGzTATkiXfoJ3cN3k39cH6tBbb3iSPuEn7OZpIk9D+e
|
qzLQ2PPQxdTXREYEnKMjMCEwHwYDVR0jBBgwFoAUaYhba4dGQEHhs3uEe6CuLN4B
|
||||||
3Q9/hX+N/jlWkaTB/FNA+7aE5IVWhmdczYilXa10V9r+RcvACJt0gsipBZVJ4jfJ
|
yNQwCgYIKoZIzj0EAwIDRwAwRAIge09+S5TZAlw5tgtiVvuERV6cT4mfutXIlwTb
|
||||||
HnWJJGRZzzxqG/xkQmpXxZO7nOPFc8SxYKWdfcgp+rjR2ogYhSz7BfKoVakGPbpX
|
+FYN/8oCIClDsqBklhB9KAelFiYt9+6FDj3z4KGVelYM5MdsO3pK
|
||||||
vZOuT9z4kkHra/WjwlkQhtHoTXdAxH3qC2UjMzO57Tx+otj0CxAv9O7CTJXISywB
|
|
||||||
vEVcmTSZkHS3eZtvvIwPx7I30ITRkYk/tLl1MbyB3SiZAgMBAAGjeDB2MA4GA1Ud
|
|
||||||
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
|
|
||||||
AQH/BAIwADAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTAWBgNVHREE
|
|
||||||
DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAx0aYvmCk7JYGNEXe
|
|
||||||
+hrOfKawkHYzWvA92cI/Oi6h+oSdHZ2UKzwFNf37cVKZ37FCrrv5pFP/xhhHvrNV
|
|
||||||
EnOx4IaF7OrnaTu5miZiUWuvRQP7ZGmGNFYbLTEF6/dj+WqyYdVaWzxRqHFu1ptC
|
|
||||||
TXysJCeyiGnR+KOOjOOQ9ZlO5JUK3OE4hagPLfaIpDDy6RXQt3ss0iNLuB1+IOtp
|
|
||||||
1URpvffLZQ8xPsEgOZyPWOcabTwJrtqBwily+lwPFn2mChUx846LwQfxtsXU/lJg
|
|
||||||
HX2RteNJx7YYNeX3Uf960mgo5an6vE8QNAsIoNHYrGyEmXDhTRe9mCHyiW2S7fZq
|
|
||||||
o9q12g==
|
|
||||||
-----END CERTIFICATE-----`
|
-----END CERTIFICATE-----`
|
||||||
ariIssuerPEM = `-----BEGIN CERTIFICATE-----
|
ariLeafCertID = "aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE"
|
||||||
MIIDSzCCAjOgAwIBAgIIOhNWtJ7Igr0wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
|
||||||
AxMVbWluaWNhIHJvb3QgY2EgM2ExMzU2MCAXDTIyMDMxNzE3NTEwOVoYDzIxMjIw
|
|
||||||
MzE3MTc1MTA5WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAzYTEzNTYwggEi
|
|
||||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDc3P6cxcCZ7FQOQrYuigReSa8T
|
|
||||||
IOPNKmlmX9OrTkPwjThiMNEETYKO1ea99yXPK36LUHC6OLmZ9jVQW2Ny1qwQCOy6
|
|
||||||
TrquhnwKgtkBMDAZBLySSEXYdKL3r0jA4sflW130/OLwhstU/yv0J8+pj7eSVOR3
|
|
||||||
zJBnYd1AqnXHRSwQm299KXgqema7uwsa8cgjrXsBzAhrwrvYlVhpWFSv3lQRDFQg
|
|
||||||
c5Z/ZDV9i26qiaJsCCmdisJZWN7N2luUgxdRqzZ4Cr2Xoilg3T+hkb2y/d6ttsPA
|
|
||||||
kaSA+pq3q6Qa7/qfGdT5WuUkcHpvKNRWqnwT9rCYlmG00r3hGgc42D/z1VvfAgMB
|
|
||||||
AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
|
||||||
BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ4zzDRUaXHVKql
|
|
||||||
STWkULGU4zGZpTAfBgNVHSMEGDAWgBQ4zzDRUaXHVKqlSTWkULGU4zGZpTANBgkq
|
|
||||||
hkiG9w0BAQsFAAOCAQEArbDHhEjGedjb/YjU80aFTPWOMRjgyfQaPPgyxwX6Dsid
|
|
||||||
1i2H1x4ud4ntz3sTZZxdQIrOqtlIWTWVCjpStwGxaC+38SdreiTTwy/nikXGa/6W
|
|
||||||
ZyQRppR3agh/pl5LHVO6GsJz3YHa7wQhEhj3xsRwa9VrRXgHbLGbPOFVRTHPjaPg
|
|
||||||
Gtsv2PN3f67DsPHF47ASqyOIRpLZPQmZIw6D3isJwfl+8CzvlB1veO0Q3uh08IJc
|
|
||||||
fspYQXvFBzYa64uKxNAJMi4Pby8cf4r36Wnb7cL4ho3fOHgAltxdW8jgibRzqZpQ
|
|
||||||
QKyxn2jX7kxeUDt0hFDJE8lOrhP73m66eBNzxe//FQ==
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
ariLeafCertID = "MFswCwYJYIZIAWUDBAIBBCCeWLRusNLb--vmWOkxm34qDjTMWkc3utIhOMoMwKDqbgQg2iiKWySZrD-6c88HMZ6vhIHZPamChLlzGHeZ7pTS8jYCCD6jRWhlRB8c"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_makeCertID(t *testing.T) {
|
func Test_makeCertID(t *testing.T) {
|
||||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||||
require.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, ariLeafCertID, actual)
|
assert.Equal(t, ariLeafCertID, actual)
|
||||||
}
|
}
|
||||||
|
@ -77,8 +40,6 @@ func Test_makeCertID(t *testing.T) {
|
||||||
func TestCertifier_GetRenewalInfo(t *testing.T) {
|
func TestCertifier_GetRenewalInfo(t *testing.T) {
|
||||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Test with a fake API.
|
// Test with a fake API.
|
||||||
mux, apiURL := tester.SetupFakeAPI(t)
|
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("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Retry-After", "21600")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, wErr := w.Write([]byte(`{
|
_, wErr := w.Write([]byte(`{
|
||||||
"suggestedWindow": {
|
"suggestedWindow": {
|
||||||
|
@ -109,19 +71,18 @@ func TestCertifier_GetRenewalInfo(t *testing.T) {
|
||||||
|
|
||||||
certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048})
|
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.NoError(t, err)
|
||||||
require.NotNil(t, ri)
|
require.NotNil(t, ri)
|
||||||
assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339))
|
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, "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, "https://aricapable.ca/docs/renewal-advice/", ri.ExplanationURL)
|
||||||
|
assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
||||||
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
issuer, err := certcrypto.ParsePEMCertificate([]byte(ariIssuerPEM))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
require.NoError(t, err, "Could not generate test key")
|
require.NoError(t, err, "Could not generate test key")
|
||||||
|
@ -135,7 +96,7 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "API timeout",
|
desc: "API timeout",
|
||||||
httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms.
|
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) {
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
// API that takes 2ms to respond.
|
// API that takes 2ms to respond.
|
||||||
time.Sleep(2 * time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
@ -144,24 +105,15 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "API error",
|
desc: "API error",
|
||||||
httpClient: http.DefaultClient,
|
httpClient: http.DefaultClient,
|
||||||
request: RenewalInfoRequest{leaf, issuer, crypto.SHA256.String()},
|
request: RenewalInfoRequest{leaf},
|
||||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||||
// API that responds with error instead of renewal info.
|
// API that responds with error instead of renewal info.
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
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 {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
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) {
|
func TestRenewalInfoResponse_ShouldRenew(t *testing.T) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
t.Run("Window is in the past", func(t *testing.T) {
|
t.Run("Window is in the past", func(t *testing.T) {
|
||||||
ri := RenewalInfoResponse{
|
ri := RenewalInfoResponse{
|
||||||
acme.RenewalInfoResponse{
|
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||||
SuggestedWindow: acme.Window{
|
SuggestedWindow: acme.Window{
|
||||||
Start: now.Add(-2 * time.Hour),
|
Start: now.Add(-2 * time.Hour),
|
||||||
End: now.Add(-1 * time.Hour),
|
End: now.Add(-1 * time.Hour),
|
||||||
},
|
},
|
||||||
ExplanationURL: "",
|
ExplanationURL: "",
|
||||||
},
|
},
|
||||||
|
RetryAfter: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := ri.ShouldRenewAt(now, 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) {
|
t.Run("Window is in the future", func(t *testing.T) {
|
||||||
ri := RenewalInfoResponse{
|
ri := RenewalInfoResponse{
|
||||||
acme.RenewalInfoResponse{
|
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||||
SuggestedWindow: acme.Window{
|
SuggestedWindow: acme.Window{
|
||||||
Start: now.Add(1 * time.Hour),
|
Start: now.Add(1 * time.Hour),
|
||||||
End: now.Add(2 * time.Hour),
|
End: now.Add(2 * time.Hour),
|
||||||
},
|
},
|
||||||
ExplanationURL: "",
|
ExplanationURL: "",
|
||||||
},
|
},
|
||||||
|
RetryAfter: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := ri.ShouldRenewAt(now, 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) {
|
t.Run("Window is in the future, but caller is willing to sleep", func(t *testing.T) {
|
||||||
ri := RenewalInfoResponse{
|
ri := RenewalInfoResponse{
|
||||||
acme.RenewalInfoResponse{
|
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||||
SuggestedWindow: acme.Window{
|
SuggestedWindow: acme.Window{
|
||||||
Start: now.Add(1 * time.Hour),
|
Start: now.Add(1 * time.Hour),
|
||||||
End: now.Add(2 * time.Hour),
|
End: now.Add(2 * time.Hour),
|
||||||
},
|
},
|
||||||
ExplanationURL: "",
|
ExplanationURL: "",
|
||||||
},
|
},
|
||||||
|
RetryAfter: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := ri.ShouldRenewAt(now, 2*time.Hour)
|
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) {
|
t.Run("Window is in the future, but caller isn't willing to sleep long enough", func(t *testing.T) {
|
||||||
ri := RenewalInfoResponse{
|
ri := RenewalInfoResponse{
|
||||||
acme.RenewalInfoResponse{
|
RenewalInfoResponse: acme.RenewalInfoResponse{
|
||||||
SuggestedWindow: acme.Window{
|
SuggestedWindow: acme.Window{
|
||||||
Start: now.Add(1 * time.Hour),
|
Start: now.Add(1 * time.Hour),
|
||||||
End: now.Add(2 * time.Hour),
|
End: now.Add(2 * time.Hour),
|
||||||
},
|
},
|
||||||
ExplanationURL: "",
|
ExplanationURL: "",
|
||||||
},
|
},
|
||||||
|
RetryAfter: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := ri.ShouldRenewAt(now, 59*time.Minute)
|
rt := ri.ShouldRenewAt(now, 59*time.Minute)
|
||||||
assert.Nil(t, rt)
|
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"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
@ -124,7 +125,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
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)
|
time.Sleep(interval)
|
||||||
|
|
||||||
|
@ -214,7 +215,7 @@ func getChallengeFQDN(domain string, followCNAME bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursion counter so it doesn't spin out of control
|
// recursion counter so it doesn't spin out of control
|
||||||
for limit := 0; limit < 50; limit++ {
|
for range 50 {
|
||||||
// Keep following CNAMEs
|
// Keep following CNAMEs
|
||||||
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
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)
|
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
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)
|
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")
|
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||||
|
|
||||||
_, err = bufio.NewReader(os.Stdin).ReadBytes('\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.
|
// 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)
|
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
||||||
if err != nil {
|
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)
|
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,14 +31,14 @@ func TestDNSProviderManual(t *testing.T) {
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
file, err := os.CreateTemp("", "lego_test")
|
file, err := os.CreateTemp("", "lego_test")
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() { _ = os.Remove(file.Name()) }()
|
defer func() { _ = os.Remove(file.Name()) }()
|
||||||
|
|
||||||
_, err = file.WriteString(test.input)
|
_, err = file.WriteString(test.input)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = file.Seek(0, io.SeekStart)
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
os.Stdin = file
|
os.Stdin = file
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,6 @@ func TestExtractSubDomain(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -93,7 +92,6 @@ func TestExtractSubDomain_errors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -14,18 +14,17 @@ func TestToFqdn(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "simple",
|
desc: "simple",
|
||||||
domain: "foo.bar.com",
|
domain: "foo.example.com",
|
||||||
expected: "foo.bar.com.",
|
expected: "foo.example.com.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "already FQDN",
|
desc: "already FQDN",
|
||||||
domain: "foo.bar.com.",
|
domain: "foo.example.com.",
|
||||||
expected: "foo.bar.com.",
|
expected: "foo.example.com.",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -43,18 +42,17 @@ func TestUnFqdn(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "simple",
|
desc: "simple",
|
||||||
fqdn: "foo.bar.com.",
|
fqdn: "foo.example.",
|
||||||
expected: "foo.bar.com",
|
expected: "foo.example",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "already domain",
|
desc: "already domain",
|
||||||
fqdn: "foo.bar.com",
|
fqdn: "foo.example",
|
||||||
expected: "foo.bar.com",
|
expected: "foo.example",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -15,10 +16,7 @@ import (
|
||||||
|
|
||||||
const defaultResolvConf = "/etc/resolv.conf"
|
const defaultResolvConf = "/etc/resolv.conf"
|
||||||
|
|
||||||
var (
|
var fqdnSoaCache = &sync.Map{}
|
||||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
|
||||||
muFqdnSoaCache sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultNameservers = []string{
|
var defaultNameservers = []string{
|
||||||
"google-public-dns-a.google.com:53",
|
"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.
|
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||||
func ClearFqdnCache() {
|
func ClearFqdnCache() {
|
||||||
muFqdnSoaCache.Lock()
|
// TODO(ldez): use `fqdnSoaCache.Clear()` when updating to go1.23
|
||||||
fqdnSoaCache = map[string]*soaCacheEntry{}
|
fqdnSoaCache.Range(func(k, v any) bool {
|
||||||
muFqdnSoaCache.Unlock()
|
fqdnSoaCache.Delete(k)
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
||||||
|
@ -98,12 +98,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
|
|
||||||
zone, err := FindZoneByFqdn(fqdn)
|
zone, err := FindZoneByFqdn(fqdn)
|
||||||
if err != nil {
|
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)
|
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("NS call failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
|
@ -115,7 +115,8 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
if len(authoritativeNss) > 0 {
|
if len(authoritativeNss) > 0 {
|
||||||
return authoritativeNss, nil
|
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
|
// 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) {
|
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||||
}
|
}
|
||||||
return soa.primaryNs, nil
|
return soa.primaryNs, nil
|
||||||
}
|
}
|
||||||
|
@ -145,60 +146,62 @@ func FindZoneByFqdn(fqdn string) (string, error) {
|
||||||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
||||||
}
|
}
|
||||||
return soa.zone, nil
|
return soa.zone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
muFqdnSoaCache.Lock()
|
|
||||||
defer muFqdnSoaCache.Unlock()
|
|
||||||
|
|
||||||
// Do we have it cached and is it still fresh?
|
// Do we have it cached and is it still fresh?
|
||||||
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
|
entAny, ok := fqdnSoaCache.Load(fqdn)
|
||||||
|
if ok && entAny != nil {
|
||||||
|
ent, ok1 := entAny.(*soaCacheEntry)
|
||||||
|
if ok1 && !ent.isExpired() {
|
||||||
return ent, nil
|
return ent, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fqdnSoaCache[fqdn] = ent
|
fqdnSoaCache.Store(fqdn, ent)
|
||||||
|
|
||||||
return ent, nil
|
return ent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
var err error
|
var err error
|
||||||
var in *dns.Msg
|
var r *dns.Msg
|
||||||
|
|
||||||
labelIndexes := dns.Split(fqdn)
|
labelIndexes := dns.Split(fqdn)
|
||||||
for _, index := range labelIndexes {
|
for _, index := range labelIndexes {
|
||||||
domain := fqdn[index:]
|
domain := fqdn[index:]
|
||||||
|
|
||||||
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if in == nil {
|
if r == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch in.Rcode {
|
switch r.Rcode {
|
||||||
case dns.RcodeSuccess:
|
case dns.RcodeSuccess:
|
||||||
// Check if we got a SOA RR in the answer section
|
// Check if we got a SOA RR in the answer section
|
||||||
if len(in.Answer) == 0 {
|
if len(r.Answer) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// CNAME records cannot/should not exist at the root of a zone.
|
// CNAME records cannot/should not exist at the root of a zone.
|
||||||
// So we skip a domain when a CNAME is found.
|
// So we skip a domain when a CNAME is found.
|
||||||
if dnsMsgContainsCNAME(in) {
|
if dnsMsgContainsCNAME(r) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ans := range in.Answer {
|
for _, ans := range r.Answer {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
return newSoaCacheEntry(soa), nil
|
return newSoaCacheEntry(soa), nil
|
||||||
}
|
}
|
||||||
|
@ -207,36 +210,46 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
// NXDOMAIN
|
// NXDOMAIN
|
||||||
default:
|
default:
|
||||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
// 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.
|
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
||||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||||
for _, ans := range msg.Answer {
|
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
|
||||||
if _, ok := ans.(*dns.CNAME); ok {
|
_, ok := rr.(*dns.CNAME)
|
||||||
return true
|
return ok
|
||||||
}
|
})
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||||
m := createDNSMsg(fqdn, rtype, recursive)
|
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 err error
|
||||||
|
var errAll error
|
||||||
|
|
||||||
for _, ns := range nameservers {
|
for _, ns := range nameservers {
|
||||||
in, err = sendDNSQuery(m, ns)
|
r, err = sendDNSQuery(m, ns)
|
||||||
if err == nil && len(in.Answer) > 0 {
|
if err == nil && len(r.Answer) > 0 {
|
||||||
break
|
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 {
|
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) {
|
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
|
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
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}
|
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}
|
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||||
// If the TCP request succeeds, the err will reset to nil
|
// If the TCP request succeeds, the "err" will reset to nil
|
||||||
in, _, err = tcp.Exchange(m, ns)
|
r, _, err = tcp.Exchange(m, ns)
|
||||||
}
|
|
||||||
|
|
||||||
return in, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDNSError(msg *dns.Msg, err error) string {
|
|
||||||
var parts []string
|
|
||||||
|
|
||||||
if msg != nil {
|
|
||||||
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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 r, nil
|
||||||
return ": " + strings.Join(parts, " ")
|
}
|
||||||
}
|
|
||||||
|
// DNSError error related to DNS calls.
|
||||||
return ""
|
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
|
package dns01
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -28,7 +30,6 @@ func TestLookupNameserversOK(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.fqdn, func(t *testing.T) {
|
t.Run(test.fqdn, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -52,12 +53,11 @@ func TestLookupNameserversErr(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "invalid tld",
|
desc: "invalid tld",
|
||||||
fqdn: "_null.n0n0.",
|
fqdn: "_null.n0n0.",
|
||||||
error: "could not determine the zone",
|
error: "could not find zone",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ var findXByFqdnTestCases = []struct {
|
||||||
fqdn: "test.lego.zz.",
|
fqdn: "test.lego.zz.",
|
||||||
zone: "lego.zz.",
|
zone: "lego.zz.",
|
||||||
nameservers: []string{"8.8.8.8:53"},
|
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",
|
desc: "several non existent nameservers",
|
||||||
|
@ -123,14 +123,15 @@ var findXByFqdnTestCases = []struct {
|
||||||
fqdn: "mail.google.com.",
|
fqdn: "mail.google.com.",
|
||||||
zone: "google.com.",
|
zone: "google.com.",
|
||||||
nameservers: []string{":7053", ":8053", ":9053"},
|
nameservers: []string{":7053", ":8053", ":9053"},
|
||||||
expectedError: "could not find the start of authority for mail.google.com.: read udp",
|
// 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",
|
desc: "no nameservers",
|
||||||
fqdn: "test.ldez.com.",
|
fqdn: "test.ldez.com.",
|
||||||
zone: "ldez.com.",
|
zone: "ldez.com.",
|
||||||
nameservers: []string{},
|
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)
|
zone, err := FindZoneByFqdnCustom(test.fqdn, test.nameservers)
|
||||||
if test.expectedError != "" {
|
if test.expectedError != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), test.expectedError)
|
assert.ErrorContains(t, err, test.expectedError)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.zone, zone)
|
assert.Equal(t, test.zone, zone)
|
||||||
|
@ -159,7 +160,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) {
|
||||||
ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers)
|
ns, err := FindPrimaryNsByFqdnCustom(test.fqdn, test.nameservers)
|
||||||
if test.expectedError != "" {
|
if test.expectedError != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), test.expectedError)
|
assert.ErrorContains(t, err, test.expectedError)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.primaryNs, ns)
|
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"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -23,23 +24,52 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableCompletePropagationRequirement obsolete.
|
||||||
|
// Deprecated: use DisableAuthoritativeNssPropagationRequirement instead.
|
||||||
func DisableCompletePropagationRequirement() ChallengeOption {
|
func DisableCompletePropagationRequirement() ChallengeOption {
|
||||||
|
return DisableAuthoritativeNssPropagationRequirement()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableAuthoritativeNssPropagationRequirement() ChallengeOption {
|
||||||
return func(chlg *Challenge) error {
|
return func(chlg *Challenge) error {
|
||||||
chlg.preCheck.requireCompletePropagation = false
|
chlg.preCheck.requireAuthoritativeNssPropagation = false
|
||||||
return nil
|
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 {
|
type preCheck struct {
|
||||||
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||||
checkFunc WrapPreCheckFunc
|
checkFunc WrapPreCheckFunc
|
||||||
|
|
||||||
// require the TXT record to be propagated to all authoritative name servers
|
// 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 {
|
func newPreCheck() preCheck {
|
||||||
return 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.
|
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||||
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
|
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)
|
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.requireCompletePropagation {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Rcode == dns.RcodeSuccess {
|
if r.Rcode == dns.RcodeSuccess {
|
||||||
fqdn = updateDomainWithCName(r, fqdn)
|
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)
|
authoritativeNss, err := lookupNameservers(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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.
|
// checkNameserversPropagation queries each of the given nameservers for the expected TXT record.
|
||||||
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) {
|
||||||
for _, ns := range nameservers {
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ func TestCheckDNSPropagation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ClearFqdnCache()
|
ClearFqdnCache()
|
||||||
|
@ -37,8 +36,8 @@ func TestCheckDNSPropagation(t *testing.T) {
|
||||||
|
|
||||||
ok, err := check.checkDNSPropagation(test.fqdn, test.value)
|
ok, err := check.checkDNSPropagation(test.fqdn, test.value)
|
||||||
if test.expectError {
|
if test.expectError {
|
||||||
assert.Errorf(t, err, "PreCheckDNS must failed for %s", test.fqdn)
|
assert.Errorf(t, err, "PreCheckDNS must fail for %s", test.fqdn)
|
||||||
assert.False(t, ok, "PreCheckDNS must failed for %s", test.fqdn)
|
assert.False(t, ok, "PreCheckDNS must fail for %s", test.fqdn)
|
||||||
} else {
|
} else {
|
||||||
assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn)
|
assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn)
|
||||||
assert.True(t, ok, "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 {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ClearFqdnCache()
|
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)
|
assert.Equal(t, test.expected, ok, test.fqdn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -104,12 +102,11 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ClearFqdnCache()
|
ClearFqdnCache()
|
||||||
|
|
||||||
_, err := checkAuthoritativeNss(test.fqdn, test.value, test.ns)
|
_, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), test.error)
|
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)
|
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
|
type arbitraryMatcher string
|
||||||
|
|
||||||
func (m arbitraryMatcher) name() string {
|
func (m arbitraryMatcher) name() string {
|
||||||
|
|
|
@ -69,7 +69,6 @@ func TestParseForwardedHeader(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,6 @@ func TestProviderServer_GetAddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelSolve(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 {
|
for _, authSolver := range authSolvers {
|
||||||
authz := authSolver.authz
|
authz := authSolver.authz
|
||||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||||
|
|
|
@ -99,7 +99,6 @@ func TestProber_Solve(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/acme/api"
|
"github.com/go-acme/lego/v4/acme/api"
|
||||||
"github.com/go-acme/lego/v4/platform/tester"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -158,7 +158,8 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
jws, err := jose.ParseSigned(string(reqBody))
|
sigAlgs := []jose.SignatureAlgorithm{jose.RS256}
|
||||||
|
jws, err := jose.ParseSigned(string(reqBody), sigAlgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (s *ProviderServer) GetAddress() string {
|
||||||
return net.JoinHostPort(s.iface, s.port)
|
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.
|
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
if s.port == "" {
|
if s.port == "" {
|
||||||
|
|
|
@ -24,7 +24,7 @@ func TestChallenge(t *testing.T) {
|
||||||
_, apiURL := tester.SetupFakeAPI(t)
|
_, apiURL := tester.SetupFakeAPI(t)
|
||||||
|
|
||||||
domain := "localhost"
|
domain := "localhost"
|
||||||
port := "23457"
|
port := "24457"
|
||||||
|
|
||||||
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
|
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
|
||||||
conn, err := tls.Dial("tcp", net.JoinHostPort(domain, port), &tls.Config{
|
conn, err := tls.Dial("tcp", net.JoinHostPort(domain, port), &tls.Config{
|
||||||
|
@ -75,7 +75,7 @@ func TestChallenge(t *testing.T) {
|
||||||
solver := NewChallenge(
|
solver := NewChallenge(
|
||||||
core,
|
core,
|
||||||
mockValidate,
|
mockValidate,
|
||||||
&ProviderServer{port: "23457"},
|
&ProviderServer{port: port},
|
||||||
)
|
)
|
||||||
|
|
||||||
authz := acme.Authorization{
|
authz := acme.Authorization{
|
||||||
|
@ -126,7 +126,7 @@ func TestChallengeIPaddress(t *testing.T) {
|
||||||
_, apiURL := tester.SetupFakeAPI(t)
|
_, apiURL := tester.SetupFakeAPI(t)
|
||||||
|
|
||||||
domain := "127.0.0.1"
|
domain := "127.0.0.1"
|
||||||
port := "23457"
|
port := "24457"
|
||||||
rd, _ := dns.ReverseAddr(domain)
|
rd, _ := dns.ReverseAddr(domain)
|
||||||
|
|
||||||
mockValidate := func(_ *api.Core, _ string, chlng acme.Challenge) error {
|
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")
|
assert.Len(t, connState.PeerCertificates, 1, "Expected the challenge server to return exactly one certificate")
|
||||||
|
|
||||||
remoteCert := connState.PeerCertificates[0]
|
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.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.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")
|
assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions")
|
||||||
|
@ -176,7 +176,7 @@ func TestChallengeIPaddress(t *testing.T) {
|
||||||
solver := NewChallenge(
|
solver := NewChallenge(
|
||||||
core,
|
core,
|
||||||
mockValidate,
|
mockValidate,
|
||||||
&ProviderServer{port: "23457"},
|
&ProviderServer{port: port},
|
||||||
)
|
)
|
||||||
|
|
||||||
authz := acme.Authorization{
|
authz := acme.Authorization{
|
||||||
|
|
|
@ -71,12 +71,12 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
||||||
// TODO: move to account struct? Currently MUST pass email.
|
// TODO: move to account struct? Currently MUST pass email.
|
||||||
email := getEmail(ctx)
|
email := getEmail(ctx)
|
||||||
|
|
||||||
serverURL, err := url.Parse(ctx.String("server"))
|
serverURL, err := url.Parse(ctx.String(flgServer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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)
|
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
||||||
accountsPath := filepath.Join(rootPath, serverPath)
|
accountsPath := filepath.Join(rootPath, serverPath)
|
||||||
rootUserPath := filepath.Join(accountsPath, email)
|
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) {
|
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.
|
// couldn't load account but got a key. Try to look the account up.
|
||||||
config := lego.NewConfig(&Account{key: privateKey})
|
config := lego.NewConfig(&Account{key: privateKey})
|
||||||
config.CADirURL = ctx.String("server")
|
config.CADirURL = ctx.String(flgServer)
|
||||||
config.UserAgent = getUserAgent(ctx)
|
config.UserAgent = getUserAgent(ctx)
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
|
|
|
@ -3,10 +3,10 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -55,18 +55,28 @@ type CertificatesStorage struct {
|
||||||
pem bool
|
pem bool
|
||||||
pfx bool
|
pfx bool
|
||||||
pfxPassword string
|
pfxPassword string
|
||||||
|
pfxFormat string
|
||||||
filename string // Deprecated
|
filename string // Deprecated
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificatesStorage create a new certificates storage.
|
// NewCertificatesStorage create a new certificates storage.
|
||||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
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{
|
return &CertificatesStorage{
|
||||||
rootPath: filepath.Join(ctx.String("path"), baseCertificatesFolderName),
|
rootPath: filepath.Join(ctx.String(flgPath), baseCertificatesFolderName),
|
||||||
archivePath: filepath.Join(ctx.String("path"), baseArchivesFolderName),
|
archivePath: filepath.Join(ctx.String(flgPath), baseArchivesFolderName),
|
||||||
pem: ctx.Bool("pem"),
|
pem: ctx.Bool(flgPEM),
|
||||||
pfx: ctx.Bool("pfx"),
|
pfx: ctx.Bool(flgPFX),
|
||||||
pfxPassword: ctx.String("pfx.pass"),
|
pfxPassword: ctx.String(flgPFXPass),
|
||||||
filename: ctx.String("filename"),
|
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)
|
return fmt.Errorf("unable to load Certificate for domain %s: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerCertPemBlock, _ := pem.Decode(certRes.IssuerCertificate)
|
certChain, err := getCertificateChain(certRes)
|
||||||
if issuerCertPemBlock == nil {
|
|
||||||
return fmt.Errorf("unable to parse Issuer Certificate for domain %s", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuerCert, err := x509.ParseCertificate(issuerCertPemBlock.Bytes)
|
|
||||||
if err != nil {
|
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)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to encode PFX data for domain %s: %w", domain, err)
|
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
|
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 ;)).
|
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
||||||
func sanitizedDomain(domain string) string {
|
func sanitizedDomain(domain string) string {
|
||||||
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
|
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
|
||||||
|
|
|
@ -6,17 +6,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Before(ctx *cli.Context) error {
|
func Before(ctx *cli.Context) error {
|
||||||
if ctx.String("path") == "" {
|
if ctx.String(flgPath) == "" {
|
||||||
log.Fatal("Could not determine current working directory. Please pass --path.")
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Could not check/create path: %v", err)
|
log.Fatalf("Could not check/create path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.String("server") == "" {
|
if ctx.String(flgServer) == "" {
|
||||||
log.Fatal("Could not determine current working server. Please pass --server.")
|
log.Fatalf("Could not determine current working server. Please pass --%s.", flgServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const flgCode = "code"
|
||||||
|
|
||||||
func createDNSHelp() *cli.Command {
|
func createDNSHelp() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "dnshelp",
|
Name: "dnshelp",
|
||||||
|
@ -16,7 +18,7 @@ func createDNSHelp() *cli.Command {
|
||||||
Action: dnsHelp,
|
Action: dnsHelp,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "code",
|
Name: flgCode,
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
|
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
|
||||||
},
|
},
|
||||||
|
@ -25,7 +27,7 @@ func createDNSHelp() *cli.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsHelp(ctx *cli.Context) error {
|
func dnsHelp(ctx *cli.Context) error {
|
||||||
code := ctx.String("code")
|
code := ctx.String(flgCode)
|
||||||
if code == "" {
|
if code == "" {
|
||||||
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
|
||||||
ew := &errWriter{w: w}
|
ew := &errWriter{w: w}
|
||||||
|
|
|
@ -12,6 +12,11 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flgAccounts = "accounts"
|
||||||
|
flgNames = "names"
|
||||||
|
)
|
||||||
|
|
||||||
func createList() *cli.Command {
|
func createList() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
|
@ -19,18 +24,18 @@ func createList() *cli.Command {
|
||||||
Action: list,
|
Action: list,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "accounts",
|
Name: flgAccounts,
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "Display accounts.",
|
Usage: "Display accounts.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "names",
|
Name: flgNames,
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "Display certificate common names only.",
|
Usage: "Display certificate common names only.",
|
||||||
},
|
},
|
||||||
// fake email, needed by NewAccountsStorage
|
// fake email, needed by NewAccountsStorage
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "email",
|
Name: flgEmail,
|
||||||
Value: "unknown",
|
Value: "unknown",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
|
@ -39,7 +44,7 @@ func createList() *cli.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(ctx *cli.Context) error {
|
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 {
|
if err := listAccount(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -56,7 +61,7 @@ func listCertificates(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
names := ctx.Bool("names")
|
names := ctx.Bool(flgNames)
|
||||||
|
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
if !names {
|
if !names {
|
||||||
|
@ -70,7 +75,7 @@ func listCertificates(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range matches {
|
for _, filename := range matches {
|
||||||
if strings.HasSuffix(filename, ".issuer.crt") {
|
if strings.HasSuffix(filename, issuerExt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +89,15 @@ func listCertificates(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name, err := certcrypto.GetCertificateMainDomain(pCert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if names {
|
if names {
|
||||||
fmt.Println(pCert.Subject.CommonName)
|
fmt.Println(name)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
|
fmt.Println(" Certificate Name:", name)
|
||||||
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
|
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
|
||||||
fmt.Println(" Expiry Date:", pCert.NotAfter)
|
fmt.Println(" Expiry Date:", pCert.NotAfter)
|
||||||
fmt.Println(" Certificate Path:", filename)
|
fmt.Println(" Certificate Path:", filename)
|
||||||
|
|
193
cmd/cmd_renew.go
193
cmd/cmd_renew.go
|
@ -17,11 +17,22 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flag names.
|
||||||
|
const (
|
||||||
|
flgDays = "days"
|
||||||
|
flgARIEnable = "ari-enable"
|
||||||
|
flgARIWaitToRenewDuration = "ari-wait-to-renew-duration"
|
||||||
|
flgReuseKey = "reuse-key"
|
||||||
|
flgRenewHook = "renew-hook"
|
||||||
|
flgNoRandomSleep = "no-random-sleep"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
||||||
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
||||||
renewEnvCertPath = "LEGO_CERT_PATH"
|
renewEnvCertPath = "LEGO_CERT_PATH"
|
||||||
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
||||||
|
renewEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH"
|
||||||
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
||||||
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
||||||
)
|
)
|
||||||
|
@ -33,73 +44,68 @@ func createRenew() *cli.Command {
|
||||||
Action: renew,
|
Action: renew,
|
||||||
Before: func(ctx *cli.Context) error {
|
Before: func(ctx *cli.Context) error {
|
||||||
// we require either domains or csr, but not both
|
// we require either domains or csr, but not both
|
||||||
hasDomains := len(ctx.StringSlice("domains")) > 0
|
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
|
||||||
hasCsr := len(ctx.String("csr")) > 0
|
hasCsr := ctx.String(flgCSR) != ""
|
||||||
if hasDomains && hasCsr {
|
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 {
|
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
|
return nil
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "days",
|
Name: flgDays,
|
||||||
Value: 30,
|
Value: 30,
|
||||||
Usage: "The number of days left on a certificate to renew it.",
|
Usage: "The number of days left on a certificate to renew it.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "ari-enable",
|
Name: flgARIEnable,
|
||||||
Usage: "Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed.",
|
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{
|
&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.",
|
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "reuse-key",
|
Name: flgReuseKey,
|
||||||
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-bundle",
|
Name: flgNoBundle,
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-staple",
|
Name: flgMustStaple,
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
||||||
" Only works if the CSR is generated by lego.",
|
" Only works if the CSR is generated by lego.",
|
||||||
},
|
},
|
||||||
&cli.TimestampFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "not-before",
|
Name: flgNotBefore,
|
||||||
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||||
Layout: time.RFC3339,
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.TimestampFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "not-after",
|
Name: flgNotAfter,
|
||||||
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||||
Layout: time.RFC3339,
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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." +
|
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.",
|
" If no match, the default offered chain will be used.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "always-deactivate-authorizations",
|
Name: flgAlwaysDeactivateAuthorizations,
|
||||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "renew-hook",
|
Name: flgRenewHook,
|
||||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-random-sleep",
|
Name: flgNoRandomSleep,
|
||||||
Usage: "Do not add a random sleep before the renewal." +
|
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.",
|
" 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)
|
certsStorage := NewCertificatesStorage(ctx)
|
||||||
|
|
||||||
bundle := !ctx.Bool("no-bundle")
|
bundle := !ctx.Bool(flgNoBundle)
|
||||||
|
|
||||||
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
||||||
|
|
||||||
// CSR
|
// CSR
|
||||||
if ctx.IsSet("csr") {
|
if ctx.IsSet(flgCSR) {
|
||||||
return renewForCSR(ctx, client, certsStorage, bundle, meta)
|
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 {
|
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]
|
domain := domains[0]
|
||||||
|
|
||||||
// load the cert resource from files.
|
// load the cert resource from files.
|
||||||
// We store the certificate, private key and metadata in different 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.
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
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]
|
cert := certificates[0]
|
||||||
|
|
||||||
var ariRenewalTime *time.Time
|
var ariRenewalTime *time.Time
|
||||||
if ctx.Bool("ari-enable") {
|
if ctx.Bool(flgARIEnable) {
|
||||||
if len(certificates) < 2 {
|
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
|
||||||
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
|
|
||||||
} else {
|
|
||||||
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
|
|
||||||
}
|
|
||||||
if ariRenewalTime != nil {
|
if ariRenewalTime != nil {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
// Figure out if we need to sleep before renewing.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +174,8 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
||||||
certDomains := certcrypto.ExtractDomains(cert)
|
certDomains := certcrypto.ExtractDomains(cert)
|
||||||
|
|
||||||
var privateKey crypto.PrivateKey
|
var privateKey crypto.PrivateKey
|
||||||
if ctx.Bool("reuse-key") {
|
if ctx.Bool(flgReuseKey) {
|
||||||
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
|
keyBytes, errR := certsStorage.ReadFile(domain, keyExt)
|
||||||
if errR != nil {
|
if errR != nil {
|
||||||
log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, errR)
|
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/go-acme/lego/issues/1656
|
||||||
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L435-L440
|
// 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
|
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472
|
||||||
const jitter = 8 * time.Minute
|
const jitter = 8 * time.Minute
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
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{
|
request := certificate.ObtainRequest{
|
||||||
Domains: merge(certDomains, domains),
|
Domains: merge(certDomains, domains),
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
MustStaple: ctx.Bool("must-staple"),
|
MustStaple: ctx.Bool(flgMustStaple),
|
||||||
NotBefore: getTime(ctx, "not-before"),
|
NotBefore: getTime(ctx, flgNotBefore),
|
||||||
NotAfter: getTime(ctx, "not-after"),
|
NotAfter: getTime(ctx, flgNotAfter),
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String(flgPreferredChain),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
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)
|
certRes, err := client.Certificate.Obtain(request)
|
||||||
|
@ -214,39 +223,26 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
|
||||||
|
|
||||||
certsStorage.SaveResource(certRes)
|
certsStorage.SaveResource(certRes)
|
||||||
|
|
||||||
if ariRenewalTime != nil {
|
addPathToMetadata(meta, domain, certRes, certsStorage)
|
||||||
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
|
|
||||||
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
|
|
||||||
Cert: certificates[0],
|
|
||||||
Issuer: certificates[1],
|
|
||||||
HashName: ctx.String("ari-hash-name"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
meta[renewEnvCertDomain] = domain
|
return launchHook(ctx.String(flgRenewHook), meta)
|
||||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
|
||||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
|
||||||
meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, ".pem")
|
|
||||||
meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, ".pfx")
|
|
||||||
|
|
||||||
return launchHook(ctx.String("renew-hook"), meta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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.
|
// load the cert resource from files.
|
||||||
// We store the certificate, private key and metadata in different 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.
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
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]
|
cert := certificates[0]
|
||||||
|
|
||||||
var ariRenewalTime *time.Time
|
var ariRenewalTime *time.Time
|
||||||
if ctx.Bool("ari-enable") {
|
if ctx.Bool(flgARIEnable) {
|
||||||
if len(certificates) < 2 {
|
ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client)
|
||||||
log.Warnf("[%s] Certificate bundle does not contain issuer, cannot use the renewalInfo endpoint", domain)
|
|
||||||
} else {
|
|
||||||
ariRenewalTime = getARIRenewalTime(ctx, certificates[0], certificates[1], domain, client)
|
|
||||||
}
|
|
||||||
if ariRenewalTime != nil {
|
if ariRenewalTime != nil {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
// Figure out if we need to sleep before renewing.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +272,18 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
||||||
|
|
||||||
request := certificate.ObtainForCSRRequest{
|
request := certificate.ObtainForCSRRequest{
|
||||||
CSR: csr,
|
CSR: csr,
|
||||||
NotBefore: getTime(ctx, "not-before"),
|
NotBefore: getTime(ctx, flgNotBefore),
|
||||||
NotAfter: getTime(ctx, "not-after"),
|
NotAfter: getTime(ctx, flgNotAfter),
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String(flgPreferredChain),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
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)
|
certRes, err := client.Certificate.ObtainForCSR(request)
|
||||||
|
@ -294,23 +293,9 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
|
||||||
|
|
||||||
certsStorage.SaveResource(certRes)
|
certsStorage.SaveResource(certRes)
|
||||||
|
|
||||||
if ariRenewalTime != nil {
|
addPathToMetadata(meta, domain, certRes, certsStorage)
|
||||||
// Post to the renewalInfo endpoint to indicate that we have renewed and replaced the certificate.
|
|
||||||
err := client.Certificate.UpdateRenewalInfo(certificate.RenewalInfoRequest{
|
|
||||||
Cert: certificates[0],
|
|
||||||
Issuer: certificates[1],
|
|
||||||
HashName: ctx.String("ari-hash-name"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("[%s] Failed to update renewal info: %v", domain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
meta[renewEnvCertDomain] = domain
|
return launchHook(ctx.String(flgRenewHook), meta)
|
||||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
|
||||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
|
||||||
|
|
||||||
return launchHook(ctx.String("renew-hook"), meta)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
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.
|
// 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 {
|
if cert.IsCA {
|
||||||
log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain)
|
log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{
|
renewalInfo, err := client.Certificate.GetRenewalInfo(certificate.RenewalInfoRequest{Cert: cert})
|
||||||
Cert: cert,
|
|
||||||
Issuer: issuer,
|
|
||||||
HashName: ctx.String("ari-hash-name"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNoARI) {
|
if errors.Is(err, api.ErrNoARI) {
|
||||||
// The server does not advertise a renewal info endpoint.
|
// 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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
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 {
|
if renewalTime == nil {
|
||||||
log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain)
|
log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain)
|
||||||
return nil
|
return nil
|
||||||
|
@ -366,6 +347,24 @@ func getARIRenewalTime(ctx *cli.Context, cert, issuer *x509.Certificate, domain
|
||||||
return renewalTime
|
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 {
|
func merge(prevDomains, nextDomains []string) []string {
|
||||||
for _, next := range nextDomains {
|
for _, next := range nextDomains {
|
||||||
var found bool
|
var found bool
|
||||||
|
|
|
@ -48,7 +48,6 @@ func Test_merge(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -108,7 +107,6 @@ func Test_needRenewal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
actual := needRenewal(test.x509Cert, "foo.com", test.days)
|
actual := needRenewal(test.x509Cert, "foo.com", test.days)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,12 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flag names.
|
||||||
|
const (
|
||||||
|
flgKeep = "keep"
|
||||||
|
flgReason = "reason"
|
||||||
|
)
|
||||||
|
|
||||||
func createRevoke() *cli.Command {
|
func createRevoke() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "revoke",
|
Name: "revoke",
|
||||||
|
@ -13,12 +19,12 @@ func createRevoke() *cli.Command {
|
||||||
Action: revoke,
|
Action: revoke,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "keep",
|
Name: flgKeep,
|
||||||
Aliases: []string{"k"},
|
Aliases: []string{"k"},
|
||||||
Usage: "Keep the certificates after the revocation instead of archiving them.",
|
Usage: "Keep the certificates after the revocation instead of archiving them.",
|
||||||
},
|
},
|
||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
Name: "reason",
|
Name: flgReason,
|
||||||
Usage: "Identifies the reason for the certificate revocation." +
|
Usage: "Identifies the reason for the certificate revocation." +
|
||||||
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
|
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
|
||||||
" Valid values are:" +
|
" Valid values are:" +
|
||||||
|
@ -41,15 +47,15 @@ func revoke(ctx *cli.Context) error {
|
||||||
certsStorage := NewCertificatesStorage(ctx)
|
certsStorage := NewCertificatesStorage(ctx)
|
||||||
certsStorage.CreateRootFolder()
|
certsStorage.CreateRootFolder()
|
||||||
|
|
||||||
for _, domain := range ctx.StringSlice("domains") {
|
for _, domain := range ctx.StringSlice(flgDomains) {
|
||||||
log.Printf("Trying to revoke certificate for domain %s", domain)
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
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)
|
err = client.Certificate.RevokeWithReason(certBytes, &reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,7 +64,7 @@ func revoke(ctx *cli.Context) error {
|
||||||
|
|
||||||
log.Println("Certificate was revoked.")
|
log.Println("Certificate was revoked.")
|
||||||
|
|
||||||
if ctx.Bool("keep") {
|
if ctx.Bool(flgKeep) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,25 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"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 {
|
func createRun() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "Register an account, then create and install a certificate",
|
Usage: "Register an account, then create and install a certificate",
|
||||||
Before: func(ctx *cli.Context) error {
|
Before: func(ctx *cli.Context) error {
|
||||||
// we require either domains or csr, but not both
|
// we require either domains or csr, but not both
|
||||||
hasDomains := len(ctx.StringSlice("domains")) > 0
|
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
|
||||||
hasCsr := len(ctx.String("csr")) > 0
|
hasCsr := ctx.String(flgCSR) != ""
|
||||||
if hasDomains && hasCsr {
|
if hasDomains && hasCsr {
|
||||||
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
|
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
|
||||||
}
|
}
|
||||||
|
@ -33,35 +44,35 @@ func createRun() *cli.Command {
|
||||||
Action: run,
|
Action: run,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "no-bundle",
|
Name: flgNoBundle,
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "must-staple",
|
Name: flgMustStaple,
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
|
||||||
" Only works if the CSR is generated by lego.",
|
" Only works if the CSR is generated by lego.",
|
||||||
},
|
},
|
||||||
&cli.TimestampFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "not-before",
|
Name: flgNotBefore,
|
||||||
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
|
||||||
Layout: time.RFC3339,
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.TimestampFlag{
|
&cli.TimestampFlag{
|
||||||
Name: "not-after",
|
Name: flgNotAfter,
|
||||||
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
|
||||||
Layout: time.RFC3339,
|
Layout: time.RFC3339,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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." +
|
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.",
|
" If no match, the default offered chain will be used.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "always-deactivate-authorizations",
|
Name: flgAlwaysDeactivateAuthorizations,
|
||||||
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "run-hook",
|
Name: flgRunHook,
|
||||||
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
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{
|
meta := map[string]string{
|
||||||
renewEnvAccountEmail: account.Email,
|
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 {
|
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
|
||||||
// Check for a global accept override
|
// Check for a global accept override
|
||||||
if ctx.Bool("accept-tos") {
|
if ctx.Bool(flgAcceptTOS) {
|
||||||
return true
|
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.")
|
log.Fatal("You did not accept the TOS. Unable to proceed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Bool("eab") {
|
if ctx.Bool(flgEAB) {
|
||||||
kid := ctx.String("kid")
|
kid := ctx.String(flgKID)
|
||||||
hmacEncoded := ctx.String("hmac")
|
hmacEncoded := ctx.String(flgHMAC)
|
||||||
|
|
||||||
if kid == "" || hmacEncoded == "" {
|
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{
|
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) {
|
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 {
|
if len(domains) > 0 {
|
||||||
// obtain a certificate, generating a new private key
|
// obtain a certificate, generating a new private key
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
MustStaple: ctx.Bool("must-staple"),
|
MustStaple: ctx.Bool(flgMustStaple),
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String(flgPreferredChain),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
|
||||||
}
|
}
|
||||||
|
|
||||||
notBefore := ctx.Timestamp("not-before")
|
notBefore := ctx.Timestamp(flgNotBefore)
|
||||||
if notBefore != nil {
|
if notBefore != nil {
|
||||||
request.NotBefore = *notBefore
|
request.NotBefore = *notBefore
|
||||||
}
|
}
|
||||||
|
|
||||||
notAfter := ctx.Timestamp("not-after")
|
notAfter := ctx.Timestamp(flgNotAfter)
|
||||||
if notAfter != nil {
|
if notAfter != nil {
|
||||||
request.NotAfter = *notAfter
|
request.NotAfter = *notAfter
|
||||||
}
|
}
|
||||||
|
@ -203,7 +211,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
|
||||||
}
|
}
|
||||||
|
|
||||||
// read the CSR
|
// read the CSR
|
||||||
csr, err := readCSRFile(ctx.String("csr"))
|
csr, err := readCSRFile(ctx.String(flgCSR))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -211,11 +219,11 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso
|
||||||
// obtain a certificate for this CSR
|
// obtain a certificate for this CSR
|
||||||
request := certificate.ObtainForCSRRequest{
|
request := certificate.ObtainForCSRRequest{
|
||||||
CSR: csr,
|
CSR: csr,
|
||||||
NotBefore: getTime(ctx, "not-before"),
|
NotBefore: getTime(ctx, flgNotBefore),
|
||||||
NotAfter: getTime(ctx, "not-after"),
|
NotAfter: getTime(ctx, flgNotAfter),
|
||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
PreferredChain: ctx.String(flgPreferredChain),
|
||||||
AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"),
|
AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations),
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.Certificate.ObtainForCSR(request)
|
return client.Certificate.ObtainForCSR(request)
|
||||||
|
|
159
cmd/flags.go
159
cmd/flags.go
|
@ -1,145 +1,248 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"software.sslmate.com/src/go-pkcs12"
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flag names.
|
||||||
|
const (
|
||||||
|
flgDomains = "domains"
|
||||||
|
flgServer = "server"
|
||||||
|
flgAcceptTOS = "accept-tos"
|
||||||
|
flgEmail = "email"
|
||||||
|
flgCSR = "csr"
|
||||||
|
flgEAB = "eab"
|
||||||
|
flgKID = "kid"
|
||||||
|
flgHMAC = "hmac"
|
||||||
|
flgKeyType = "key-type"
|
||||||
|
flgFilename = "filename"
|
||||||
|
flgPath = "path"
|
||||||
|
flgHTTP = "http"
|
||||||
|
flgHTTPPort = "http.port"
|
||||||
|
flgHTTPProxyHeader = "http.proxy-header"
|
||||||
|
flgHTTPWebroot = "http.webroot"
|
||||||
|
flgHTTPMemcachedHost = "http.memcached-host"
|
||||||
|
flgHTTPS3Bucket = "http.s3-bucket"
|
||||||
|
flgHTTPFrostFSEndpoint = "http.frostfs-endpoint"
|
||||||
|
flgHTTPFrostFSContainer = "http.frostfs-container"
|
||||||
|
flgHTTPFrostFSWallet = "http.frostfs-wallet"
|
||||||
|
flgHTTPFrostFSWalletAccount = "http.frostfs-wallet-account"
|
||||||
|
flgHTTPFrostFSWalletPass = "http.frostfs-wallet-password"
|
||||||
|
flgTLS = "tls"
|
||||||
|
flgTLSPort = "tls.port"
|
||||||
|
flgDNS = "dns"
|
||||||
|
flgDNSDisableCP = "dns.disable-cp"
|
||||||
|
flgDNSPropagationWait = "dns.propagation-wait"
|
||||||
|
flgDNSPropagationDisableANS = "dns.propagation-disable-ans"
|
||||||
|
flgDNSPropagationRNS = "dns.propagation-rns"
|
||||||
|
flgDNSResolvers = "dns.resolvers"
|
||||||
|
flgHTTPTimeout = "http-timeout"
|
||||||
|
flgDNSTimeout = "dns-timeout"
|
||||||
|
flgPEM = "pem"
|
||||||
|
flgPFX = "pfx"
|
||||||
|
flgPFXPass = "pfx.pass"
|
||||||
|
flgPFXFormat = "pfx.format"
|
||||||
|
flgCertTimeout = "cert.timeout"
|
||||||
|
flgOverallRequestLimit = "overall-request-limit"
|
||||||
|
flgUserAgent = "user-agent"
|
||||||
|
)
|
||||||
|
|
||||||
func CreateFlags(defaultPath string) []cli.Flag {
|
func CreateFlags(defaultPath string) []cli.Flag {
|
||||||
return []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "domains",
|
Name: flgDomains,
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "Add a domain to the process. Can be specified multiple times.",
|
Usage: "Add a domain to the process. Can be specified multiple times.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "server",
|
Name: flgServer,
|
||||||
Aliases: []string{"s"},
|
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.",
|
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
|
||||||
Value: lego.LEDirectoryProduction,
|
Value: lego.LEDirectoryProduction,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "accept-tos",
|
Name: flgAcceptTOS,
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "email",
|
Name: flgEmail,
|
||||||
Aliases: []string{"m"},
|
Aliases: []string{"m"},
|
||||||
Usage: "Email used for registration and recovery contact.",
|
Usage: "Email used for registration and recovery contact.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "csr",
|
Name: flgCSR,
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Usage: "Certificate signing request filename, if an external CSR is to be used.",
|
Usage: "Certificate signing request filename, if an external CSR is to be used.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "eab",
|
Name: flgEAB,
|
||||||
|
EnvVars: []string{"LEGO_EAB"},
|
||||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "kid",
|
Name: flgKID,
|
||||||
|
EnvVars: []string{"LEGO_EAB_KID"},
|
||||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "hmac",
|
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.",
|
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "key-type",
|
Name: flgKeyType,
|
||||||
Aliases: []string{"k"},
|
Aliases: []string{"k"},
|
||||||
Value: "ec256",
|
Value: "ec256",
|
||||||
Usage: "Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384.",
|
Usage: "Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "filename",
|
Name: flgFilename,
|
||||||
Usage: "(deprecated) Filename of the generated certificate.",
|
Usage: "(deprecated) Filename of the generated certificate.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "path",
|
Name: flgPath,
|
||||||
EnvVars: []string{"LEGO_PATH"},
|
EnvVars: []string{"LEGO_PATH"},
|
||||||
Usage: "Directory to use for storing the data.",
|
Usage: "Directory to use for storing the data.",
|
||||||
Value: defaultPath,
|
Value: defaultPath,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "http",
|
Name: flgHTTP,
|
||||||
Usage: "Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
Usage: "Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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.",
|
Usage: "Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port.",
|
||||||
Value: ":80",
|
Value: ":80",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "http.proxy-header",
|
Name: flgHTTPProxyHeader,
|
||||||
Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.",
|
Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.",
|
||||||
Value: "Host",
|
Value: "Host",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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." +
|
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",
|
" This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&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.",
|
Usage: "Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPS3Bucket,
|
||||||
|
Usage: "Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPFrostFSEndpoint,
|
||||||
|
Usage: "Set FrostFS endpoint to use for HTTP-01 based challenges. Challenges will be written to FrostFS container",
|
||||||
|
EnvVars: []string{"FROSTFS_ENDPOINT"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPFrostFSContainer,
|
||||||
|
Usage: "Set FrostFS container ID to use for HTTP-01 based challenges. Challenges will be written to FrostFS container",
|
||||||
|
EnvVars: []string{"FROSTFS_CONTAINER"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPFrostFSWallet,
|
||||||
|
Usage: "Path to NEO wallet to use for interaction with FrostFS. If no wallet is provided an ephemeral one will be generated. Such key will only work for publicly writable containers and will significantly reduce security: any attacker with knowledge of FrostFS endpoint and CID will be able to obtain certificates for your domain",
|
||||||
|
EnvVars: []string{"FROSTFS_WALLET"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPFrostFSWalletAccount,
|
||||||
|
Usage: "Wallet account to use for interaction with FrostFS. If not set, the first account from wallet will be used",
|
||||||
|
EnvVars: []string{"FROSTFS_WALLET_ACCOUNT"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flgHTTPFrostFSWalletPass,
|
||||||
|
Usage: "Account password to decrypt the wallet. If not set, an empty password is assumed",
|
||||||
|
EnvVars: []string{"FROSTFS_WALLET_PASSWORD"},
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "tls",
|
Name: flgTLS,
|
||||||
Usage: "Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
Usage: "Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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.",
|
Usage: "Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port.",
|
||||||
Value: ":443",
|
Value: ":443",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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.",
|
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{
|
&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.",
|
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{
|
&cli.StringSliceFlag{
|
||||||
Name: "dns.resolvers",
|
Name: flgDNSResolvers,
|
||||||
Usage: "Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination." +
|
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." +
|
" For DNS-01 challenge verification, the authoritative DNS server is queried directly." +
|
||||||
" Supported: host:port." +
|
" Supported: host:port." +
|
||||||
" The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
|
" The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "http-timeout",
|
Name: flgHTTPTimeout,
|
||||||
Usage: "Set the HTTP timeout value to a specific value in seconds.",
|
Usage: "Set the HTTP timeout value to a specific value in seconds.",
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&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.",
|
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries.",
|
||||||
Value: 10,
|
Value: 10,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "pem",
|
Name: flgPEM,
|
||||||
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
|
Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "pfx",
|
Name: flgPFX,
|
||||||
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
|
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{
|
&cli.StringFlag{
|
||||||
Name: "pfx.pass",
|
Name: flgPFXPass,
|
||||||
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
|
||||||
Value: pkcs12.DefaultPassword,
|
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{
|
&cli.IntFlag{
|
||||||
Name: "cert.timeout",
|
Name: flgCertTimeout,
|
||||||
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
||||||
Value: 30,
|
Value: 30,
|
||||||
},
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: flgOverallRequestLimit,
|
||||||
|
Usage: "ACME overall requests limit.",
|
||||||
|
Value: certificate.DefaultOverallRequestLimit,
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "user-agent",
|
Name: flgUserAgent,
|
||||||
Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli",
|
Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
21
cmd/setup.go
21
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 {
|
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
||||||
config := lego.NewConfig(acc)
|
config := lego.NewConfig(acc)
|
||||||
config.CADirURL = ctx.String("server")
|
config.CADirURL = ctx.String(flgServer)
|
||||||
|
|
||||||
config.Certificate = lego.CertificateConfig{
|
config.Certificate = lego.CertificateConfig{
|
||||||
KeyType: keyType,
|
KeyType: keyType,
|
||||||
Timeout: time.Duration(ctx.Int("cert.timeout")) * time.Second,
|
Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second,
|
||||||
|
OverallRequestLimit: ctx.Int(flgOverallRequestLimit),
|
||||||
}
|
}
|
||||||
config.UserAgent = getUserAgent(ctx)
|
config.UserAgent = getUserAgent(ctx)
|
||||||
|
|
||||||
if ctx.IsSet("http-timeout") {
|
if ctx.IsSet(flgHTTPTimeout) {
|
||||||
config.HTTPClient.Timeout = time.Duration(ctx.Int("http-timeout")) * time.Second
|
config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
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)
|
log.Fatalf("Could not create client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.GetExternalAccountRequired() && !ctx.IsSet("eab") {
|
if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) {
|
||||||
log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
|
log.Fatalf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC)
|
||||||
}
|
}
|
||||||
|
|
||||||
return client
|
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.
|
// getKeyType the type from which private keys should be generated.
|
||||||
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
||||||
keyType := ctx.String("key-type")
|
keyType := ctx.String(flgKeyType)
|
||||||
switch strings.ToUpper(keyType) {
|
switch strings.ToUpper(keyType) {
|
||||||
case "RSA2048":
|
case "RSA2048":
|
||||||
return certcrypto.RSA2048
|
return certcrypto.RSA2048
|
||||||
|
@ -82,15 +83,15 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEmail(ctx *cli.Context) string {
|
func getEmail(ctx *cli.Context) string {
|
||||||
email := ctx.String("email")
|
email := ctx.String(flgEmail)
|
||||||
if email == "" {
|
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
|
return email
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserAgent(ctx *cli.Context) string {
|
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 {
|
func createNonExistingFolder(path string) error {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -12,53 +13,77 @@ import (
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
"github.com/go-acme/lego/v4/providers/dns"
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"github.com/go-acme/lego/v4/providers/http/frostfs"
|
||||||
"github.com/go-acme/lego/v4/providers/http/memcached"
|
"github.com/go-acme/lego/v4/providers/http/memcached"
|
||||||
|
"github.com/go-acme/lego/v4/providers/http/s3"
|
||||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupChallenges(ctx *cli.Context, client *lego.Client) {
|
func setupChallenges(ctx *cli.Context, client *lego.Client) {
|
||||||
if !ctx.Bool("http") && !ctx.Bool("tls") && !ctx.IsSet("dns") {
|
if !ctx.Bool(flgHTTP) && !ctx.Bool(flgTLS) && !ctx.IsSet(flgDNS) {
|
||||||
log.Fatal("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
|
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))
|
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Bool("tls") {
|
if ctx.Bool(flgTLS) {
|
||||||
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
|
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("dns") {
|
if ctx.IsSet(flgDNS) {
|
||||||
setupDNS(ctx, client)
|
err := setupDNS(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo // the complexity is expected.
|
||||||
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
||||||
switch {
|
switch {
|
||||||
case ctx.IsSet("http.webroot"):
|
case ctx.IsSet(flgHTTPWebroot):
|
||||||
ps, err := webroot.NewHTTPProvider(ctx.String("http.webroot"))
|
ps, err := webroot.NewHTTPProvider(ctx.String(flgHTTPWebroot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return ps
|
return ps
|
||||||
case ctx.IsSet("http.memcached-host"):
|
case ctx.IsSet(flgHTTPMemcachedHost):
|
||||||
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice("http.memcached-host"))
|
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return ps
|
return ps
|
||||||
case ctx.IsSet("http.port"):
|
case ctx.IsSet(flgHTTPS3Bucket):
|
||||||
iface := ctx.String("http.port")
|
ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
case ctx.IsSet(flgHTTPFrostFSEndpoint):
|
||||||
|
ps, err := frostfs.NewHTTPProvider(
|
||||||
|
ctx.String(flgHTTPFrostFSEndpoint),
|
||||||
|
ctx.String(flgHTTPFrostFSContainer),
|
||||||
|
ctx.String(flgHTTPFrostFSWallet),
|
||||||
|
ctx.String(flgHTTPFrostFSWalletAccount),
|
||||||
|
ctx.String(flgHTTPFrostFSWalletPass),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
case ctx.IsSet(flgHTTPPort):
|
||||||
|
iface := ctx.String(flgHTTPPort)
|
||||||
if !strings.Contains(iface, ":") {
|
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)
|
host, port, err := net.SplitHostPort(iface)
|
||||||
|
@ -67,13 +92,13 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := http01.NewProviderServer(host, port)
|
srv := http01.NewProviderServer(host, port)
|
||||||
if header := ctx.String("http.proxy-header"); header != "" {
|
if header := ctx.String(flgHTTPProxyHeader); header != "" {
|
||||||
srv.SetProxyHeader(header)
|
srv.SetProxyHeader(header)
|
||||||
}
|
}
|
||||||
return srv
|
return srv
|
||||||
case ctx.Bool("http"):
|
case ctx.Bool(flgHTTP):
|
||||||
srv := http01.NewProviderServer("", "")
|
srv := http01.NewProviderServer("", "")
|
||||||
if header := ctx.String("http.proxy-header"); header != "" {
|
if header := ctx.String(flgHTTPProxyHeader); header != "" {
|
||||||
srv.SetProxyHeader(header)
|
srv.SetProxyHeader(header)
|
||||||
}
|
}
|
||||||
return srv
|
return srv
|
||||||
|
@ -85,10 +110,10 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
||||||
|
|
||||||
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
||||||
switch {
|
switch {
|
||||||
case ctx.IsSet("tls.port"):
|
case ctx.IsSet(flgTLSPort):
|
||||||
iface := ctx.String("tls.port")
|
iface := ctx.String(flgTLSPort)
|
||||||
if !strings.Contains(iface, ":") {
|
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)
|
host, port, err := net.SplitHostPort(iface)
|
||||||
|
@ -97,7 +122,7 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
return tlsalpn01.NewProviderServer(host, port)
|
return tlsalpn01.NewProviderServer(host, port)
|
||||||
case ctx.Bool("tls"):
|
case ctx.Bool(flgTLS):
|
||||||
return tlsalpn01.NewProviderServer("", "")
|
return tlsalpn01.NewProviderServer("", "")
|
||||||
default:
|
default:
|
||||||
log.Fatal("Invalid HTTP challenge options.")
|
log.Fatal("Invalid HTTP challenge options.")
|
||||||
|
@ -105,22 +130,62 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDNS(ctx *cli.Context, client *lego.Client) {
|
func setupDNS(ctx *cli.Context, client *lego.Client) error {
|
||||||
provider, err := dns.NewDNSChallengeProviderByName(ctx.String("dns"))
|
err := checkPropagationExclusiveOptions(ctx)
|
||||||
if err != nil {
|
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,
|
err = client.Challenge.SetDNS01Provider(provider,
|
||||||
dns01.CondOption(len(servers) > 0,
|
dns01.CondOption(len(servers) > 0,
|
||||||
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice("dns.resolvers")))),
|
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice(flgDNSResolvers)))),
|
||||||
dns01.CondOption(ctx.Bool("dns.disable-cp"),
|
|
||||||
dns01.DisableCompletePropagationRequirement()),
|
dns01.CondOption(ctx.Bool(flgDNSDisableCP) || ctx.Bool(flgDNSPropagationDisableANS),
|
||||||
dns01.CondOption(ctx.IsSet("dns-timeout"),
|
dns01.DisableAuthoritativeNssPropagationRequirement()),
|
||||||
dns01.AddDNSTimeout(time.Duration(ctx.Int("dns-timeout"))*time.Second)),
|
|
||||||
|
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
|
package cmd
|
||||||
// THIS FILE MUST NOT BE EDITED BY HAND
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -31,13 +30,16 @@ func allDNSCodes() string {
|
||||||
"clouddns",
|
"clouddns",
|
||||||
"cloudflare",
|
"cloudflare",
|
||||||
"cloudns",
|
"cloudns",
|
||||||
|
"cloudru",
|
||||||
"cloudxns",
|
"cloudxns",
|
||||||
"conoha",
|
"conoha",
|
||||||
"constellix",
|
"constellix",
|
||||||
|
"cpanel",
|
||||||
"derak",
|
"derak",
|
||||||
"desec",
|
"desec",
|
||||||
"designate",
|
"designate",
|
||||||
"digitalocean",
|
"digitalocean",
|
||||||
|
"directadmin",
|
||||||
"dnshomede",
|
"dnshomede",
|
||||||
"dnsimple",
|
"dnsimple",
|
||||||
"dnsmadeeasy",
|
"dnsmadeeasy",
|
||||||
|
@ -65,7 +67,9 @@ func allDNSCodes() string {
|
||||||
"hetzner",
|
"hetzner",
|
||||||
"hostingde",
|
"hostingde",
|
||||||
"hosttech",
|
"hosttech",
|
||||||
|
"httpnet",
|
||||||
"httpreq",
|
"httpreq",
|
||||||
|
"huaweicloud",
|
||||||
"hurricane",
|
"hurricane",
|
||||||
"hyperone",
|
"hyperone",
|
||||||
"ibmcloud",
|
"ibmcloud",
|
||||||
|
@ -81,11 +85,15 @@ func allDNSCodes() string {
|
||||||
"joker",
|
"joker",
|
||||||
"liara",
|
"liara",
|
||||||
"lightsail",
|
"lightsail",
|
||||||
|
"limacity",
|
||||||
"linode",
|
"linode",
|
||||||
"liquidweb",
|
"liquidweb",
|
||||||
"loopia",
|
"loopia",
|
||||||
"luadns",
|
"luadns",
|
||||||
|
"mailinabox",
|
||||||
"metaname",
|
"metaname",
|
||||||
|
"mijnhost",
|
||||||
|
"mittwald",
|
||||||
"mydnsjp",
|
"mydnsjp",
|
||||||
"mythicbeasts",
|
"mythicbeasts",
|
||||||
"namecheap",
|
"namecheap",
|
||||||
|
@ -115,7 +123,10 @@ func allDNSCodes() string {
|
||||||
"sakuracloud",
|
"sakuracloud",
|
||||||
"scaleway",
|
"scaleway",
|
||||||
"selectel",
|
"selectel",
|
||||||
|
"selectelv2",
|
||||||
|
"selfhostde",
|
||||||
"servercow",
|
"servercow",
|
||||||
|
"shellrent",
|
||||||
"simply",
|
"simply",
|
||||||
"sonic",
|
"sonic",
|
||||||
"stackpath",
|
"stackpath",
|
||||||
|
@ -130,9 +141,11 @@ func allDNSCodes() string {
|
||||||
"vkcloud",
|
"vkcloud",
|
||||||
"vscale",
|
"vscale",
|
||||||
"vultr",
|
"vultr",
|
||||||
|
"webnames",
|
||||||
"websupport",
|
"websupport",
|
||||||
"wedos",
|
"wedos",
|
||||||
"yandex",
|
"yandex",
|
||||||
|
"yandex360",
|
||||||
"yandexcloud",
|
"yandexcloud",
|
||||||
"zoneee",
|
"zoneee",
|
||||||
"zonomi",
|
"zonomi",
|
||||||
|
@ -298,24 +311,28 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
|
|
||||||
case "azuredns":
|
case "azuredns":
|
||||||
// generated from: providers/dns/azuredns/azuredns.toml
|
// generated from: providers/dns/azuredns/azuredns.toml
|
||||||
ew.writeln(`Configuration for AzureDNS.`)
|
ew.writeln(`Configuration for Azure DNS.`)
|
||||||
ew.writeln(`Code: 'azuredns'`)
|
ew.writeln(`Code: 'azuredns'`)
|
||||||
ew.writeln(`Since: 'v0.1.0'`)
|
ew.writeln(`Since: 'v4.13.0'`)
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
ew.writeln(`Credentials:`)
|
||||||
|
ew.writeln(` - "AZURE_CLIENT_CERTIFICATE_PATH": Client certificate path`)
|
||||||
ew.writeln(` - "AZURE_CLIENT_ID": Client ID`)
|
ew.writeln(` - "AZURE_CLIENT_ID": Client ID`)
|
||||||
ew.writeln(` - "AZURE_CLIENT_SECRET": Client secret`)
|
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(` - "AZURE_TENANT_ID": Tenant ID`)
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
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_ENVIRONMENT": Azure environment, one of: public, usgovernment, and china`)
|
||||||
ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`)
|
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_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_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_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`)
|
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "BLUECAT_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "BLUECAT_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "BLUECAT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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(` - "BLUECAT_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
@ -486,10 +504,10 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout`)
|
ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout (in seconds)`)
|
||||||
ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check (in seconds)`)
|
||||||
ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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`)
|
ew.writeln(` - "CLOUDFLARE_TTL": The TTL of the TXT record used for the DNS challenge (in seconds)`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudflare`)
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudns`)
|
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":
|
case "cloudxns":
|
||||||
// generated from: providers/dns/cloudxns/cloudxns.toml
|
// generated from: providers/dns/cloudxns/cloudxns.toml
|
||||||
ew.writeln(`Configuration for CloudXNS.`)
|
ew.writeln(`Configuration for CloudXNS.`)
|
||||||
|
@ -581,6 +622,30 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/constellix`)
|
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":
|
case "derak":
|
||||||
// generated from: providers/dns/derak/derak.toml
|
// generated from: providers/dns/derak/derak.toml
|
||||||
ew.writeln(`Configuration for Derak Cloud.`)
|
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_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "DESIGNATE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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_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_PROJECT_ID": Project ID`)
|
||||||
ew.writeln(` - "OS_TENANT_NAME": Tenant name (deprecated see OS_PROJECT_NAME and OS_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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/digitalocean`)
|
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":
|
case "dnshomede":
|
||||||
// generated from: providers/dns/dnshomede/dnshomede.toml
|
// generated from: providers/dns/dnshomede/dnshomede.toml
|
||||||
ew.writeln(`Configuration for dnsHome.de.`)
|
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(` - "DNSHOMEDE_CREDENTIALS": Comma-separated list of domain:password credential pairs`)
|
||||||
ew.writeln()
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnshomede`)
|
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(`Additional Configuration:`)
|
||||||
ew.writeln(` - "EFFICIENTIP_HTTP_TIMEOUT": API request timeout`)
|
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_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "EFFICIENTIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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`)
|
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()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
ew.writeln(` - "EXOSCALE_API_ZONE": API zone`)
|
|
||||||
ew.writeln(` - "EXOSCALE_ENDPOINT": API endpoint URL`)
|
ew.writeln(` - "EXOSCALE_ENDPOINT": API endpoint URL`)
|
||||||
ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout`)
|
ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "EXOSCALE_POLLING_INTERVAL": Time between DNS propagation check`)
|
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()
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
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()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
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_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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_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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcloud`)
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hosttech`)
|
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":
|
case "httpreq":
|
||||||
// generated from: providers/dns/httpreq/httpreq.toml
|
// generated from: providers/dns/httpreq/httpreq.toml
|
||||||
ew.writeln(`Configuration for HTTP request.`)
|
ew.writeln(`Configuration for HTTP request.`)
|
||||||
|
@ -1245,6 +1363,28 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/httpreq`)
|
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":
|
case "hurricane":
|
||||||
// generated from: providers/dns/hurricane/hurricane.toml
|
// generated from: providers/dns/hurricane/hurricane.toml
|
||||||
ew.writeln(`Configuration for Hurricane Electric DNS.`)
|
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(` - "HURRICANE_TOKENS": TXT record names and tokens`)
|
||||||
ew.writeln()
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/hurricane`)
|
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(`Credentials:`)
|
||||||
ew.writeln(` - "SOFTLAYER_API_KEY": Classic Infrastructure API key`)
|
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()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "IPV64_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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(` - "IPV64_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
@ -1557,6 +1702,27 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/lightsail`)
|
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":
|
case "linode":
|
||||||
// generated from: providers/dns/linode/linode.toml
|
// generated from: providers/dns/linode/linode.toml
|
||||||
ew.writeln(`Configuration for Linode (v4).`)
|
ew.writeln(`Configuration for Linode (v4).`)
|
||||||
|
@ -1585,17 +1751,17 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
ew.writeln(`Credentials:`)
|
||||||
ew.writeln(` - "LIQUID_WEB_PASSWORD": Storm API Password`)
|
ew.writeln(` - "LWAPI_PASSWORD": Liquid Web API Password`)
|
||||||
ew.writeln(` - "LIQUID_WEB_USERNAME": Storm API Username`)
|
ew.writeln(` - "LWAPI_USERNAME": Liquid Web API Username`)
|
||||||
ew.writeln(` - "LIQUID_WEB_ZONE": DNS Zone`)
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
ew.writeln(` - "LIQUID_WEB_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`)
|
ew.writeln(` - "LWAPI_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(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "LIQUID_WEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
ew.writeln(` - "LWAPI_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(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
ew.writeln(` - "LIQUID_WEB_URL": Storm API endpoint`)
|
ew.writeln(` - "LWAPI_URL": Liquid Web API endpoint`)
|
||||||
|
ew.writeln(` - "LWAPI_ZONE": DNS Zone`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`)
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/luadns`)
|
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":
|
case "metaname":
|
||||||
// generated from: providers/dns/metaname/metaname.toml
|
// generated from: providers/dns/metaname/metaname.toml
|
||||||
ew.writeln(`Configuration for Metaname.`)
|
ew.writeln(`Configuration for Metaname.`)
|
||||||
|
@ -1663,6 +1849,48 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaname`)
|
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":
|
case "mydnsjp":
|
||||||
// generated from: providers/dns/mydnsjp/mydnsjp.toml
|
// generated from: providers/dns/mydnsjp/mydnsjp.toml
|
||||||
ew.writeln(`Configuration for MyDNS.jp.`)
|
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
@ -1996,9 +2225,12 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
ew.writeln(`Credentials:`)
|
||||||
ew.writeln(` - "OVH_APPLICATION_KEY": Application key`)
|
ew.writeln(` - "OVH_ACCESS_TOKEN": Access token`)
|
||||||
ew.writeln(` - "OVH_APPLICATION_SECRET": Application secret`)
|
ew.writeln(` - "OVH_APPLICATION_KEY": Application key (Application Key authentication)`)
|
||||||
ew.writeln(` - "OVH_CONSUMER_KEY": Consumer key`)
|
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(` - "OVH_ENDPOINT": Endpoint URL (ovh-eu or ovh-ca)`)
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
|
@ -2024,6 +2256,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "PDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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_HTTP_TIMEOUT": API request timeout`)
|
||||||
ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "REGRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
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(` - "REGRU_TTL": The TTL of the TXT record used for the DNS challenge`)
|
||||||
|
|
||||||
ew.writeln()
|
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_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_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_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()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
|
@ -2259,14 +2495,15 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
ew.writeln(`Credentials:`)
|
||||||
ew.writeln(` - "SCALEWAY_API_TOKEN": API token`)
|
ew.writeln(` - "SCW_PROJECT_ID": Project to use (optional)`)
|
||||||
ew.writeln(` - "SCALEWAY_PROJECT_ID": Project to use (optional)`)
|
ew.writeln(` - "SCW_SECRET_KEY": Secret key`)
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
ew.writeln(` - "SCALEWAY_POLLING_INTERVAL": Time between DNS propagation check`)
|
ew.writeln(` - "SCW_ACCESS_KEY": Access key`)
|
||||||
ew.writeln(` - "SCALEWAY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
|
ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check`)
|
||||||
ew.writeln(` - "SCALEWAY_TTL": The TTL of the TXT record used for the DNS challenge`)
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/scaleway`)
|
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()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectel`)
|
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":
|
case "servercow":
|
||||||
// generated from: providers/dns/servercow/servercow.toml
|
// generated from: providers/dns/servercow/servercow.toml
|
||||||
ew.writeln(`Configuration for Servercow.`)
|
ew.writeln(`Configuration for Servercow.`)
|
||||||
|
@ -2313,6 +2596,27 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/servercow`)
|
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":
|
case "simply":
|
||||||
// generated from: providers/dns/simply/simply.toml
|
// generated from: providers/dns/simply/simply.toml
|
||||||
ew.writeln(`Configuration for Simply.com.`)
|
ew.writeln(`Configuration for Simply.com.`)
|
||||||
|
@ -2613,6 +2917,26 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`)
|
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":
|
case "websupport":
|
||||||
// generated from: providers/dns/websupport/websupport.toml
|
// generated from: providers/dns/websupport/websupport.toml
|
||||||
ew.writeln(`Configuration for Websupport.`)
|
ew.writeln(`Configuration for Websupport.`)
|
||||||
|
@ -2676,6 +3000,27 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
ew.writeln()
|
ew.writeln()
|
||||||
ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex`)
|
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":
|
case "yandexcloud":
|
||||||
// generated from: providers/dns/yandexcloud/yandexcloud.toml
|
// generated from: providers/dns/yandexcloud/yandexcloud.toml
|
||||||
ew.writeln(`Configuration for Yandex Cloud.`)
|
ew.writeln(`Configuration for Yandex Cloud.`)
|
||||||
|
@ -2685,7 +3030,7 @@ func displayDNSHelp(w io.Writer, name string) error {
|
||||||
|
|
||||||
ew.writeln(`Credentials:`)
|
ew.writeln(`Credentials:`)
|
||||||
ew.writeln(` - "YANDEX_CLOUD_FOLDER_ID": The string id of folder (aka project) in Yandex Cloud`)
|
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()
|
||||||
|
|
||||||
ew.writeln(`Additional Configuration:`)
|
ew.writeln(`Additional Configuration:`)
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
.PHONY: default clean hugo hugo-build
|
.PHONY: default clean hugo hugo-build
|
||||||
|
|
||||||
default: hugo
|
default: clean hugo
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf public/
|
rm -rf public/
|
||||||
|
|
||||||
|
|
||||||
hugo-build: clean hugo-themes
|
hugo-build: clean
|
||||||
hugo --enableGitInfo --source .
|
hugo --enableGitInfo --source .
|
||||||
|
|
||||||
hugo:
|
hugo:
|
||||||
hugo server --disableFastRender --enableGitInfo --watch --source .
|
hugo server --disableFastRender --enableGitInfo --watch --source .
|
||||||
# hugo server -D
|
# 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
|
date: 2019-03-03T16:39:46+01:00
|
||||||
draft: false
|
draft: false
|
||||||
chapter: true
|
chapter: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Lego
|
|
||||||
|
|
||||||
Let's Encrypt client and ACME library written in Go.
|
Let's Encrypt client and ACME library written in Go.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -25,7 +23,7 @@ Let's Encrypt client and ACME library written in Go.
|
||||||
- TLS (tls-alpn-01)
|
- TLS (tls-alpn-01)
|
||||||
- SAN certificate support
|
- SAN certificate support
|
||||||
- [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default
|
- [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" >}})
|
- Comes with multiple optional [DNS providers]({{% ref "dns" %}})
|
||||||
- [Custom challenge solvers]({{< ref "usage/library/Writing-a-Challenge-Solver" >}})
|
- [Custom challenge solvers]({{% ref "usage/library/Writing-a-Challenge-Solver" %}})
|
||||||
- Certificate bundling
|
- Certificate bundling
|
||||||
- OCSP helper function
|
- 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:
|
Here is an example bash command using the Cloudflare DNS provider:
|
||||||
|
|
||||||
```console
|
```bash
|
||||||
$ CLOUDFLARE_EMAIL=you@example.com \
|
$ CLOUDFLARE_EMAIL=you@example.com \
|
||||||
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
|
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
|
||||||
lego --dns cloudflare --domains www.example.com --email you@example.com run
|
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:
|
Here is an example bash command using the CloudFlare DNS provider:
|
||||||
|
|
||||||
```console
|
```bash
|
||||||
$ cat /the/path/to/my/key
|
$ cat /the/path/to/my/key
|
||||||
b9841238feb177a84330febba8a83208921177bffe733
|
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
|
$ 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
|
```txt
|
||||||
No key found for account you@example.com. Generating a P256 key.
|
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. |
|
| `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.
|
The 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) |
|
| `ALICLOUD_SECURITY_TOKEN` | STS Security Token (optional) |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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
|
## 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)
|
- [Go client](https://github.com/aliyun/alibaba-cloud-sdk-go)
|
||||||
|
|
||||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
<!-- 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 |
|
| `ALL_INKL_PASSWORD` | KAS password |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## Additional Configuration
|
||||||
|
@ -54,7 +54,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `ALL_INKL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `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.
|
The 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 |
|
| `ARVANCLOUD_API_KEY` | API key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `AURORA_SECRET` | Secret password to be used |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `AUTODNS_API_USER` | Username |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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). |
|
| `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.
|
The 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
|
## 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 |
|
| `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.
|
The 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
|
date: 2019-03-03T16:39:46+01:00
|
||||||
draft: false
|
draft: false
|
||||||
slug: azuredns
|
slug: azuredns
|
||||||
dnsprovider:
|
dnsprovider:
|
||||||
since: "v0.1.0"
|
since: "v4.13.0"
|
||||||
code: "azuredns"
|
code: "azuredns"
|
||||||
url: "https://azure.microsoft.com/services/dns/"
|
url: "https://azure.microsoft.com/services/dns/"
|
||||||
---
|
---
|
||||||
|
@ -14,33 +14,50 @@ dnsprovider:
|
||||||
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
<!-- 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-->
|
<!--more-->
|
||||||
|
|
||||||
- Code: `azuredns`
|
- 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
|
```bash
|
||||||
### Using client secret
|
### Using client secret
|
||||||
|
|
||||||
AZURE_CLIENT_ID=<your service principal client ID> \
|
AZURE_CLIENT_ID=<your service principal client ID> \
|
||||||
AZURE_TENANT_ID=<your service principal tenant ID> \
|
AZURE_TENANT_ID=<your service principal tenant ID> \
|
||||||
AZURE_CLIENT_SECRET=<your service principal client secret> \
|
AZURE_CLIENT_SECRET=<your service principal client secret> \
|
||||||
lego --domains example.com --email your_example@email.com --dns azuredns run
|
lego --domains example.com --email your_example@email.com --dns azuredns run
|
||||||
|
|
||||||
### Using client certificate
|
### Using client certificate
|
||||||
|
|
||||||
AZURE_CLIENT_ID=<your service principal client ID> \
|
AZURE_CLIENT_ID=<your service principal client ID> \
|
||||||
AZURE_TENANT_ID=<your service principal tenant ID> \
|
AZURE_TENANT_ID=<your service principal tenant ID> \
|
||||||
AZURE_CLIENT_CERTIFICATE_PATH=<your service principal certificate path> \
|
AZURE_CLIENT_CERTIFICATE_PATH=<your service principal certificate path> \
|
||||||
lego --domains example.com --email your_example@email.com --dns azuredns run
|
lego --domains example.com --email your_example@email.com --dns azuredns run
|
||||||
|
|
||||||
### Using Azure CLI
|
### Using Azure CLI
|
||||||
|
|
||||||
az login \
|
az login \
|
||||||
lego --domains example.com --email your_example@email.com --dns azuredns run
|
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 |
|
| Environment Variable Name | Description |
|
||||||
|-----------------------|-------------|
|
|-----------------------|-------------|
|
||||||
|
| `AZURE_CLIENT_CERTIFICATE_PATH` | Client certificate path |
|
||||||
| `AZURE_CLIENT_ID` | Client ID |
|
| `AZURE_CLIENT_ID` | Client ID |
|
||||||
| `AZURE_CLIENT_SECRET` | Client secret |
|
| `AZURE_CLIENT_SECRET` | Client secret |
|
||||||
| `AZURE_RESOURCE_GROUP` | DNS zone resource group |
|
|
||||||
| `AZURE_SUBSCRIPTION_ID` | DNS zone subscription ID |
|
|
||||||
| `AZURE_TENANT_ID` | Tenant ID |
|
| `AZURE_TENANT_ID` | Tenant ID |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## Additional Configuration
|
||||||
|
|
||||||
| Environment Variable Name | Description |
|
| 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_ENVIRONMENT` | Azure environment, one of: public, usgovernment, and china |
|
||||||
| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||||
| `AZURE_PRIVATE_ZONE` | Set to true to use Azure Private DNS Zones and not public |
|
| `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_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_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 |
|
| `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.
|
The 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
|
## 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`
|
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`
|
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)
|
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:
|
Link:
|
||||||
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
|
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
#### Service Discovery
|
||||||
|
|
||||||
|
Lego automatically finds all visible Azure (private) DNS zones using [Azure ResourceGraph query](https://learn.microsoft.com/en-us/azure/governance/resource-graph/).
|
||||||
|
This can be limited by specifying environment variable `AZURE_SUBSCRIPTION_ID` and/or `AZURE_RESOURCE_GROUP` which limits the
|
||||||
|
DNS zones to only a subscription or to one resourceGroup.
|
||||||
|
|
||||||
|
Additionally environment variable `AZURE_SERVICEDISCOVERY_FILTER` can be used to filter DNS zones with an addition Kusto filter eg:
|
||||||
|
|
||||||
|
```
|
||||||
|
resources
|
||||||
|
| where type =~ "microsoft.network/dnszones"
|
||||||
|
| ${AZURE_SERVICEDISCOVERY_FILTER}
|
||||||
|
| project subscriptionId, resourceGroup, name
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Client secret
|
||||||
|
|
||||||
|
The Azure Credentials can be configured using the following environment variables:
|
||||||
|
* AZURE_CLIENT_ID = "Client ID"
|
||||||
|
* AZURE_CLIENT_SECRET = "Client secret"
|
||||||
|
* AZURE_TENANT_ID = "Tenant ID"
|
||||||
|
|
||||||
|
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
|
||||||
|
|
||||||
|
#### Client certificate
|
||||||
|
|
||||||
|
The Azure Credentials can be configured using the following environment variables:
|
||||||
|
* AZURE_CLIENT_ID = "Client ID"
|
||||||
|
* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
|
||||||
|
* AZURE_TENANT_ID = "Tenant ID"
|
||||||
|
|
||||||
|
This authentication method can be specifically used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
|
||||||
|
|
||||||
### Workload identity
|
### Workload identity
|
||||||
|
|
||||||
#### 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. \
|
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
|
||||||
Workloads running inside compute typed resource can inherit from this configuration to get rights on Azure resources.
|
|
||||||
|
|
||||||
#### Workload identity for AKS
|
|
||||||
|
|
||||||
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials. \
|
|
||||||
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand. \
|
|
||||||
|
|
||||||
Here is a summary of the steps to follow to use it :
|
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"`.
|
* 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 :
|
Link :
|
||||||
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
|
- [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 |
|
| `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.
|
The 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
|
## Additional Configuration
|
||||||
|
@ -52,7 +52,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `BINDMAN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `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.
|
The 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 |
|
| `BLUECAT_USER_NAME` | API username |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## Additional Configuration
|
||||||
|
@ -59,10 +59,11 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `BLUECAT_HTTP_TIMEOUT` | API request timeout |
|
| `BLUECAT_HTTP_TIMEOUT` | API request timeout |
|
||||||
| `BLUECAT_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `BLUECAT_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||||
| `BLUECAT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `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 |
|
| `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.
|
The 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 |
|
| `BRANDIT_API_USERNAME` | The API username |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `BUNNY_API_KEY` | API key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CHECKDOMAIN_TOKEN` | API token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CIVO_TOKEN` | Authentication token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CLOUDDNS_PASSWORD` | Account password |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `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.
|
The 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
|
## Additional Configuration
|
||||||
|
|
||||||
| Environment Variable Name | Description |
|
| Environment Variable Name | Description |
|
||||||
|--------------------------------|-------------|
|
|--------------------------------|-------------|
|
||||||
| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout |
|
| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout (in seconds) |
|
||||||
| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check (in seconds) |
|
||||||
| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation (in seconds) |
|
||||||
| `CLOUDFLARE_TTL` | The TTL of the TXT record used for the DNS challenge |
|
| `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.
|
The 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
|
## 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.
|
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.
|
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:
|
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 |
|
| `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.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CLOUDXNS_SECRET_KEY` | The API secret key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CONOHA_TENANT_ID` | Tenant ID |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `CONSTELLIX_SECRET_KEY` | User secret key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `DERAK_API_KEY` | The API key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## Additional Configuration
|
||||||
|
@ -54,7 +54,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `DERAK_WEBSITE_ID` | Force the zone/website ID |
|
| `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.
|
The 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 |
|
| `DESEC_TOKEN` | Domain token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `OS_USER_ID` | User ID |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## Additional Configuration
|
||||||
|
@ -77,11 +77,12 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `DESIGNATE_POLLING_INTERVAL` | Time between DNS propagation check |
|
| `DESIGNATE_POLLING_INTERVAL` | Time between DNS propagation check |
|
||||||
| `DESIGNATE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `DESIGNATE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||||
| `DESIGNATE_TTL` | The TTL of the TXT record used for the DNS challenge |
|
| `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_PROJECT_ID` | Project ID |
|
||||||
| `OS_TENANT_NAME` | Tenant name (deprecated see OS_PROJECT_NAME and OS_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.
|
The 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
|
## 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 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)
|
- [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
|
## More information
|
||||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns digitalocean --domains my.example.org run
|
||||||
| `DO_AUTH_TOKEN` | Authentication token |
|
| `DO_AUTH_TOKEN` | Authentication token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `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.
|
The 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 |
|
| `DNSIMPLE_OAUTH_TOKEN` | OAuth token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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
|
## 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.
|
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/),
|
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.
|
To authenticate you need to provide a valid API token.
|
||||||
HTTP Basic Authentication is intentionally not supported.
|
HTTP Basic Authentication is intentionally not supported.
|
||||||
|
@ -69,7 +69,7 @@ HTTP Basic Authentication is intentionally not supported.
|
||||||
### API tokens
|
### API tokens
|
||||||
|
|
||||||
You can [generate a new API token](https://support.dnsimple.com/articles/api-access-token/) from your account page.
|
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 |
|
| `DNSMADEEASY_API_SECRET` | The API Secret key |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `DNSPOD_API_KEY` | The user token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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 |
|
| `DODE_TOKEN` | API token |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The 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
|
## 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 |
|
| `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.
|
The 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
|
## 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. -->
|
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
|
||||||
<!-- providers/dns/dode/dode.toml -->
|
<!-- providers/dns/dode/dode.toml -->
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue