Compare commits

...

193 commits

Author SHA1 Message Date
256957db5c Expose rate limit errors in public API
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-11-05 13:32:07 +03:00
10ccc57587 frostfs: Add object ID to error messages
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-17 11:21:50 +03:00
597d147c7d frostfs: Reject tokens with slash character
Current reverse proxy configs assume that token is a valid filename
with no nesting levels. It's better to reject unsupported tokens early

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-16 17:16:35 +03:00
61ce76f648 frostfs: Expire saved tokens automatically
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-16 16:18:20 +03:00
9ff9d5be25 frostfs: Fix invalid signatures issued by key from json
Ephemeral keys worked fine while keys loaded from filesystem would
generate invalid signatures. This was caused by destroying private key
material during calls to Wallet.Close() and Account.Close(). Since these
calls do nothing except wiping the private key, we omit them now.
Responsibility for private key security is delegated to caller of getKey()

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-16 12:05:26 +03:00
254983fbe2 Fix linter errors
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-15 16:52:53 +03:00
d8c8aba312 Add HTTP-01 solver with FrostFS backend
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-15 16:52:34 +03:00
30563a0fb1 Open NEO wallet from file system
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-15 15:45:50 +03:00
23e60f1e98 Read test connection credentials from env vars
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-15 13:53:08 +03:00
bb87c097ba feat: FrostFS client for future HTTP-01 solver
Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2024-10-15 12:36:23 +03:00
Fernandez Ludovic
e70ec01016 Prepare release v4.19.2 2024-10-06 20:10:36 +02:00
Ludovic Fernandez
49dacb2db1
fix: go1.22 compatibility (#2295) 2024-10-06 20:01:31 +02:00
Fernandez Ludovic
90a0b0e1e4 Detach v4.19.1 2024-10-06 13:48:52 +02:00
Fernandez Ludovic
6fbfe93012 Prepare release v4.19.1 2024-10-06 13:48:52 +02:00
Ludovic Fernandez
36fa8b661f
fix: follow Go convention about generated comment (#2292) 2024-10-05 15:13:40 +02:00
Ludovic Fernandez
748e4c0d70
epik: add User-Agent (#2290) 2024-10-05 00:11:16 +02:00
Ludovic Fernandez
85b5ef459a
selectelv2: use baseURL from configuration (#2291) 2024-10-04 23:18:51 +02:00
Fernandez Ludovic
6e2e6d2ea7 Detach v4.19.0 2024-10-03 16:10:04 +02:00
Fernandez Ludovic
0c0ee41c5f Prepare release v4.19.0 2024-10-03 16:10:04 +02:00
djx30103
a83482cac4
perf: reducing the lock strength of the soa cache entry (#2285)
Co-authored-by: icpd <35096485+icpd@users.noreply.github.com>
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-10-03 05:20:57 +02:00
Ludovic Fernandez
d81507c126
feat: add PropagationWait function (#2288) 2024-10-02 19:31:52 +02:00
Ludovic Fernandez
56986eaa99
namesilo: restrict CleanUp (#2287) 2024-09-29 23:28:55 +02:00
Ludovic Fernandez
c704ba5832
feat: add dns.propagation-rns option (#2284) 2024-09-26 21:02:39 +02:00
Ludovic Fernandez
d2898e1706
chore: use contants for flag names (#2283) 2024-09-20 19:47:50 +02:00
Ludovic Fernandez
20c8d6c413
Add DNS provider for SelfHost.(de|eu) (#2278)
Co-authored-by: Dominik Menke <git@dmke.org>
2024-09-20 13:46:38 +02:00
Fernandez Ludovic
eb7de2a32f chore: improve PR template 2024-09-20 03:34:39 +02:00
Fernandez Ludovic
141d60d41e chore: add PR template 2024-09-20 03:19:32 +02:00
Ludovic Fernandez
69bd2e0373
ionos: follow CNAME (#2281) 2024-09-19 15:29:20 +02:00
Ludovic Fernandez
0da0942081
chore: use UTC instead of GMT when possible (#2275) 2024-09-16 19:04:08 +02:00
Ludovic Fernandez
d52f7b0b58
chore: update dependencies (#2272) 2024-09-14 14:26:25 +02:00
Ludovic Fernandez
253e3305bc
godaddy: fix cleanup (#2270) 2024-09-10 20:53:16 +02:00
endymx
b95f03d3b3
Add DNS provider for HuaweiCloud (#2267)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-09-10 11:13:04 +00:00
mlec
65cd007d3f
exoscale: update Egoscale to v3.1.1 (#2256)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-09-10 12:52:08 +02:00
Ludovic Fernandez
75b910b296
feat(cli): add dns.propagation-wait flag (#2266) 2024-09-07 21:40:21 +02:00
UnrealShadow
b3e630761e
docs: fix typos in some DNS descriptors (#2264)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-09-06 10:18:25 +00:00
Laurent Cheylus
dc429c26a0
docs: add installation for OpenBSD (#2261) 2024-09-04 12:54:29 +00:00
Laurent Cheylus
5c6572392f
docs: update installation for FreeBSD (#2260) 2024-09-04 12:04:43 +00:00
Fernandez Ludovic
2d880c2e6c Detach v4.18.0 2024-08-31 13:04:37 +02:00
Fernandez Ludovic
fd2bdf9e48 Prepare release v4.18.0 2024-08-31 13:04:37 +02:00
Ludovic Fernandez
beaa35caf9
Add DNS provider for Mittwald (#2200) 2024-08-30 19:46:02 +02:00
Ludovic Fernandez
7062e8c28f
Add DNS provider for Lima-City (#2248) 2024-08-30 01:20:44 +02:00
Ludovic Fernandez
f93651a54d
Add DNS provider for mijn.host (#2252) 2024-08-29 21:07:01 +02:00
Rob Stradling
29e98f8a43
fix(ari): avoid Int63n panic in ShouldRenewAt() (#2246) 2024-08-21 22:33:28 +00:00
YanLIU
c083a989a1
chore: update go version inside go.mod (#2241) 2024-08-15 19:30:45 +00:00
Fernandez Ludovic
cb24671848 docs: fix favicon 2024-08-15 03:19:13 +02:00
Fernandez Ludovic
501aded55b chore: update linter 2024-08-15 02:54:50 +02:00
Ludovic Fernandez
5ab212cf8d
docs: update theme (#2240) 2024-08-14 23:55:11 +02:00
Kyle Julian
24f421463a
cloudflare: update documentation to specify the unit of time-based options (#2239)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-08-10 21:36:05 +00:00
Austen
fa7cf5cd4f
feat: add LEGO_ISSUER_CERT_PATH to run hook (#2234) 2024-07-25 21:25:06 +02:00
Mikel Olasagasti Uranga
db2ab55cdd
chore: bump to github.com/vultr/govultr/v3 (#2235)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-07-25 16:58:21 +02:00
Fernandez Ludovic
13b5cb57f0 directadmin: update documentation 2024-07-21 21:21:53 +02:00
Ludovic Fernandez
5bea70766f
chore: homogenous zone name env var management (#2232) 2024-07-21 15:06:01 +02:00
jwklijnsma
c759f56556
Add DNS provider for DirectAdmin (#2225)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-07-21 13:32:17 +02:00
Ludovic Fernandez
9873b9b897
bluecat: skip deploy (#2230) 2024-07-20 16:01:18 +02:00
Jan Dittrich
04864ff13b
designate: allow manually overwriting DNS zone (#2204)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-07-18 15:27:04 +00:00
Ludovic Fernandez
321cea51e4
fix: restore Solaris (#2217) 2024-06-29 18:50:36 +02:00
Ludovic Fernandez
fa0c05f5d0
ovh: allow to use ovh.conf file (#2216) 2024-06-26 12:44:05 +00:00
dj-money
4cbe9a2af5
docs: add note about snap (#2201) 2024-06-20 20:37:37 +02:00
Ludovic Fernandez
834a9089f1
feat: expose certificates pool creation (#2210) 2024-06-13 21:10:59 +00:00
Ludovic Fernandez
c63be848f6
feat: add option to handle the overall request limit (#2209) 2024-06-13 22:48:04 +02:00
Fernandez Ludovic
69cacab700 chore: improve issue templates 2024-06-12 03:25:26 +02:00
Fernandez Ludovic
82073f3bcd chore: fix changelog 2024-06-12 03:14:22 +02:00
Fernandez Ludovic
8992dae8a7 Detach v4.17.4 2024-06-12 03:12:41 +02:00
Fernandez Ludovic
2f464d47d6 Prepare release v4.17.4 2024-06-12 03:12:41 +02:00
Ludovic Fernandez
c0f04e87e7
chore: update linter (#2206) 2024-06-12 03:09:21 +02:00
Ludovic Fernandez
9dbc0988b8
chore: update dependencies (#2205) 2024-06-12 02:55:35 +02:00
Fernandez Ludovic
96bb0ba904 chore: improve issue templates 2024-05-31 16:22:18 +02:00
Fernandez Ludovic
e508b664b3 Detach v4.17.3 2024-05-28 16:24:48 +02:00
Fernandez Ludovic
8abe1008bb Prepare release v4.17.3 2024-05-28 16:24:35 +02:00
Fernandez Ludovic
96a8381a6b fix: disable snap release 2024-05-28 16:20:55 +02:00
Fernandez Ludovic
4d7fcb6048 Detach v4.17.2 2024-05-28 14:56:30 +02:00
Fernandez Ludovic
65e8940311 Prepare release v4.17.2 2024-05-28 14:56:10 +02:00
Fernandez Ludovic
71b48f8217 fix: disable Solaris build 2024-05-28 14:55:05 +02:00
Fernandez Ludovic
942ba6f591 Detach v4.17.1 2024-05-28 13:30:57 +02:00
Fernandez Ludovic
de6f9d26b1 Prepare release v4.17.1 2024-05-28 13:30:57 +02:00
Fernandez Ludovic
f038102ef9 chore: fix snapcraft release 2024-05-28 13:28:21 +02:00
Fernandez Ludovic
975c40a8bb Detach v4.17.0 2024-05-28 12:46:36 +02:00
Fernandez Ludovic
d3d59c166e Prepare release v4.17.0 2024-05-28 12:46:36 +02:00
jotasi
5eb87685e2
pdns: reconstruct zone URLs to enable non-root folder API endpoints (#2141)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-05-27 22:18:24 +02:00
Henrik Alves
f89e257694
dode: update API URL (#2191) 2024-05-27 15:28:32 +02:00
Ludovic Fernandez
220c608c80
chore: update linter (#2190) 2024-05-27 13:04:32 +02:00
Igor Zornik
92bde4cd56
chore: remove remaining deprecated ARI call (#2189) 2024-05-21 20:14:25 +02:00
Ludovic Fernandez
d39d57fbc9
chore: remove useless file 2024-05-12 03:23:19 +02:00
Ludovic Fernandez
bf10a46784
chore: update linter (#2183) 2024-05-09 19:31:20 +00:00
Ludovic Fernandez
c61aeba3e2
godaddy: documentation new API limitations (#2182) 2024-05-09 21:05:42 +02:00
Ludovic Fernandez
11b4beff7e
route53: adds option to not wait for changes (#2181) 2024-05-09 21:05:21 +02:00
Orgad Shaneh
2ec9e42ee3
azuredns: use TenantID also for cli authentication (#2176) 2024-05-07 18:56:05 +00:00
Igor Zornik
983c181e45
feat: renewal retry after value (#2170) 2024-05-07 17:01:12 +00:00
Ludovic Fernandez
f6d1413a3f
exoscale: simplify record creation (#2179) 2024-05-07 10:30:44 +00:00
Idix
acd338259d
ovh: add OAuth2 authentication (#2173)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-05-06 15:43:25 +02:00
Ludovic Fernandez
42aa57e2b9
exec: stream command output (#2166) 2024-04-21 18:01:40 +02:00
Ludovic Fernandez
76eb1eac8a
feat: add LEGO_ISSUER_CERT_PATH to hook (#2164) 2024-04-17 13:09:06 +00:00
Ludovic Fernandez
d60c335cc0
feat: fills LEGO_CERT_PFX_PATH and LEGO_CERT_PEM_PATH only when needed (#2160) 2024-04-16 23:10:59 +02:00
Ludovic Fernandez
55dd478cb2
chore: update dependencies (#2156) 2024-04-15 15:54:21 +02:00
Ludovic Fernandez
ca32b56550
oracle: update API client (#2155) 2024-04-14 23:27:26 +02:00
Artem Chirkov
8623f0df01
Add DNS provider for Selectel v2 (#2152)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-04-10 10:29:29 +00:00
Fernandez Ludovic
a832515ad5 chore: fix typos 2024-04-10 11:19:21 +02:00
Ludovic Fernandez
ef9086a0f9
httpnet: add provider to NewDNSChallengeProviderByName (#2146) 2024-03-27 11:19:07 +01:00
xpume
8dd1fa5a32
docs: fix link to alibaba API documentation (#2145) 2024-03-24 21:39:59 +00:00
Ludovic Fernandez
3371145f01
chore: update to go1.22 (#2144) 2024-03-20 20:30:35 +01:00
Markus Blaschke
874e3ea023
azuredns: servicediscovery for zones (#2140)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-20 04:36:35 +01:00
Ludovic Fernandez
27fd142ca1
scaleway: add alternative env var names (#2136) 2024-03-20 03:31:18 +00:00
Guillaume Belanger
61553c4195
chore: add snap to release packages (#2134) 2024-03-15 03:12:08 +01:00
Fernandez Ludovic
441775ec65 Detach v4.16.1 2024-03-11 00:49:12 +01:00
Fernandez Ludovic
40dcce60be Prepare release v4.16.1 2024-03-11 00:49:12 +01:00
Ludovic Fernandez
19bbefbc8c
fix: don't generate ARI cert ID if ARI is not enable (#2138) 2024-03-11 00:04:53 +01:00
Fernandez Ludovic
719d26c0fc Detach v4.16.0 2024-03-10 00:15:13 +01:00
Fernandez Ludovic
5bde3fbedd Prepare release v4.16.0 2024-03-10 00:15:13 +01:00
Ludovic Fernandez
008adfddca
Add DNS provider for Shellrent (#2126) 2024-03-08 17:49:57 +01:00
Samantha
6dd8d035d1
feat: implement 'replaces' field in newOrder and draft-ietf-acme-ari-03 CertID changes (#2114) 2024-03-08 15:22:09 +00:00
Ludovic Fernandez
adea063bb1
chore: update to github.com/go-jose/go-jose/v4 (#2130) 2024-03-08 14:07:55 +01:00
fuku
82e9a5e2a9
nifcloud: fix bug in case of same auth zone (#2125)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-04 20:17:28 +01:00
Ludovic Fernandez
6933296e2f
easydns: fix zone detection (#2121) 2024-03-03 15:41:55 +01:00
Jess Thrysoee
a7ca3d7f1c
bunny: support delegated sub-domains (#2123) 2024-02-29 21:08:03 +00:00
Ludovic Fernandez
9d0cd24533
fix: test on CI (#2119) 2024-02-24 20:49:40 +01:00
Ludovic Fernandez
fac40196c5
Add DNS provider for Mail-in-a-Box (#2110) 2024-02-24 01:31:16 +01:00
Ludovic Fernandez
1106ad382f
ns1: fix record creation (#2115) 2024-02-16 21:24:58 +01:00
Fernandez Ludovic
fd6047a1b8 chore: use org variables inside the CI 2024-02-15 02:22:55 +01:00
Ludovic Fernandez
ba67a265c0
feat: improve errors and logs related to DNS call (#2109) 2024-02-11 14:37:09 +01:00
Ludovic Fernandez
7fe1796157
chore: minor changes (#2108) 2024-02-09 21:55:43 +01:00
Ikko Eltociear Ashimine
b9b0412f7c
chore: fix typo (#2106) 2024-02-08 16:14:25 +00:00
Ludovic Fernandez
23824af555
chore: update to go1.21 (#2103) 2024-02-08 03:16:48 +01:00
Ludovic Fernandez
c5a95c4cd0
cpanel: remove custom DNS call (#2102) 2024-02-06 17:17:59 +01:00
Ludovic Fernandez
83ff393131
Add DNS provider for CPanel and WHM (#1977) 2024-02-04 19:43:54 +01:00
Mike Dalrymple
719adc3964
route53: fix example (#2100) 2024-02-03 02:36:43 +00:00
Fernandez Ludovic
6decaed8a3 chore: update CI 2024-02-02 02:21:15 +01:00
Ludovic Fernandez
ac6c0a5f63
chore: replace mitchellh/mapstructure by go-viper/mapstructure/v2 (#2095) 2024-01-30 17:55:09 +00:00
Rahil Bhimjiani
1a3de70183
docs: add installation for Gentoo (#2094)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-30 13:06:03 +00:00
Fernandez Ludovic
ec8306f4b5 Detach v4.15.0 2024-01-28 18:02:59 +01:00
Fernandez Ludovic
46fe435c2c Prepare release v4.15.0 2024-01-28 18:02:59 +01:00
Ludovic Fernandez
755d479e62
chore: update dependencies (#2092) 2024-01-28 14:04:31 +01:00
pschou
4eab81a9eb
feat(cli): add format option for PFX encoding (#2063)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-27 22:44:14 +00:00
m0t1x
9c1a856b73
pdns: optional custom API version (#2019)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-27 22:23:10 +00:00
Martin Weindel
d263a28c64
feat: support simplified issuance for very long domain names at Let's Encrypt (#2054)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-26 23:16:47 +00:00
Ludovic Fernandez
ad6e38e7db
desec: increase default propagation interval (#2089) 2024-01-25 14:21:11 +01:00
Samantha
3c73f624ba
feat: update CertID format as per draft-ietf-acme-ari-02 (#2066) 2024-01-24 21:02:50 +00:00
Ludovic Fernandez
bb830677d1
constellix: follow rate limiting headers (#2085) 2024-01-23 14:18:54 +00:00
Ludovic Fernandez
7fd1704282
inwx: improve sleep calculation (#2086) 2024-01-19 13:55:57 +01:00
Phil Pennock
143aa4f3ee
gcloud: support GCE_ZONE_ID to bypass zone list (#2081)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-18 21:30:20 +00:00
Günther Noack
9d4c60e67a
inwx: wait before generating new TOTP TANs (#2084)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-18 20:51:57 +00:00
Ludovic Fernandez
c17f659c5d
ionos: fix DNS record removal (#2083) 2024-01-17 22:18:17 +01:00
pschou
c847ac4a4c
feat(cli): add environment variable for specifying alternate directory URL (#2061) 2024-01-12 21:11:42 +00:00
George Kusayko
3ba40ff7da
Add DNS provider for Webnames (#2077)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-01-12 21:23:03 +01:00
Danil Solovyov
68dc83ae0a
cloudru: change default URLs (#2076) 2024-01-10 00:31:04 +00:00
Patrik
e98dea02de
scaleway: add cname support (#2075) 2024-01-06 13:02:10 +01:00
Fernandez Ludovic
6da922047a chore: change test ports (related to Windows and CI) 2024-01-06 12:43:26 +01:00
Alexis Savin
7186ebb6f1
efficientip: add insecure skip verify option (#2052)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-12 21:29:57 +01:00
Alexander Kazarin
5af3c6c042
regru: HTTP method changed to POST (#2051)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-12 13:38:13 +01:00
Alexander Kazarin
cab8e1f556
regru: client certificate support (#2050)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-11-10 02:15:33 +01:00
Ludovic Fernandez
d51b5e408b
chore: update linter (#2046) 2023-10-31 14:08:50 +01:00
pchanvallon
4f242c93e6
azuredns: allow oidc authentication (#2036)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-30 18:26:35 +01:00
Ludovic Fernandez
c9ff534e39
nifcloud: fix API requests (#2039) 2023-10-18 09:53:04 +00:00
Ludovic Fernandez
52990b3c9e
liquidweb: add LWAPI_ prefix for env vars (#2034) 2023-10-15 01:12:55 +02:00
Jack Hayhurst
8afdc9d01c
liquidweb: detect zone automatically (#2031)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-14 17:08:29 +02:00
Christoph
2140e6befe
gandiv5: Add "Bearer" prefix to the auth header (#2029) 2023-10-07 00:22:02 +00:00
pchanvallon
bf8c7abf6d
azuredns: provide the ability to select authentication methods (#2026)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-10-05 11:25:44 +00:00
volayvaz
c2fd4498e5
otc: sequential challenge (#2023) 2023-09-21 14:04:52 +00:00
Ludovic Fernandez
b523518108
ipv64: remove unused option (#2022) 2023-09-20 21:30:21 +00:00
Lars Wiegman
d0aa6b3c0d
doc: update Designate documentation (#1465) 2023-09-20 19:47:00 +00:00
Ludovic Fernandez
113648a368
gandiv5: add Personal Access Token support (#2007) 2023-09-20 03:42:25 +00:00
Ludovic Fernandez
766e581f8d
Add DNS provider for http.net (#2014) 2023-09-20 01:40:26 +00:00
Fernandez Ludovic
a05e3832a1 Detach v4.14.2 2023-09-20 01:32:03 +02:00
Fernandez Ludovic
a6ddcac65a Prepare release v4.14.2 2023-09-20 01:31:52 +02:00
Fernandez Ludovic
5ef996ee05 chore: temporary workaround 2023-09-20 01:30:33 +02:00
Fernandez Ludovic
8a7fd67c58 Detach v4.14.1 2023-09-20 00:53:13 +02:00
Fernandez Ludovic
6209be73de Prepare release v4.14.1 2023-09-20 00:53:13 +02:00
Ludovic Fernandez
11724c7bf3
bunny: fix zone detection (#2012) 2023-09-19 16:04:28 +00:00
Ludovic Fernandez
04359244cd
ovh: update client to v1.4.2 (#2010) 2023-09-04 14:56:17 +02:00
Ludovic Fernandez
668a529c48
bunny: use NRDCG fork (#2009) 2023-09-03 16:27:27 +02:00
Ludovic Fernandez
6af2c756ac
Revert "chore: exclude egoscale v1.19.0" (#2003) 2023-08-24 21:13:20 +02:00
Fernandez Ludovic
cef49ab93b Detach v4.14.0 2023-08-21 15:57:10 +02:00
Fernandez Ludovic
838eff2c02 Prepare release v4.14.0 2023-08-21 15:57:10 +02:00
Fernandez Ludovic
a7befed6e8 nearlyfreespeech: fix salt 2023-08-21 00:25:40 +02:00
Robert Obryk
a423bb7411
nearlyfreespeech: fix authentication (#1999)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-08-20 22:04:35 +00:00
Andreas Deininger
406dad30fe
doc: retrieve hugo-theme-learn as hugo module (#1994) 2023-08-19 19:11:03 +00:00
Andreas Deininger
6792701dbc
doc: fix broken links (#1997) 2023-08-19 19:03:45 +02:00
Ludovic Fernandez
b37d60c033
chore: update to go1.20 (#1993) 2023-08-19 16:05:33 +00:00
Andreas Deininger
c365d7efc8
doc: bump hugo version (#1996)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-08-19 01:20:15 +02:00
Andreas Deininger
ed559f0568
doc: fixing typos (#1995)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-08-18 18:10:11 +02:00
Daniel Néri
e8a97d9b90
exec: fix CLI documention (#1991) 2023-08-14 11:10:10 +00:00
Jed Laundry
f4f42f16b7
azuredns: update docs (#1988)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-08-11 14:27:38 +00:00
Ludovic Fernandez
07c4daeff3
Add DNS provider for Yandex 360 (#1975) 2023-08-04 15:39:23 +00:00
Ludovic Fernandez
01747e39cc
chore: exclude egoscale v1.19.0 (#1985) 2023-08-04 09:58:56 +02:00
Sylvain Desbureaux
35c259e91d
pdns: fix notify (#1983) 2023-08-03 12:37:24 +00:00
Ludovic Fernandez
3cefc7a51b
chore: fix grammar and typo (#1978)
Co-authored-by: Dominik Menke <git@dmke.org>
2023-07-29 12:59:24 +02:00
Ludovic Fernandez
f582d12f65
route53: avoid unexpected records deletion (#1976)
Co-authored-by: David King <king.c.david@googlemail.com>
2023-07-27 18:56:40 +00:00
ember
d21706420a
Allow to set EAB kid and hmac via environment variables (#1959)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-07-27 12:07:10 +00:00
Ludovic Fernandez
ae7823705e
Add DNS provider for cloud.ru (#1968) 2023-07-27 11:09:39 +00:00
kingcdavid
6c13564bad
Adding S3 support for HTTP domain validation (#1970)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2023-07-27 10:42:48 +00:00
Ludovic Fernandez
fc47c35e89
chore: migrate to aws-sdk-go-v2 (lightsail, route53) (#1973) 2023-07-27 12:15:26 +02:00
Fernandez Ludovic
ed14dda361 Detach v4.13.3 2023-07-25 19:49:30 +02:00
Fernandez Ludovic
fa815959fa Prepare release v4.13.3 2023-07-25 19:49:30 +02:00
Michael
f6aad431be
azure: fix configuration from env vars (#1972) 2023-07-25 13:42:49 +00:00
611 changed files with 19463 additions and 3349 deletions

1
.github/FUNDING.yml vendored
View file

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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -1,4 +1,4 @@
name: New DNS provider support name: 🧩 New DNS provider support
description: Request for the support of a new DNS provider. 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
View 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 #

View file

@ -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

View file

@ -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 ./...

View file

@ -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

View file

@ -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 }}

View file

@ -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

View 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

View file

@ -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 &#34;LEGO_PATH&#34; 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&#39;t clean up managed records like SOA and NS - **[dnsprovider]** designate: Don't clean up managed records like SOA and NS
- **[dnsprovider]** dnspod: update lib. - **[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&#39;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 &#34;HTTP request&#34;. - **[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.

View file

@ -7,7 +7,7 @@ To ensure a great and easy experience for everyone, please review the few guidel
- Use the issue search to see if the issue has already been reported. - 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.

View file

@ -16,7 +16,7 @@ Let's Encrypt client and ACME library written in Go.
- ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html)
- Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS ApplicationLayer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS ApplicationLayer Protocol Negotiation (ALPN) Challenge Extension
- Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [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 -->

View file

@ -16,7 +16,7 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account) 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)
} }

View file

@ -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")

View file

@ -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.

View file

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

View file

@ -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"
) )

View file

@ -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

View file

@ -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
} }

View file

@ -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)
}

View file

@ -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()

View file

@ -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,

View file

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

View file

@ -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 {

View file

@ -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()

View file

@ -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) {

View file

@ -22,6 +22,17 @@ import (
"golang.org/x/net/idna" "golang.org/x/net/idna"
) )
const (
// DefaultOverallRequestLimit is the overall number of request per second
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
// From the documentation the limitation is 20 requests per second,
// but using 20 as value doesn't work but 18 do.
// https://letsencrypt.org/docs/rate-limits/
// ZeroSSL has a limit of 7.
// https://help.zerossl.com/hc/en-us/articles/17864245480093-Advantages-over-Using-Let-s-Encrypt#h_01HT4Z1JCJFJQFJ1M3P7S085Q9
DefaultOverallRequestLimit = 18
)
// maxBodySize is the maximum size of body that we will read. // maxBodySize is the maximum size of body that we will read.
const maxBodySize = 1024 * 1024 const maxBodySize = 1024 * 1024
@ -63,6 +74,10 @@ type ObtainRequest struct {
Bundle bool Bundle bool
PreferredChain string PreferredChain string
AlwaysDeactivateAuthorizations bool AlwaysDeactivateAuthorizations bool
// A string uniquely identifying a previously-issued certificate which this
// order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
ReplacesCertID string
} }
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it. // ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
@ -79,6 +94,10 @@ type ObtainForCSRRequest struct {
Bundle bool Bundle bool
PreferredChain string PreferredChain string
AlwaysDeactivateAuthorizations bool AlwaysDeactivateAuthorizations bool
// A string uniquely identifying a previously-issued certificate which this
// order is intended to replace.
// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
ReplacesCertID string
} }
type resolver interface { type resolver interface {
@ -88,6 +107,7 @@ type resolver interface {
type CertifierOptions struct { type CertifierOptions struct {
KeyType certcrypto.KeyType KeyType certcrypto.KeyType
Timeout time.Duration Timeout time.Duration
OverallRequestLimit int
} }
// Certifier A service to obtain/renew/revoke certificates. // Certifier A service to obtain/renew/revoke certificates.
@ -95,15 +115,23 @@ type Certifier struct {
core *api.Core core *api.Core
resolver resolver resolver resolver
options CertifierOptions options CertifierOptions
overallRequestLimit int
} }
// NewCertifier creates a Certifier. // NewCertifier creates a Certifier.
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier { func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
return &Certifier{ c := &Certifier{
core: core, core: core,
resolver: resolver, resolver: resolver,
options: options, options: options,
} }
c.overallRequestLimit = options.OverallRequestLimit
if c.overallRequestLimit <= 0 {
c.overallRequestLimit = DefaultOverallRequestLimit
}
return c
} }
// Obtain tries to obtain a single certificate using all domains passed into it. // Obtain tries to obtain a single certificate using all domains passed into it.
@ -126,6 +154,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
orderOpts := &api.OrderOptions{ orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore, NotBefore: request.NotBefore,
NotAfter: request.NotAfter, NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
} }
order, err := c.core.Orders.NewWithOptions(domains, orderOpts) order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -149,11 +178,11 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError) failures := newObtainError()
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain) cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
if err != nil { if err != nil {
for _, auth := range authz { for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err failures.Add(challenge.GetTargetedDomain(auth), err)
} }
} }
@ -161,12 +190,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
c.deactivateAuthorizations(order, true) c.deactivateAuthorizations(order, true)
} }
// Do not return an empty failures map, because return cert, failures.Join()
// it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
} }
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it. // ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
@ -196,6 +220,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
orderOpts := &api.OrderOptions{ orderOpts := &api.OrderOptions{
NotBefore: request.NotBefore, NotBefore: request.NotBefore,
NotAfter: request.NotAfter, NotAfter: request.NotAfter,
ReplacesCertID: request.ReplacesCertID,
} }
order, err := c.core.Orders.NewWithOptions(domains, orderOpts) order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
@ -219,11 +244,11 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(obtainError) failures := newObtainError()
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain) cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
if err != nil { if err != nil {
for _, auth := range authz { for _, auth := range authz {
failures[challenge.GetTargetedDomain(auth)] = err failures.Add(challenge.GetTargetedDomain(auth), err)
} }
} }
@ -236,12 +261,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
cert.CSR = certcrypto.PEMEncode(request.CSR) cert.CSR = certcrypto.PEMEncode(request.CSR)
} }
// Do not return an empty failures map, return cert, failures.Join()
// because it would still be a non-nil error value
if len(failures) > 0 {
return cert, failures
}
return cert, nil
} }
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) { func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
@ -253,8 +273,10 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
} }
} }
// Determine certificate name(s) based on the authorization resources commonName := ""
commonName := domains[0] if len(domains[0]) <= 64 {
commonName = domains[0]
}
// RFC8555 Section 7.4 "Applying for Certificate Issuance" // RFC8555 Section 7.4 "Applying for Certificate Issuance"
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4 // https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
@ -262,7 +284,12 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
// Clients SHOULD NOT make any assumptions about the sort order of // Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order // "identifiers" or "authorizations" elements in the returned order
// object. // object.
san := []string{commonName}
var san []string
if commonName != "" {
san = append(san, commonName)
}
for _, auth := range order.Identifiers { for _, auth := range order.Identifiers {
if auth.Value != commonName { if auth.Value != commonName {
san = append(san, auth.Value) san = append(san, auth.Value)
@ -284,15 +311,14 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
return nil, err return nil, err
} }
commonName := domains[0]
certRes := &Resource{ certRes := &Resource{
Domain: commonName, Domain: domains[0],
CertURL: respOrder.Certificate, CertURL: respOrder.Certificate,
PrivateKey: privateKeyPem, PrivateKey: privateKeyPem,
} }
if respOrder.Status == acme.StatusValid { if respOrder.Status == acme.StatusValid {
// if the certificate is available right away, short cut! // if the certificate is available right away, shortcut!
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain) ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
if errR != nil { if errR != nil {
return nil, errR return nil, errR
@ -608,8 +634,13 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
return nil, err return nil, err
} }
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
if err != nil {
return nil, err
}
return &Resource{ return &Resource{
Domain: x509Certs[0].Subject.CommonName, Domain: domain,
Certificate: cert, Certificate: cert,
IssuerCertificate: issuer, IssuerCertificate: issuer,
CertURL: url, CertURL: url,

View file

@ -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 {

View 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)
}

View file

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

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View 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()

View file

@ -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()

View file

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

View file

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

View file

@ -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
} }

View file

@ -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)
}) })

View file

@ -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 {

View file

@ -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()

View file

@ -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()

View file

@ -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 {

View file

@ -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()

View file

@ -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
} }

View file

@ -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 == "" {

View file

@ -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{

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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}

View file

@ -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)

View file

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

View file

@ -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)

View file

@ -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
} }

View file

@ -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)

View file

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

View file

@ -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 {

View file

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

View file

@ -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:`)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -21,7 +21,7 @@ To start using the CLI prompt "provider", start lego with `--dns manual`:
$ lego --email "you@example.com" --domains="example.com" --dns "manual" run $ 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.

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns acme-dns --domains my.example.org run
| `ACME_DNS_STORAGE_PATH` | The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates. | | `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" %}}).

View file

@ -50,7 +50,7 @@ lego --email you@example.com --dns alidns --domains my.example.org run
| `ALICLOUD_SECURITY_TOKEN` | STS Security Token (optional) | | `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. -->

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns auroradns --domains my.example.org run
| `AURORA_SECRET` | Secret password to be used | | `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" %}}).

View file

@ -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" %}}).

View file

@ -43,7 +43,7 @@ _Please contribute by adding a CLI example._
| `instance metadata service` | If the credentials are **not** set via the environment, then it will attempt to get a bearer token via the [instance metadata service](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service). | | `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" %}}).

View file

@ -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`.

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns bindman --domains my.example.org run
| `BINDMAN_MANAGER_ADDRESS` | The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server | | `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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -53,20 +53,20 @@ lego --email you@example.com --dns cloudflare --domains my.example.org run
| `CLOUDFLARE_ZONE_API_TOKEN` | Alias to CF_ZONE_API_TOKEN | | `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:

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns cloudns --domains my.example.org run
| `CLOUDNS_AUTH_PASSWORD` | The password for API user ID | | `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" %}}).

View 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. -->

View file

@ -42,7 +42,7 @@ lego --email you@example.com --dns cloudxns --domains my.example.org run
| `CLOUDXNS_SECRET_KEY` | The API secret key | | `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" %}}).

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

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

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -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

View file

@ -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" %}}).

View 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. -->

View file

@ -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" %}}).

View file

@ -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.

View file

@ -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" %}}).

View file

@ -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" %}}).

View file

@ -40,7 +40,7 @@ lego --email you@example.com --dns dode --domains my.example.org run
| `DODE_TOKEN` | API token | | `DODE_TOKEN` | API token |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}). More information [here]({{% ref "dns#configuration-and-credentials" %}}).
## Additional Configuration ## Additional Configuration
@ -54,14 +54,14 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
| `DODE_TTL` | The TTL of the TXT record used for the DNS challenge | | `DODE_TTL` | The TTL of the TXT record used for the DNS challenge |
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here]({{< ref "dns#configuration-and-credentials" >}}). More information [here]({{% ref "dns#configuration-and-credentials" %}}).
## More information ## More information
- [API documentation](https://www.do.de/wiki/LetsEncrypt_-_Entwickler) - [API documentation](https://www.do.de/wiki/freie-ssl-tls-zertifikate-ueber-acme/)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. --> <!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/dode/dode.toml --> <!-- providers/dns/dode/dode.toml -->

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