Compare commits
183 commits
tcl/master
...
tcl/http-0
Author | SHA1 | Date | |
---|---|---|---|
|
e70ec01016 | ||
|
49dacb2db1 | ||
|
90a0b0e1e4 | ||
|
6fbfe93012 | ||
|
36fa8b661f | ||
|
748e4c0d70 | ||
|
85b5ef459a | ||
|
6e2e6d2ea7 | ||
|
0c0ee41c5f | ||
|
a83482cac4 | ||
|
d81507c126 | ||
|
56986eaa99 | ||
|
c704ba5832 | ||
|
d2898e1706 | ||
|
20c8d6c413 | ||
|
eb7de2a32f | ||
|
141d60d41e | ||
|
69bd2e0373 | ||
|
0da0942081 | ||
|
d52f7b0b58 | ||
|
253e3305bc | ||
|
b95f03d3b3 | ||
|
65cd007d3f | ||
|
75b910b296 | ||
|
b3e630761e | ||
|
dc429c26a0 | ||
|
5c6572392f | ||
|
2d880c2e6c | ||
|
fd2bdf9e48 | ||
|
beaa35caf9 | ||
|
7062e8c28f | ||
|
f93651a54d | ||
|
29e98f8a43 | ||
|
c083a989a1 | ||
|
cb24671848 | ||
|
501aded55b | ||
|
5ab212cf8d | ||
|
24f421463a | ||
|
fa7cf5cd4f | ||
|
db2ab55cdd | ||
|
13b5cb57f0 | ||
|
5bea70766f | ||
|
c759f56556 | ||
|
9873b9b897 | ||
|
04864ff13b | ||
|
321cea51e4 | ||
|
fa0c05f5d0 | ||
|
4cbe9a2af5 | ||
|
834a9089f1 | ||
|
c63be848f6 | ||
|
69cacab700 | ||
|
82073f3bcd | ||
|
8992dae8a7 | ||
|
2f464d47d6 | ||
|
c0f04e87e7 | ||
|
9dbc0988b8 | ||
|
96bb0ba904 | ||
|
e508b664b3 | ||
|
8abe1008bb | ||
|
96a8381a6b | ||
|
4d7fcb6048 | ||
|
65e8940311 | ||
|
71b48f8217 | ||
|
942ba6f591 | ||
|
de6f9d26b1 | ||
|
f038102ef9 | ||
|
975c40a8bb | ||
|
d3d59c166e | ||
|
5eb87685e2 | ||
|
f89e257694 | ||
|
220c608c80 | ||
|
92bde4cd56 | ||
|
d39d57fbc9 | ||
|
bf10a46784 | ||
|
c61aeba3e2 | ||
|
11b4beff7e | ||
|
2ec9e42ee3 | ||
|
983c181e45 | ||
|
f6d1413a3f | ||
|
acd338259d | ||
|
42aa57e2b9 | ||
|
76eb1eac8a | ||
|
d60c335cc0 | ||
|
55dd478cb2 | ||
|
ca32b56550 | ||
|
8623f0df01 | ||
|
a832515ad5 | ||
|
ef9086a0f9 | ||
|
8dd1fa5a32 | ||
|
3371145f01 | ||
|
874e3ea023 | ||
|
27fd142ca1 | ||
|
61553c4195 | ||
|
441775ec65 | ||
|
40dcce60be | ||
|
19bbefbc8c | ||
|
719d26c0fc | ||
|
5bde3fbedd | ||
|
008adfddca | ||
|
6dd8d035d1 | ||
|
adea063bb1 | ||
|
82e9a5e2a9 | ||
|
6933296e2f | ||
|
a7ca3d7f1c | ||
|
9d0cd24533 | ||
|
fac40196c5 | ||
|
1106ad382f | ||
|
fd6047a1b8 | ||
|
ba67a265c0 | ||
|
7fe1796157 | ||
|
b9b0412f7c | ||
|
23824af555 | ||
|
c5a95c4cd0 | ||
|
83ff393131 | ||
|
719adc3964 | ||
|
6decaed8a3 | ||
|
ac6c0a5f63 | ||
|
1a3de70183 | ||
|
ec8306f4b5 | ||
|
46fe435c2c | ||
|
755d479e62 | ||
|
4eab81a9eb | ||
|
9c1a856b73 | ||
|
d263a28c64 | ||
|
ad6e38e7db | ||
|
3c73f624ba | ||
|
bb830677d1 | ||
|
7fd1704282 | ||
|
143aa4f3ee | ||
|
9d4c60e67a | ||
|
c17f659c5d | ||
|
c847ac4a4c | ||
|
3ba40ff7da | ||
|
68dc83ae0a | ||
|
e98dea02de | ||
|
6da922047a | ||
|
7186ebb6f1 | ||
|
5af3c6c042 | ||
|
cab8e1f556 | ||
|
d51b5e408b | ||
|
4f242c93e6 | ||
|
c9ff534e39 | ||
|
52990b3c9e | ||
|
8afdc9d01c | ||
|
2140e6befe | ||
|
bf8c7abf6d | ||
|
c2fd4498e5 | ||
|
b523518108 | ||
|
d0aa6b3c0d | ||
|
113648a368 | ||
|
766e581f8d | ||
|
a05e3832a1 | ||
|
a6ddcac65a | ||
|
5ef996ee05 | ||
|
8a7fd67c58 | ||
|
6209be73de | ||
|
11724c7bf3 | ||
|
04359244cd | ||
|
668a529c48 | ||
|
6af2c756ac | ||
|
cef49ab93b | ||
|
838eff2c02 | ||
|
a7befed6e8 | ||
|
a423bb7411 | ||
|
406dad30fe | ||
|
6792701dbc | ||
|
b37d60c033 | ||
|
c365d7efc8 | ||
|
ed559f0568 | ||
|
e8a97d9b90 | ||
|
f4f42f16b7 | ||
|
07c4daeff3 | ||
|
01747e39cc | ||
|
35c259e91d | ||
|
3cefc7a51b | ||
|
f582d12f65 | ||
|
d21706420a | ||
|
ae7823705e | ||
|
6c13564bad | ||
|
fc47c35e89 | ||
|
ed14dda361 | ||
|
fa815959fa | ||
|
f6aad431be |
605 changed files with 18762 additions and 3349 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
||||||
github: ldez
|
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Bug Report
|
name: 🐞 Bug Report
|
||||||
description: Create a report to help us improve.
|
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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -86,24 +105,33 @@ 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.
|
||||||
type Certifier struct {
|
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.
|
||||||
|
@ -124,8 +152,9 @@ 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.
|
||||||
|
@ -194,8 +218,9 @@ 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"
|
||||||
|
@ -18,16 +15,18 @@ 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
|
||||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
if window := end.Sub(start); window > 0 {
|
||||||
rt := start.Add(randomDuration)
|
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||||
|
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,18 +146,19 @@ 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)
|
||||||
return ent, nil
|
if ok && entAny != nil {
|
||||||
|
ent, ok1 := entAny.(*soaCacheEntry)
|
||||||
|
if ok1 && !ent.isExpired() {
|
||||||
|
return ent, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||||
|
@ -164,41 +166,42 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error)
|
||||||
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",
|
||||||
|
@ -119,18 +119,19 @@ var findXByFqdnTestCases = []struct {
|
||||||
nameservers: []string{":7053", ":8053", "8.8.8.8:53"},
|
nameservers: []string{":7053", ":8053", "8.8.8.8:53"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "only non-existent nameservers",
|
desc: "only non-existent nameservers",
|
||||||
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)
|
||||||
|
|
205
cmd/cmd_renew.go
205
cmd/cmd_renew.go
|
@ -17,13 +17,24 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flag names.
|
||||||
const (
|
const (
|
||||||
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
flgDays = "days"
|
||||||
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
flgARIEnable = "ari-enable"
|
||||||
renewEnvCertPath = "LEGO_CERT_PATH"
|
flgARIWaitToRenewDuration = "ari-wait-to-renew-duration"
|
||||||
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
flgReuseKey = "reuse-key"
|
||||||
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
flgRenewHook = "renew-hook"
|
||||||
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
flgNoRandomSleep = "no-random-sleep"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
||||||
|
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
||||||
|
renewEnvCertPath = "LEGO_CERT_PATH"
|
||||||
|
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
||||||
|
renewEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH"
|
||||||
|
renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH"
|
||||||
|
renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createRenew() *cli.Command {
|
func createRenew() *cli.Command {
|
||||||
|
@ -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)
|
||||||
|
|
141
cmd/flags.go
141
cmd/flags.go
|
@ -1,145 +1,218 @@
|
||||||
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"
|
||||||
|
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,
|
||||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
EnvVars: []string{"LEGO_EAB"},
|
||||||
|
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "kid",
|
Name: flgKID,
|
||||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
EnvVars: []string{"LEGO_EAB_KID"},
|
||||||
|
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "hmac",
|
Name: flgHMAC,
|
||||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
EnvVars: []string{"LEGO_EAB_HMAC"},
|
||||||
|
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&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.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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
23
cmd/setup.go
23
cmd/setup.go
|
@ -35,16 +35,17 @@ func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.
|
||||||
|
|
||||||
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
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"
|
||||||
|
@ -13,52 +14,63 @@ import (
|
||||||
"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/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(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 +79,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 +97,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 +109,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 +117,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 -->
|
||||||
|
|
|
@ -42,7 +42,7 @@ lego --email example@example.com --dns domeneshop --domains example.com run
|
||||||
| `DOMENESHOP_API_TOKEN` | API token |
|
| `DOMENESHOP_API_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" >}}).
|
||||||
| `DOMENESHOP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
| `DOMENESHOP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||||
|
|
||||||
### API credentials
|
### API credentials
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ lego --email you@example.com --dns dreamhost --domains my.example.org run
|
||||||
| `DREAMHOST_API_KEY` | The API key |
|
| `DREAMHOST_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
|
||||||
|
@ -53,7 +53,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
||||||
| `DREAMHOST_TTL` | The TTL of the TXT record used for the DNS challenge |
|
| `DREAMHOST_TTL` | The TTL of the TXT record used for the DNS challenge |
|
||||||
|
|
||||||
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
|
||||||
More information [here]({{< ref "dns#configuration-and-credentials" >}}).
|
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue