mirror of
https://github.com/dehydrated-io/dehydrated.git
synced 2026-01-11 22:30:44 +01:00
Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911a822c0c | ||
|
|
4fd777e87e | ||
|
|
e3ef43c816 | ||
|
|
67b111a7b0 | ||
|
|
fa68ad8b23 | ||
|
|
5c4adf6baa | ||
|
|
35bfea55b6 | ||
|
|
ea84199863 | ||
|
|
6091ba4bc2 | ||
|
|
6fb8eba56a | ||
|
|
19c7fbbf47 | ||
|
|
7128e6b63c | ||
|
|
861f4c733d | ||
|
|
ad3f08084c | ||
|
|
784fb806c8 | ||
|
|
b2574b16d1 | ||
|
|
da641588ce | ||
|
|
8e6ddf6286 | ||
|
|
8e5977890a | ||
|
|
3bcf0c7f5a | ||
|
|
b347bc9086 | ||
|
|
08477170e9 | ||
|
|
f4cf92bae5 | ||
|
|
93573cda3c | ||
|
|
607a6088d3 | ||
|
|
880c99aa63 | ||
|
|
7ac25358ef | ||
|
|
5733863b93 | ||
|
|
f6a84a88fa | ||
|
|
e963438c5a | ||
|
|
095165ee96 | ||
|
|
199cd59774 | ||
|
|
e17456778f | ||
|
|
71f6bc617e | ||
|
|
6ee4ae508e | ||
|
|
91cccc0c23 | ||
|
|
ab016803dd | ||
|
|
7d8573af12 | ||
|
|
fb06530097 | ||
|
|
5c1551e946 | ||
|
|
20c27b291c | ||
|
|
24f66a3473 | ||
|
|
21bff55b7c | ||
|
|
374fce0249 | ||
|
|
00941472b2 | ||
|
|
527933db24 | ||
|
|
33a421f1e4 | ||
|
|
dd0bbd2405 | ||
|
|
26660e11c7 | ||
|
|
316054ad1c | ||
|
|
29b67962ac | ||
|
|
3a7795589b | ||
|
|
082da2527c | ||
|
|
e784ba3853 | ||
|
|
abd369d062 | ||
|
|
cb7fb82beb | ||
|
|
174616becd | ||
|
|
27fd41d75f | ||
|
|
ea106ef72e | ||
|
|
f2d6a6152e | ||
|
|
129ec851ed | ||
|
|
835963fa6e | ||
|
|
829aaeff2d | ||
|
|
481aba7d7b | ||
|
|
fbcaac89f9 | ||
|
|
589e9f30b3 | ||
|
|
f2103340f3 | ||
|
|
c670c18299 | ||
|
|
7cc9e2d07f | ||
|
|
7dfde364a3 | ||
|
|
7d3288f428 | ||
|
|
e69df6521b | ||
|
|
8ddead4854 | ||
|
|
308b3ec750 | ||
|
|
39e1068a87 | ||
|
|
6d9fcd2588 | ||
|
|
60cb678e3b | ||
|
|
5f8cfa50ba | ||
|
|
b3abc41dbe | ||
|
|
b3b2fee496 | ||
|
|
416fd0fd1b | ||
|
|
142c69dd90 | ||
|
|
74c136905b | ||
|
|
5fc1175aef | ||
|
|
4b91fcf498 | ||
|
|
11323d0727 | ||
|
|
a9a64c9fd0 | ||
|
|
42a0fc9a5e | ||
|
|
e119d9136b | ||
|
|
275fb40ab4 | ||
|
|
7e92850957 | ||
|
|
bb5a1473d1 | ||
|
|
7f970b527c | ||
|
|
dc552c602e | ||
|
|
9827a411b3 | ||
|
|
4a55f93896 | ||
|
|
a07c8d14f6 | ||
|
|
42047fdf11 | ||
|
|
76d7e31981 | ||
|
|
4fd4d4d3c2 | ||
|
|
229f7186a6 | ||
|
|
4b7a1e4ce6 | ||
|
|
871efe653b | ||
|
|
dbb0ef1ce1 | ||
|
|
fcfb077a95 | ||
|
|
bc9344392a | ||
|
|
5b7c898b63 | ||
|
|
58bd926e30 | ||
|
|
c8333f5a56 | ||
|
|
307eaadddf | ||
|
|
dfffb1b88b | ||
|
|
e2eeaf7ec6 | ||
|
|
946e5712ba | ||
|
|
018254974c | ||
|
|
05eda91a2f | ||
|
|
f60f2f81e8 | ||
|
|
4f358e22f4 | ||
|
|
f9d0b1bd70 | ||
|
|
be13dcd454 | ||
|
|
74a536c161 | ||
|
|
444cea4669 | ||
|
|
ea93170959 | ||
|
|
133e31de0b | ||
|
|
aadf7d5e64 | ||
|
|
e4a32acbe2 | ||
|
|
1c77730373 | ||
|
|
e623fcc024 | ||
|
|
585ed5404b | ||
|
|
92aa1ecd5a | ||
|
|
5783a2dd45 | ||
|
|
fba49ba28e | ||
|
|
10d4b98e7f | ||
|
|
e4e712c03a | ||
|
|
2a8af8fda7 | ||
|
|
9165cfdebf | ||
|
|
b5dddd7a2b | ||
|
|
ce3d658377 | ||
|
|
89de83c994 | ||
|
|
52c2c19994 | ||
|
|
53c458c318 | ||
|
|
ba5928776f | ||
|
|
dff7d4ea35 | ||
|
|
0262997451 | ||
|
|
8ba56a8048 | ||
|
|
2fca309e94 | ||
|
|
13b8a3f29f | ||
|
|
a67816468a | ||
|
|
ea46aee44f | ||
|
|
537877a0e2 | ||
|
|
981179a770 | ||
|
|
ff18d39aa8 | ||
|
|
7c40c727a0 | ||
|
|
9f1ff67870 | ||
|
|
b116e6bc2b | ||
|
|
6083218501 | ||
|
|
2533931cf1 | ||
|
|
b93eac3893 | ||
|
|
e374d21d45 | ||
|
|
70d261a729 | ||
|
|
947dbb9e29 | ||
|
|
8a414e55bc | ||
|
|
fd3fc8af62 | ||
|
|
6e802ddc19 | ||
|
|
0211d24577 | ||
|
|
68274646bb | ||
|
|
c0bcf91410 | ||
|
|
a91074b707 | ||
|
|
a6a07779ad | ||
|
|
a6e6aa7445 | ||
|
|
dcdb2940fb | ||
|
|
0ade30cc74 | ||
|
|
bc34f3aa86 | ||
|
|
5940c55e18 | ||
|
|
2eedd69ee9 | ||
|
|
be252c7db9 | ||
|
|
9ebab3e026 | ||
|
|
ad291207d0 | ||
|
|
a7b2af2b92 | ||
|
|
082ed17a0a | ||
|
|
dec5ad5840 | ||
|
|
da67297288 | ||
|
|
83bf2664b0 | ||
|
|
63854b752b | ||
|
|
901f9f76e2 | ||
|
|
b5de2e26eb | ||
|
|
73a116e879 | ||
|
|
9c35fce61e | ||
|
|
87194f6596 | ||
|
|
62d37c9b3d | ||
|
|
b53cb6643b | ||
|
|
fb41783885 | ||
|
|
0bc0bd13d6 | ||
|
|
6d02bfdb42 | ||
|
|
727443483d | ||
|
|
7a0e71c6c2 | ||
|
|
45f5c17260 | ||
|
|
61083cf522 | ||
|
|
afba7c694c | ||
|
|
471899b4d8 | ||
|
|
ec5dbcc816 | ||
|
|
0f69481e2b | ||
|
|
6f3fed496d | ||
|
|
5fd93ea874 | ||
|
|
656af8cadc | ||
|
|
3e521e1c01 | ||
|
|
68cb1e0661 | ||
|
|
35a9f31643 |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: lukas2511
|
||||
custom: ["https://paypal.me/lukas2511", "http://www.amazon.de/registry/wishlist/1TUCFJK35IO4Q"]
|
||||
14
.travis.yml
14
.travis.yml
@@ -1,14 +0,0 @@
|
||||
sudo: false
|
||||
language: shell
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- ngrok
|
||||
|
||||
script:
|
||||
- export CI="true"
|
||||
- ./test.sh
|
||||
94
CHANGELOG
94
CHANGELOG
@@ -1,6 +1,100 @@
|
||||
# Change Log
|
||||
This file contains a log of major changes in dehydrated
|
||||
|
||||
## [x.x.x] - xxxx-xx-xx
|
||||
## Added
|
||||
- New config variable `DEHYDRATED_SUDO_ENV` to allow passing environment variables over sudo calls
|
||||
|
||||
## [0.7.1] - 2022-10-31
|
||||
## Changed
|
||||
- `--force` no longer forces domain name revalidation by default, a new argument `--force-validation` has been added for that
|
||||
- Added support for EC secp521r1 algorithm (works with e.g. zerossl)
|
||||
- `EC PARAMETERS` are no longer written to privkey.pem (didn't seem necessary and was causing issues with various software)
|
||||
|
||||
## Fixed
|
||||
- Requests resulting in `badNonce` errors are now automatically retried (fixes operation with LE staging servers)
|
||||
- Deprecated `egrep` usage has been removed
|
||||
|
||||
## Added
|
||||
- Implemented EC for account keys
|
||||
- Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs)
|
||||
- Implemented RFC 8738 (validating/signing certificates for IP addresses instead of domain names) support (this will not work with most public CAs, if any!)
|
||||
|
||||
## [0.7.0] - 2020-12-10
|
||||
## Added
|
||||
- Support for external account bindings
|
||||
- Special support for ZeroSSL
|
||||
- Support presets for some CAs instead of requiring URLs
|
||||
- Allow requesting preferred chain (`--preferred-chain`)
|
||||
- Added method to show CAs current terms of service (`--display-terms`)
|
||||
- Allow setting path to domains.txt using cli arguments (`--domains-txt`)
|
||||
- Added new cli command `--cleanupdelete` which deletes old files instead of archiving them
|
||||
|
||||
## Fixed
|
||||
- No more silent failures on broken hook-scripts
|
||||
- Better error-handling with KEEP_GOING enabled
|
||||
- Check actual order status instead of assuming it's valid
|
||||
- Don't include keyAuthorization in challenge validation (RFC compliance)
|
||||
|
||||
## Changed
|
||||
- Using EC secp384r1 as default certificate type
|
||||
- Use JSON.sh to parse JSON
|
||||
- Use account URL instead of account ID (RFC compliance)
|
||||
- Dehydrated now has a new home: https://github.com/dehydrated-io/dehydrated
|
||||
- Added `OCSP_FETCH` and `OCSP_DAYS` to per-certificate configurable options
|
||||
- Cleanup now also removes dangling symlinks
|
||||
|
||||
## [0.6.5] - 2019-06-26
|
||||
## Fixed
|
||||
- Fixed broken APIv1 compatibility from last update
|
||||
|
||||
## [0.6.4] - 2019-06-25
|
||||
## Changed
|
||||
- Fetch account ID from Location header instead of account json
|
||||
|
||||
## [0.6.3] - 2019-06-25
|
||||
## Changed
|
||||
- OCSP refresh interval is now configurable
|
||||
- Implemented POST-as-GET
|
||||
- Call exit_hook on errors (with error-message as first parameter)
|
||||
|
||||
## Added
|
||||
- Initial support for tls-alpn-01 validation
|
||||
- New hook: sync_cert (for syncing certificate files to disk, see example hook description)
|
||||
|
||||
## Fixes
|
||||
- Fetch account information after registration to avoid missing account id
|
||||
|
||||
## [0.6.2] - 2018-04-25
|
||||
## Added
|
||||
- New deploy_ocsp hook
|
||||
- Allow account registration with custom key
|
||||
|
||||
## Changed
|
||||
- Don't walk certificate chain for ACMEv2 (certificate contains chain by default)
|
||||
- Improved documentation on wildcards
|
||||
|
||||
## Fixes
|
||||
- Added workaround for compatibility with filesystem ACLs
|
||||
- Close unwanted external file-descriptors
|
||||
- Fixed JSON parsing on force-renewal
|
||||
- Fixed cleanup of challenge files/dns-entries on validation errors
|
||||
- A few more minor fixes
|
||||
|
||||
## [0.6.1] - 2018-03-13
|
||||
## Changed
|
||||
- Use new ACME v2 endpoint by default
|
||||
|
||||
## [0.6.0] - 2018-03-11
|
||||
## Changed
|
||||
- Challenge validation loop has been modified to loop over authorization identifiers instead of altnames (ACMEv2 + wildcard support)
|
||||
- Removed LICENSE parameter from config (terms of service is now acquired directly from the CA directory)
|
||||
|
||||
## Added
|
||||
- Support for ACME v02 (including wildcard certificates!)
|
||||
- New hook: generate_csr (see example hook script for more information)
|
||||
- Calling random hook on startup to make it clear to hook script authors that unknown hooks should just be ignored...
|
||||
|
||||
## [0.5.0] - 2018-01-13
|
||||
## Changed
|
||||
- Certificate chain is now cached (CHAINCACHE)
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Lukas Schauer
|
||||
Copyright (c) 2015-2021 Lukas Schauer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
62
README.md
62
README.md
@@ -1,20 +1,23 @@
|
||||
# dehydrated
|
||||
# dehydrated [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=23P9DSJBTY7C8)
|
||||
|
||||

|
||||

|
||||
|
||||
This is a client for signing certificates with an ACME-server (currently only provided by Let's Encrypt) implemented as a relatively simple bash-script.
|
||||
Dehydrated is a client for signing certificates with an ACME-server (e.g. Let's Encrypt) implemented as a relatively simple (zsh-compatible) bash-script.
|
||||
This client supports both ACME v1 and the new ACME v2 including support for wildcard certificates!
|
||||
|
||||
It uses the `openssl` utility for everything related to actually handling keys and certificates, so you need to have that installed.
|
||||
|
||||
Other dependencies are: cURL, sed, grep, mktemp (all found on almost any system, cURL being the only exception)
|
||||
Other dependencies are: cURL, sed, grep, awk, mktemp (all found pre-installed on almost any system, cURL being the only exception).
|
||||
|
||||
Current features:
|
||||
- Signing of a list of domains
|
||||
- Signing of a CSR
|
||||
- Renewal if a certificate is about to expire or SAN (subdomains) changed
|
||||
- Signing of a list of domains (including wildcard domains!)
|
||||
- Signing of a custom CSR (either standalone or completely automated using hooks!)
|
||||
- Renewal if a certificate is about to expire or defined set of domains changed
|
||||
- Certificate revocation
|
||||
- and lots more..
|
||||
|
||||
Please keep in mind that this software and even the acme-protocol are relatively young and may still have some unresolved issues. Feel free to report any issues you find with this script or contribute by submitting a pull request.
|
||||
Please keep in mind that this software, the ACME-protocol and all supported CA servers out there are relatively young and there might be a few issues. Feel free to report any issues you find with this script or contribute by submitting a pull request,
|
||||
but please check for duplicates first (feel free to comment on those to get things rolling).
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -47,12 +50,15 @@ Default command: help
|
||||
|
||||
Commands:
|
||||
--version (-v) Print version information
|
||||
--display-terms Display current terms of service
|
||||
--register Register account key
|
||||
--account Update account contact information
|
||||
--cron (-c) Sign/renew non-existent/changed/expiring certificates.
|
||||
--signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage)
|
||||
--revoke (-r) path/to/cert.pem Revoke specified certificate
|
||||
--deactivate Deactivate account
|
||||
--cleanup (-gc) Move unused certificate files to archive directory
|
||||
--cleanup-delete (-gcd) Deletes (!) unused certificate files
|
||||
--help (-h) Show help text
|
||||
--env (-e) Output configuration variables for use in other scripts
|
||||
|
||||
@@ -62,47 +68,27 @@ Parameters:
|
||||
--ipv4 (-4) Resolve names to IPv4 addresses only
|
||||
--ipv6 (-6) Resolve names to IPv6 addresses only
|
||||
--domain (-d) domain.tld Use specified domain name(s) instead of domains.txt entry (one certificate!)
|
||||
--ca url/preset Use specified CA URL or preset
|
||||
--alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified)
|
||||
--keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode
|
||||
--force (-x) Force renew of certificate even if it is longer valid than value in RENEW_DAYS
|
||||
--force (-x) Force certificate renewal even if it is not due to expire within RENEW_DAYS
|
||||
--force-validation Force revalidation of domain names (used in combination with --force)
|
||||
--no-lock (-n) Don't use lockfile (potentially dangerous!)
|
||||
--lock-suffix example.com Suffix lockfile name with a string (useful for with -d)
|
||||
--ocsp Sets option in CSR indicating OCSP stapling to be mandatory
|
||||
--privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation)
|
||||
--domains-txt path/to/domains.txt Use specified domains.txt instead of default/configured one
|
||||
--config (-f) path/to/config Use specified config file
|
||||
--hook (-k) path/to/hook.sh Use specified script for hooks
|
||||
--preferred-chain issuer-cn Use alternative certificate chain identified by issuer CN
|
||||
--out (-o) certs/directory Output certificates into the specified directory
|
||||
--challenge (-t) http-01|dns-01 Which challenge should be used? Currently http-01 and dns-01 are supported
|
||||
--alpn alpn-certs/directory Output alpn verification certificates into the specified directory
|
||||
--challenge (-t) http-01|dns-01|tls-alpn-01 Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported
|
||||
--algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
|
||||
```
|
||||
|
||||
## Donate
|
||||
## Chat
|
||||
|
||||
I'm having fun developing dehydrated, but it takes time, and time is money, at least that's what I've been told.
|
||||
|
||||
I will definitively continue developing dehydrated for free, but if you want to support me you can do so using the following ways:
|
||||
|
||||
### PayPal
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=23P9DSJBTY7C8)
|
||||
|
||||
### BitCoin
|
||||
|
||||
Send bitcoins to 12487bHxcrREffTGwUDnoxF1uYxCA7ztKK
|
||||
|
||||
### Server
|
||||
|
||||
I'm still planning on building a bigger testing-suite for dehydrated, it would be really cool to have a big(ish) server running
|
||||
in a datacenter somewhere without having to pay for it... If you are a server provider and can offer me a (dedicated!) machine,
|
||||
please contact me at `donations@dehydrated.de`.
|
||||
|
||||
### Other ways
|
||||
|
||||
I always like to play around with modern(ish) network and computer gear, 10G switches and stuff, modern ARM boards
|
||||
(but please not that Raspberry Pi rubbish), tiny PCs for routing, etc.
|
||||
|
||||
If you have something that seems of value and that you don't need anymore feel free to contact me at
|
||||
`donations@dehydrated.de`.
|
||||
|
||||
Also here is my [Amazon Wishlist](http://www.amazon.de/registry/wishlist/1TUCFJK35IO4Q) :)
|
||||
Dehydrated has an official IRC-channel `#dehydrated` on libera.chat that can be used for general discussion and suggestions.
|
||||
|
||||
The channel can also be accessed with Matrix using the official libera.chat bridge at `#dehydrated:libera.chat`.
|
||||
|
||||
1647
dehydrated
1647
dehydrated
File diff suppressed because it is too large
Load Diff
19
docs/acme-v1.md
Normal file
19
docs/acme-v1.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## (Future) Removal of API version 1
|
||||
|
||||
The ACME API version 1 was never really standardized and was only supported by Let's Encrypt. Even though the protocol specification was public,
|
||||
it wasn't really friendly to be integrated into existing CA systems so initial adoption was basically non-existant.
|
||||
|
||||
ACME version 2 is being designed to overcome these issues by becoming an official IETF standard and supporting a more traditional approach of account
|
||||
and order management in the backend, making it friendlier to integrate into existing systems centered around those. It has since become a semi-stable IETF
|
||||
standard draft which only ever got two breaking changes, Content-Type enforcement and `POST-as-GET`, the latter being announced in October 2018 to be enforced
|
||||
by November 2019. See https://datatracker.ietf.org/wg/acme/documents/ for a better insight into the draft and its changes.
|
||||
|
||||
Next to backend changes that many users won't really care about ACME v2 has all of the features ACME v1 had, but also some additional new features like
|
||||
e.g. support for [wildcard certificates](domains_txt.md#wildcards).
|
||||
|
||||
Since ACME v2 is basically to be considered stable and ACME v1 has no real benefits over v2, there doesn't seem to be much of a reason to keep the old
|
||||
protocol around, but since there actually are a few Certificate Authorities and resellers that implemented the v1 protocol and didn't yet make the change
|
||||
to v2, so dehydrated still supports the old protocol for now.
|
||||
|
||||
Please keep in mind that support for the old ACME protocol version 1 might get removed at any point of bigger inconvenience, e.g. on code changes that
|
||||
would require a lot of work or ugly workarounds to keep both versions supported.
|
||||
@@ -28,4 +28,4 @@ Or when you do have a DNS API, pass the details accordingly to achieve the same
|
||||
|
||||
You can delete the TXT record when called with operation `clean_challenge`, when $2 is also the domain name.
|
||||
|
||||
Here are some examples: [Examples for DNS-01 hooks](https://github.com/lukas2511/dehydrated/wiki/Examples-for-DNS-01-hooks)
|
||||
Here are some examples: [Examples for DNS-01 hooks](https://github.com/dehydrated-io/dehydrated/wiki)
|
||||
|
||||
@@ -1,19 +1,107 @@
|
||||
### domains.txt
|
||||
## domains.txt
|
||||
|
||||
dehydrated uses the file `domains.txt` as configuration for which certificates should be requested.
|
||||
dehydrated uses the file `domains.txt` as configuration for which certificates
|
||||
should be requested.
|
||||
|
||||
The file should have the following format:
|
||||
|
||||
```text
|
||||
example.org
|
||||
example.com www.example.com
|
||||
example.net www.example.net wiki.example.net
|
||||
```
|
||||
|
||||
This states that there are the following certificates:
|
||||
* `example.org` without any *alternative names*
|
||||
* `example.com` with an *alternative name* of `www.example.com`
|
||||
* `example.net` with the *alternative names*: `www.example.net` and
|
||||
`wiki.example.net`
|
||||
|
||||
### Aliases
|
||||
|
||||
You can define an *alias* for your certificate which will (instead of the
|
||||
primary domain) be used as the directory name under your `CERTDIR` and for a
|
||||
per-certificate lookup. This is done using the `>` character. This allows
|
||||
multiple certificates with identical sets of domains but different
|
||||
configuration to exist.
|
||||
|
||||
Here is an example of using an *alias* called `certalias` for creating the
|
||||
certificate for `example.net` with *alternative names* `www.example.net` and
|
||||
`wiki.example.net`. The certificate will be stored in the directory `certalias`
|
||||
under your `CERTDIR`.
|
||||
|
||||
```text
|
||||
example.net www.example.net wiki.example.net > certalias
|
||||
```
|
||||
|
||||
This states that there should be two certificates `example.com` and `example.net`,
|
||||
with the other domains in the corresponding line being their alternative names.
|
||||
This allows to set per certificates options. The options you can change are
|
||||
explained in [Per Certificate Config](per-certificate-config.md).
|
||||
|
||||
You can define an alias for your certificate which will (instead of the primary domain) be
|
||||
used as directory name under your certdir and for a per-certificate lookup.
|
||||
This allows multiple certificates with identical sets of domains but different configuration
|
||||
to exist.
|
||||
If you want to create different certificate types for the same domain
|
||||
you can use:
|
||||
|
||||
```text
|
||||
*.service.example.org service.example.org > star_service_example_org_rsa
|
||||
*.service.example.org service.example.org > star_service_example_org_ecdsa
|
||||
```
|
||||
|
||||
Then add a config file `certs/star_service_example_org_rsa/config` with
|
||||
the value
|
||||
|
||||
```
|
||||
KEY_ALGO="rsa"
|
||||
```
|
||||
|
||||
or respectively
|
||||
|
||||
```
|
||||
KEY_ALGO="ecdsa"
|
||||
```
|
||||
|
||||
### Wildcards
|
||||
|
||||
Support for wildcards was added by the ACME v2 protocol.
|
||||
|
||||
Certificates with a wildcard domain as the first (or only) name require an
|
||||
*alias* to be set. *Aliases* can't start with `*.`.
|
||||
|
||||
For example to create the wildcard for `*.service.example.com` your
|
||||
`domains.txt` could use the *alias* method like this:
|
||||
|
||||
```text
|
||||
*.service.example.com > star_service_example_com
|
||||
```
|
||||
|
||||
This creates a wildcard certificate for only `*.service.example.com` and will
|
||||
store it in the directory `star_service_example_com` under your `CERTDIR`. As a
|
||||
note this certificate will **NOT** be valid for `service.example.com` but only
|
||||
for `*.service.example.com`. So it would, for example, be valid for
|
||||
`foo.service.example.com`.
|
||||
|
||||
|
||||
Another way to create it is using *alternative names*. For example your
|
||||
`domains.txt` could do this:
|
||||
|
||||
```text
|
||||
service.example.com *.service.example.com
|
||||
eggs.example.com *.ham.example.com
|
||||
```
|
||||
|
||||
This creates two certificates one for `service.example.com` with an
|
||||
*alternative name* of `*.service.example.com` and a second certificate for
|
||||
`eggs.example.com` with an *alternative name* of `*.ham.example.com`.
|
||||
|
||||
**Note:** The first certificate is valid for both `service.example.com` and for
|
||||
`*.service.example.com` which can be a useful way to create wildcard
|
||||
certificates.
|
||||
|
||||
### Drop-in directory
|
||||
|
||||
If a directory named `domains.txt.d` exists in the same location as
|
||||
`domains.txt`, the contents of `*.txt` files in that directory are appended to
|
||||
the list of domains, in alphabetical order of the filenames. This is useful for
|
||||
automation, as it doesn't require editing an existing file to add new domains.
|
||||
|
||||
Warning: Behaviour of this might change as the naming between `domains.txt.d`
|
||||
and the `DOMAINS_D` config variable (which is used for per-certificate
|
||||
configuration) is a bit confusing.
|
||||
|
||||
@@ -10,27 +10,33 @@
|
||||
# Default values of this config are in comments #
|
||||
########################################################
|
||||
|
||||
# Which user should dehydrated run as? This will be implictly enforced when running as root
|
||||
# Which user should dehydrated run as? This will be implicitly enforced when running as root
|
||||
#DEHYDRATED_USER=
|
||||
|
||||
# Which group should dehydrated run as? This will be implictly enforced when running as root
|
||||
# Which group should dehydrated run as? This will be implicitly enforced when running as root
|
||||
#DEHYDRATED_GROUP=
|
||||
|
||||
# Should dehydrated pass environment variables over sudo?
|
||||
#DEHYDRATED_SUDO_ENV="no"
|
||||
|
||||
# Resolve names to addresses of IP version only. (curl)
|
||||
# supported values: 4, 6
|
||||
# default: <unset>
|
||||
#IP_VERSION=
|
||||
|
||||
# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
|
||||
#CA="https://acme-v01.api.letsencrypt.org/directory"
|
||||
# URL to certificate authority or internal preset
|
||||
# Presets: letsencrypt, letsencrypt-test, zerossl, buypass, buypass-test
|
||||
# default: letsencrypt
|
||||
#CA="letsencrypt"
|
||||
|
||||
# Path to certificate authority license terms redirect (default: https://acme-v01.api.letsencrypt.org/terms)
|
||||
#CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"
|
||||
# Path to old certificate authority
|
||||
# Set this value to your old CA value when upgrading from ACMEv1 to ACMEv2 under a different endpoint.
|
||||
# If dehydrated detects an account-key for the old CA it will automatically reuse that key
|
||||
# instead of registering a new one.
|
||||
# default: https://acme-v01.api.letsencrypt.org/directory
|
||||
#OLDCA="https://acme-v01.api.letsencrypt.org/directory"
|
||||
|
||||
# Path to license agreement (default: <unset>)
|
||||
#LICENSE=""
|
||||
|
||||
# Which challenge should be used? Currently http-01 and dns-01 are supported
|
||||
# Which challenge should be used? Currently http-01, dns-01 and tls-alpn-01 are supported
|
||||
#CHALLENGETYPE="http-01"
|
||||
|
||||
# Path to a directory containing additional config files, allowing to override
|
||||
@@ -39,6 +45,11 @@
|
||||
# default: <unset>
|
||||
#CONFIG_D=
|
||||
|
||||
# Directory for per-domain configuration files.
|
||||
# If not set, per-domain configurations are sourced from each certificates output directory.
|
||||
# default: <unset>
|
||||
#DOMAINS_D=
|
||||
|
||||
# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
|
||||
#BASEDIR=$SCRIPTDIR
|
||||
|
||||
@@ -48,6 +59,9 @@
|
||||
# Output directory for generated certificates
|
||||
#CERTDIR="${BASEDIR}/certs"
|
||||
|
||||
# Output directory for alpn verification certificates
|
||||
#ALPNCERTDIR="${BASEDIR}/alpn-certs"
|
||||
|
||||
# Directory for account keys and registration information
|
||||
#ACCOUNTDIR="${BASEDIR}/accounts"
|
||||
|
||||
@@ -91,7 +105,7 @@
|
||||
#PRIVATE_KEY_ROLLOVER="no"
|
||||
|
||||
# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
|
||||
#KEY_ALGO=rsa
|
||||
#KEY_ALGO=secp384r1
|
||||
|
||||
# E-mail to use during the registration (default: <unset>)
|
||||
#CONTACT_EMAIL=
|
||||
@@ -105,8 +119,17 @@
|
||||
# Fetch OCSP responses (default: no)
|
||||
#OCSP_FETCH="no"
|
||||
|
||||
# OCSP refresh interval (default: 5 days)
|
||||
#OCSP_DAYS=5
|
||||
|
||||
# Issuer chain cache directory (default: $BASEDIR/chains)
|
||||
#CHAINCACHE="${BASEDIR}/chains"
|
||||
|
||||
# Automatic cleanup (default: no)
|
||||
#AUTO_CLEANUP="no"
|
||||
|
||||
# ACME API version (default: auto)
|
||||
#API=auto
|
||||
|
||||
# Preferred issuer chain (default: <unset> -> uses default chain)
|
||||
#PREFERRED_CHAIN=
|
||||
|
||||
@@ -1,2 +1,39 @@
|
||||
# Create certificate for 'example.org' with an alternative name of
|
||||
# 'www.example.org'. It will be stored in the directory ${CERT_DIR}/example.org
|
||||
example.org www.example.org
|
||||
|
||||
# Create certificate for 'example.com' with alternative names of
|
||||
# 'www.example.com' & 'wiki.example.com'. It will be stored in the directory
|
||||
# ${CERT_DIR}/example.com
|
||||
example.com www.example.com wiki.example.com
|
||||
|
||||
# Using the alias 'certalias' create certificate for 'example.net' with
|
||||
# alternate name 'www.example.net' and store it in the directory
|
||||
# ${CERTDIR}/certalias
|
||||
example.net www.example.net > certalias
|
||||
|
||||
# Using the alias 'service_example_com' create a wildcard certificate for
|
||||
# '*.service.example.com' and store it in the directory
|
||||
# ${CERTDIR}/service_example_com
|
||||
# NOTE: It is NOT a certificate for 'service.example.com'
|
||||
*.service.example.com > service_example_com
|
||||
|
||||
# Using the alias 'star_service_example_org' create a wildcard certificate for
|
||||
# '*.service.example.org' with an alternative name of `service.example.org'
|
||||
# and store it in the directory ${CERTDIR}/star_service_example_org
|
||||
# NOTE: It is a certificate for 'service.example.org'
|
||||
*.service.example.org service.example.org > star_service_example_org
|
||||
|
||||
# Optionally you can also append the certificate algorithm here to create
|
||||
# multiple certificate types for the same domain.
|
||||
#
|
||||
# This allows to set per certificates options. How to do this is
|
||||
# explained in [domains.txt documentation](domains_txt.md).
|
||||
#
|
||||
*.service.example.org service.example.org > star_service_example_org_rsa
|
||||
*.service.example.org service.example.org > star_service_example_org_ecdsa
|
||||
|
||||
# Create a certificate for 'service.example.net' with an alternative name of
|
||||
# '*.service.example.net' (which is a wildcard domain) and store it in the
|
||||
# directory ${CERTDIR}/service.example.net
|
||||
service.example.net *.service.example.net
|
||||
|
||||
@@ -1,108 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
deploy_challenge() {
|
||||
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
|
||||
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
|
||||
|
||||
# This hook is called once for every domain that needs to be
|
||||
# validated, including any alternative names you may have listed.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The domain name (CN or subject alternative name) being
|
||||
# validated.
|
||||
# - TOKEN_FILENAME
|
||||
# The name of the file containing the token to be served for HTTP
|
||||
# validation. Should be served by your web server as
|
||||
# /.well-known/acme-challenge/${TOKEN_FILENAME}.
|
||||
# - TOKEN_VALUE
|
||||
# The token value that needs to be served for validation. For DNS
|
||||
# validation, this is what you want to put in the _acme-challenge
|
||||
# TXT record. For HTTP validation it is the value that is expected
|
||||
# be found in the $TOKEN_FILENAME file.
|
||||
# This hook is called once for every domain that needs to be
|
||||
# validated, including any alternative names you may have listed.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The domain name (CN or subject alternative name) being
|
||||
# validated.
|
||||
# - TOKEN_FILENAME
|
||||
# The name of the file containing the token to be served for HTTP
|
||||
# validation. Should be served by your web server as
|
||||
# /.well-known/acme-challenge/${TOKEN_FILENAME}.
|
||||
# - TOKEN_VALUE
|
||||
# The token value that needs to be served for validation. For DNS
|
||||
# validation, this is what you want to put in the _acme-challenge
|
||||
# TXT record. For HTTP validation it is the value that is expected
|
||||
# be found in the $TOKEN_FILENAME file.
|
||||
|
||||
# Simple example: Use nsupdate with local named
|
||||
# printf 'server 127.0.0.1\nupdate add _acme-challenge.%s 300 IN TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key
|
||||
}
|
||||
|
||||
clean_challenge() {
|
||||
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
|
||||
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
|
||||
|
||||
# This hook is called after attempting to validate each domain,
|
||||
# whether or not validation was successful. Here you can delete
|
||||
# files or DNS records that are no longer needed.
|
||||
#
|
||||
# The parameters are the same as for deploy_challenge.
|
||||
# This hook is called after attempting to validate each domain,
|
||||
# whether or not validation was successful. Here you can delete
|
||||
# files or DNS records that are no longer needed.
|
||||
#
|
||||
# The parameters are the same as for deploy_challenge.
|
||||
|
||||
# Simple example: Use nsupdate with local named
|
||||
# printf 'server 127.0.0.1\nupdate delete _acme-challenge.%s TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key
|
||||
}
|
||||
|
||||
sync_cert() {
|
||||
local KEYFILE="${1}" CERTFILE="${2}" FULLCHAINFILE="${3}" CHAINFILE="${4}" REQUESTFILE="${5}"
|
||||
|
||||
# This hook is called after the certificates have been created but before
|
||||
# they are symlinked. This allows you to sync the files to disk to prevent
|
||||
# creating a symlink to empty files on unexpected system crashes.
|
||||
#
|
||||
# This hook is not intended to be used for further processing of certificate
|
||||
# files, see deploy_cert for that.
|
||||
#
|
||||
# Parameters:
|
||||
# - KEYFILE
|
||||
# The path of the file containing the private key.
|
||||
# - CERTFILE
|
||||
# The path of the file containing the signed certificate.
|
||||
# - FULLCHAINFILE
|
||||
# The path of the file containing the full certificate chain.
|
||||
# - CHAINFILE
|
||||
# The path of the file containing the intermediate certificate(s).
|
||||
# - REQUESTFILE
|
||||
# The path of the file containing the certificate signing request.
|
||||
|
||||
# Simple example: sync the files before symlinking them
|
||||
# sync "${KEYFILE}" "${CERTFILE}" "${FULLCHAINFILE}" "${CHAINFILE}" "${REQUESTFILE}"
|
||||
}
|
||||
|
||||
deploy_cert() {
|
||||
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
|
||||
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
|
||||
|
||||
# This hook is called once for each certificate that has been
|
||||
# produced. Here you might, for instance, copy your new certificates
|
||||
# to service-specific locations and reload the service.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - KEYFILE
|
||||
# The path of the file containing the private key.
|
||||
# - CERTFILE
|
||||
# The path of the file containing the signed certificate.
|
||||
# - FULLCHAINFILE
|
||||
# The path of the file containing the full certificate chain.
|
||||
# - CHAINFILE
|
||||
# The path of the file containing the intermediate certificate(s).
|
||||
# - TIMESTAMP
|
||||
# Timestamp when the specified certificate was created.
|
||||
# This hook is called once for each certificate that has been
|
||||
# produced. Here you might, for instance, copy your new certificates
|
||||
# to service-specific locations and reload the service.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - KEYFILE
|
||||
# The path of the file containing the private key.
|
||||
# - CERTFILE
|
||||
# The path of the file containing the signed certificate.
|
||||
# - FULLCHAINFILE
|
||||
# The path of the file containing the full certificate chain.
|
||||
# - CHAINFILE
|
||||
# The path of the file containing the intermediate certificate(s).
|
||||
# - TIMESTAMP
|
||||
# Timestamp when the specified certificate was created.
|
||||
|
||||
# Simple example: Copy file to nginx config
|
||||
# cp "${KEYFILE}" "${FULLCHAINFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl
|
||||
# systemctl reload nginx
|
||||
}
|
||||
|
||||
unchanged_cert() {
|
||||
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
|
||||
deploy_ocsp() {
|
||||
local DOMAIN="${1}" OCSPFILE="${2}" TIMESTAMP="${3}"
|
||||
|
||||
# This hook is called once for each certificate that is still
|
||||
# valid and therefore wasn't reissued.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - KEYFILE
|
||||
# The path of the file containing the private key.
|
||||
# - CERTFILE
|
||||
# The path of the file containing the signed certificate.
|
||||
# - FULLCHAINFILE
|
||||
# The path of the file containing the full certificate chain.
|
||||
# - CHAINFILE
|
||||
# The path of the file containing the intermediate certificate(s).
|
||||
# This hook is called once for each updated ocsp stapling file that has
|
||||
# been produced. Here you might, for instance, copy your new ocsp stapling
|
||||
# files to service-specific locations and reload the service.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - OCSPFILE
|
||||
# The path of the ocsp stapling file
|
||||
# - TIMESTAMP
|
||||
# Timestamp when the specified ocsp stapling file was created.
|
||||
|
||||
# Simple example: Copy file to nginx config
|
||||
# cp "${OCSPFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl
|
||||
# systemctl reload nginx
|
||||
}
|
||||
|
||||
|
||||
unchanged_cert() {
|
||||
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
|
||||
|
||||
# This hook is called once for each certificate that is still
|
||||
# valid and therefore wasn't reissued.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - KEYFILE
|
||||
# The path of the file containing the private key.
|
||||
# - CERTFILE
|
||||
# The path of the file containing the signed certificate.
|
||||
# - FULLCHAINFILE
|
||||
# The path of the file containing the full certificate chain.
|
||||
# - CHAINFILE
|
||||
# The path of the file containing the intermediate certificate(s).
|
||||
}
|
||||
|
||||
invalid_challenge() {
|
||||
local DOMAIN="${1}" RESPONSE="${2}"
|
||||
local DOMAIN="${1}" RESPONSE="${2}"
|
||||
|
||||
# This hook is called if the challenge response has failed, so domain
|
||||
# owners can be aware and act accordingly.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - RESPONSE
|
||||
# The response that the verification server returned
|
||||
# This hook is called if the challenge response has failed, so domain
|
||||
# owners can be aware and act accordingly.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain name, i.e. the certificate common
|
||||
# name (CN).
|
||||
# - RESPONSE
|
||||
# The response that the verification server returned
|
||||
|
||||
# Simple example: Send mail to root
|
||||
# printf "Subject: Validation of ${DOMAIN} failed!\n\nOh noez!" | sendmail root
|
||||
}
|
||||
|
||||
request_failure() {
|
||||
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
|
||||
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}"
|
||||
|
||||
# This hook is called when an HTTP request fails (e.g., when the ACME
|
||||
# server is busy, returns an error, etc). It will be called upon any
|
||||
# response code that does not start with '2'. Useful to alert admins
|
||||
# about problems with requests.
|
||||
#
|
||||
# Parameters:
|
||||
# - STATUSCODE
|
||||
# The HTML status code that originated the error.
|
||||
# - REASON
|
||||
# The specified reason for the error.
|
||||
# - REQTYPE
|
||||
# The kind of request that was made (GET, POST...)
|
||||
# This hook is called when an HTTP request fails (e.g., when the ACME
|
||||
# server is busy, returns an error, etc). It will be called upon any
|
||||
# response code that does not start with '2'. Useful to alert admins
|
||||
# about problems with requests.
|
||||
#
|
||||
# Parameters:
|
||||
# - STATUSCODE
|
||||
# The HTML status code that originated the error.
|
||||
# - REASON
|
||||
# The specified reason for the error.
|
||||
# - REQTYPE
|
||||
# The kind of request that was made (GET, POST...)
|
||||
# - HEADERS
|
||||
# HTTP headers returned by the CA
|
||||
|
||||
# Simple example: Send mail to root
|
||||
# printf "Subject: HTTP request failed failed!\n\nA http request failed with status ${STATUSCODE}!" | sendmail root
|
||||
}
|
||||
|
||||
generate_csr() {
|
||||
local DOMAIN="${1}" CERTDIR="${2}" ALTNAMES="${3}"
|
||||
|
||||
# This hook is called before any certificate signing operation takes place.
|
||||
# It can be used to generate or fetch a certificate signing request with external
|
||||
# tools.
|
||||
# The output should be just the certificate signing request formatted as PEM.
|
||||
#
|
||||
# Parameters:
|
||||
# - DOMAIN
|
||||
# The primary domain as specified in domains.txt. This does not need to
|
||||
# match with the domains in the CSR, it's basically just the directory name.
|
||||
# - CERTDIR
|
||||
# Certificate output directory for this particular certificate. Can be used
|
||||
# for storing additional files.
|
||||
# - ALTNAMES
|
||||
# All domain names for the current certificate as specified in domains.txt.
|
||||
# Again, this doesn't need to match with the CSR, it's just there for convenience.
|
||||
|
||||
# Simple example: Look for pre-generated CSRs
|
||||
# if [ -e "${CERTDIR}/pre-generated.csr" ]; then
|
||||
# cat "${CERTDIR}/pre-generated.csr"
|
||||
# fi
|
||||
}
|
||||
|
||||
startup_hook() {
|
||||
@@ -113,13 +204,17 @@ startup_hook() {
|
||||
}
|
||||
|
||||
exit_hook() {
|
||||
local ERROR="${1:-}"
|
||||
|
||||
# This hook is called at the end of the cron command and can be used to
|
||||
# do some final (cleanup or other) tasks.
|
||||
|
||||
:
|
||||
#
|
||||
# Parameters:
|
||||
# - ERROR
|
||||
# Contains error message if dehydrated exits with error
|
||||
}
|
||||
|
||||
HANDLER="$1"; shift
|
||||
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|startup_hook|exit_hook)$ ]]; then
|
||||
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|sync_cert|deploy_cert|deploy_ocsp|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
|
||||
"$HANDLER" "$@"
|
||||
fi
|
||||
|
||||
@@ -60,4 +60,3 @@ HOOK: clean_challenge lukas.im blablabla blablabla.supersecure www.lukas.im blub
|
||||
HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehydrated/certs/lukas.im/cert.pem /etc/dehydrated/certs/lukas.im/fullchain.pem /etc/dehydrated/certs/lukas.im/chain.pem 1460152408
|
||||
+ Done!
|
||||
```
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Import
|
||||
|
||||
If you want to import existing keys from the official letsencrypt client have a look at [Import from official letsencrypt client](https://github.com/lukas2511/dehydrated/wiki/Import-from-official-letsencrypt-client).
|
||||
BIN
docs/logo.jpg
BIN
docs/logo.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB |
BIN
docs/logo.png
Normal file
BIN
docs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -1,4 +1,4 @@
|
||||
.TH DEHYDRATED 1 2017-09-20 "Dehydrated ACME Client"
|
||||
.TH DEHYDRATED 1 2018-01-13 "Dehydrated ACME Client"
|
||||
.SH NAME
|
||||
dehydrated \- ACME client implemented as a shell-script
|
||||
.SH SYNOPSIS
|
||||
@@ -7,8 +7,8 @@ dehydrated \- ACME client implemented as a shell-script
|
||||
[\fBargument\fR [\fBargument\fR]]
|
||||
.IR ...
|
||||
.SH DESCRIPTION
|
||||
A client for ACME-based Certificate Authorities, such as LetsEncrypt. It
|
||||
allows to request and obtain TLS certificates from an ACME-based
|
||||
A client for ACME-based Certificate Authorities, such as LetsEncrypt. It can
|
||||
be used to request and obtain TLS certificates from an ACME-based
|
||||
certificate authority.
|
||||
|
||||
Before any certificates can be requested, Dehydrated needs
|
||||
@@ -20,8 +20,8 @@ Dehydrated will notify if no account is configured. Run with \fB--register
|
||||
|
||||
Next, all domain names must be provided in domains.txt. The format is line
|
||||
based: If the file contains two lines "example.com" and "example.net",
|
||||
Dehydrated will request two certificate, one for "example.com" and the other
|
||||
for "example.net". A single line while "example.com example.net" will request a
|
||||
dehydrated will request two certificate, one for "example.com" and the other
|
||||
for "example.net". A single line containing "example.com example.net" will request a
|
||||
single certificate valid for both "example.net" and "example.com" through the \fISubject
|
||||
Alternative Name\fR (SAN) field.
|
||||
|
||||
@@ -106,7 +106,7 @@ Keep going after encountering an error while creating/renewing multiple
|
||||
certificates in cron mode
|
||||
.TP
|
||||
.BR \-\-force ", " \-x
|
||||
Force renew of certificate even if it is longer valid than value in RENEW_DAYS
|
||||
Force certificate renewal even if it is not due to expire within RENEW_DAYS
|
||||
.TP
|
||||
.BR \-\-no\-lock ", " \-n
|
||||
Don't use lockfile (potentially dangerous!)
|
||||
@@ -145,7 +145,7 @@ Please report any bugs that you may encounter at the project web site
|
||||
Dehydrated was written by Lukas Schauer. This man page was contributed by
|
||||
Daniel Molkentin.
|
||||
.SH COPYRIGHT
|
||||
Copyright 20015-2017 by Lukas Schauer and the respective contributors.
|
||||
Copyright 2015-2018 by Lukas Schauer and the respective contributors.
|
||||
Provided under the MIT License. See the LICENSE file that accompanies the
|
||||
distribution for licensing information.
|
||||
.SH SEE ALSO
|
||||
@@ -7,15 +7,19 @@ To use this feature create a `config` file in the certificates output directory
|
||||
Currently supported options:
|
||||
|
||||
- PRIVATE_KEY_RENEW
|
||||
- PRIVATE_KEY_ROLLOVER
|
||||
- KEY_ALGO
|
||||
- KEYSIZE
|
||||
- OCSP_MUST_STAPLE
|
||||
- OCSP_FETCH
|
||||
- OCSP_DAYS
|
||||
- CHALLENGETYPE
|
||||
- HOOK
|
||||
- HOOK_CHAIN
|
||||
- WELLKNOWN
|
||||
- OPENSSL_CNF
|
||||
- RENEW_DAYS
|
||||
- PREFERRED_CHAIN
|
||||
|
||||
## DOMAINS_D
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ you will quickly hit these limits and find yourself locked out.
|
||||
To avoid this, please set the CA property to the Let’s Encrypt staging server URL in your config file:
|
||||
|
||||
```bash
|
||||
CA="https://acme-staging.api.letsencrypt.org/directory"
|
||||
CA_TERMS="https://acme-staging.api.letsencrypt.org/terms"
|
||||
CA="https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
```
|
||||
|
||||
Alternatively you can define the CA using the CLI argument `--ca letsencrypt-test` (`letsencrypt-test` is an integrated preset-CA corresponding to the URL above).
|
||||
|
||||
124
docs/tls-alpn.md
Normal file
124
docs/tls-alpn.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# TLS-ALPN-01
|
||||
|
||||
With `tls-alpn-01`-type verification Let's Encrypt (or the ACME-protocol in general) is checking if you are in control of a domain by accessing
|
||||
your webserver using a custom ALPN and expecting a specially crafted TLS certificate containing a verification token.
|
||||
It will do that for any (sub-)domain you want to sign a certificate for.
|
||||
|
||||
Dehydrated generates the required verification certificates, but the delivery is out of its scope.
|
||||
|
||||
### Example lighttpd config
|
||||
|
||||
lighttpd can be configured to recognize ALPN `acme-tls/1` and to respond to such
|
||||
requests using the specially crafted TLS certificates generated by dehydrated.
|
||||
Configure lighttpd and dehydrated to use the same path for these certificates.
|
||||
(Be sure to allow read access to the user account under which the lighttpd
|
||||
server is running.) `mkdir -p /etc/dehydrated/alpn-certs`
|
||||
|
||||
lighttpd.conf:
|
||||
```
|
||||
ssl.acme-tls-1 = "/etc/dehydrated/alpn-certs"
|
||||
```
|
||||
|
||||
When renewing certificates, specify `-t tls-alpn-01` and `--alpn /etc/dehydrated/alpn-certs` to dehydrated, e.g.
|
||||
```
|
||||
dehydrated -t tls-alpn-01 --alpn /etc/dehydrated/alpn-certs -c --out /etc/lighttpd/certs -d www.example.com
|
||||
# gracefully reload lighttpd to use the new certificates by sending lighttpd pid SIGUSR1
|
||||
systemctl reload lighttpd
|
||||
```
|
||||
|
||||
### Example nginx config
|
||||
|
||||
On an nginx tcp load-balancer you can use the `ssl_preread` module to map a different port for acme-tls
|
||||
requests than for e.g. HTTP/2 or HTTP/1.1 requests.
|
||||
|
||||
Your config should look something like this:
|
||||
|
||||
```nginx
|
||||
stream {
|
||||
map $ssl_preread_alpn_protocols $tls_port {
|
||||
~\bacme-tls/1\b 10443;
|
||||
default 443;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
listen [::]:443;
|
||||
proxy_pass 10.13.37.42:$tls_port;
|
||||
ssl_preread on;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That way https requests are forwarded to port 443 on the backend server, and acme-tls/1 requests are
|
||||
forwarded to port 10443.
|
||||
|
||||
In the future nginx might support internal routing based on custom ALPNs, but for now you'll have to
|
||||
use a custom responder for the alpn verification certificates (see below).
|
||||
|
||||
### Example responder
|
||||
|
||||
I hacked together a simple responder in Python, it might not be the best, but it works for me:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import ssl
|
||||
import socketserver
|
||||
import threading
|
||||
import re
|
||||
import os
|
||||
|
||||
ALPNDIR="/etc/dehydrated/alpn-certs"
|
||||
PROXY_PROTOCOL=False
|
||||
|
||||
FALLBACK_CERTIFICATE="/etc/ssl/certs/ssl-cert-snakeoil.pem"
|
||||
FALLBACK_KEY="/etc/ssl/private/ssl-cert-snakeoil.key"
|
||||
|
||||
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
|
||||
def create_context(self, certfile, keyfile, first=False):
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.set_ciphers('ECDHE+AESGCM')
|
||||
ssl_context.set_alpn_protocols(["acme-tls/1"])
|
||||
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
||||
if first:
|
||||
ssl_context.set_servername_callback(self.load_certificate)
|
||||
ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile)
|
||||
return ssl_context
|
||||
|
||||
def load_certificate(self, sslsocket, sni_name, sslcontext):
|
||||
print("Got request for %s" % sni_name)
|
||||
if not re.match(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$', sni_name):
|
||||
return
|
||||
|
||||
certfile = os.path.join(ALPNDIR, "%s.crt.pem" % sni_name)
|
||||
keyfile = os.path.join(ALPNDIR, "%s.key.pem" % sni_name)
|
||||
|
||||
if not os.path.exists(certfile) or not os.path.exists(keyfile):
|
||||
return
|
||||
|
||||
sslsocket.context = self.create_context(certfile, keyfile)
|
||||
|
||||
def handle(self):
|
||||
if PROXY_PROTOCOL:
|
||||
buf = b""
|
||||
while b"\r\n" not in buf:
|
||||
buf += self.request.recv(1)
|
||||
|
||||
ssl_context = self.create_context(FALLBACK_CERTIFICATE, FALLBACK_KEY, True)
|
||||
newsock = ssl_context.wrap_socket(self.request, server_side=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
HOST, PORT = "0.0.0.0", 10443
|
||||
|
||||
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler, bind_and_activate=False)
|
||||
server.allow_reuse_address = True
|
||||
try:
|
||||
server.server_bind()
|
||||
server.server_activate()
|
||||
server.serve_forever()
|
||||
except:
|
||||
server.shutdown()
|
||||
```
|
||||
@@ -11,12 +11,6 @@ the current workaround is to move `private_key.pem` (and, if you care, `private_
|
||||
|
||||
This will hopefully be fixed in the future.
|
||||
|
||||
## "Provided agreement URL [LICENSE1] does not match current agreement URL [LICENSE2]"
|
||||
|
||||
Set LICENSE in your config to the value in place of "LICENSE2".
|
||||
|
||||
LICENSE1 and LICENSE2 are just placeholders for the real values in this troubleshooting document!
|
||||
|
||||
## "Error creating new cert :: Too many certificates already issued for: [...]"
|
||||
|
||||
This is not an issue with dehydrated but an API limit with boulder (the ACME server).
|
||||
@@ -36,3 +30,23 @@ If you are using HTTP validation make sure that the path you have configured wit
|
||||
To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`. Note that if you have an IPv6 address, the challenge connection will be on IPv6. Be sure that you test HTTP connections on both IPv4 and IPv6. Checking the test file in your browser is often not sufficient because the browser just fails over to IPv4.
|
||||
|
||||
If you get any error you'll have to fix your web server configuration.
|
||||
|
||||
## DNS invalid challenge since dehydrated 0.6.0 / Why are DNS challenges deployed first and verified later?
|
||||
|
||||
Since Let's Encrypt (and in general the ACMEv2 protocol) now supports wildcard domains there is a situation where DNS caching can become a problem.
|
||||
If somebody wants to validate a certificate with `example.org` and `*.example.org` there are two tokens that have to be deployed on `_acme-challenge.example.org`.
|
||||
|
||||
If dehydrated would deploy and verify each token on its own the CA would cache the first token on `_acme-challenge.example.org` and the next challenge would simply fail.
|
||||
Let's Encrypt uses your DNS TTL with a max limit of 5 minutes, but this doesn't seem to be part of the ACME protocol, just some LE specific configuration,
|
||||
so with other CAs and certain DNS providers who don't allow low TTLs this could potentially take hours.
|
||||
|
||||
Since dehydrated now deploys all challenges first that no longer is a problem. The CA will query and cache both challenges, and both authorizations can be validated.
|
||||
Some hook-scripts were written in a way that erases the old TXT record rather than adding a new entry, those should be (and many of them already have been) fixed.
|
||||
|
||||
There are certain DNS providers which really only allow one TXT record on a domain. This is really odd and you should probably contact your DNS provider and ask them
|
||||
to fix this.
|
||||
|
||||
If for whatever reason you can't switch DNS providers and your DNS provider only supports one TXT record and doesn't want to fix that you could try splitting your
|
||||
certificate into multiple certificates and add a sleep in the `deploy_cert` hook.
|
||||
If you can't do that or really don't want to please leave a comment on https://github.com/lukas2511/dehydrated/issues/554,
|
||||
if many people are having this unfixable problem I might try to implement a workaround.
|
||||
|
||||
@@ -24,7 +24,7 @@ With Nginx you'll need to add this to any of your `server`/VHost config blocks:
|
||||
```nginx
|
||||
server {
|
||||
[...]
|
||||
location /.well-known/acme-challenge {
|
||||
location ^~ /.well-known/acme-challenge {
|
||||
alias /var/www/dehydrated;
|
||||
}
|
||||
[...]
|
||||
|
||||
266
test.sh
266
test.sh
@@ -1,266 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fail early
|
||||
set -eu -o pipefail
|
||||
|
||||
# Check if running in CI environment
|
||||
if [[ ! "${CI:-false}" == "true" ]]; then
|
||||
echo "ERROR: Not running in CI environment!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_TEST() {
|
||||
echo
|
||||
echo "${1} "
|
||||
}
|
||||
_SUBTEST() {
|
||||
echo -n " + ${1} "
|
||||
}
|
||||
_PASS() {
|
||||
echo -e "[\u001B[32mPASS\u001B[0m]"
|
||||
}
|
||||
_FAIL() {
|
||||
echo -e "[\u001B[31mFAIL\u001B[0m]"
|
||||
echo
|
||||
echo "Problem: ${@}"
|
||||
echo
|
||||
echo "STDOUT:"
|
||||
cat tmplog
|
||||
echo
|
||||
echo "STDERR:"
|
||||
cat errorlog
|
||||
exit 1
|
||||
}
|
||||
_CHECK_FILE() {
|
||||
_SUBTEST "Checking if file '${1}' exists..."
|
||||
if [[ -e "${1}" ]]; then
|
||||
_PASS
|
||||
else
|
||||
_FAIL "Missing file: ${1}"
|
||||
fi
|
||||
}
|
||||
_CHECK_LOG() {
|
||||
_SUBTEST "Checking if log contains '${1}'..."
|
||||
if grep -- "${1}" tmplog > /dev/null; then
|
||||
_PASS
|
||||
else
|
||||
_FAIL "Missing in log: ${1}"
|
||||
fi
|
||||
}
|
||||
_CHECK_NOT_LOG() {
|
||||
_SUBTEST "Checking if log doesn't contain '${1}'..."
|
||||
if grep -- "${1}" tmplog > /dev/null; then
|
||||
_FAIL "Found in log: ${1}"
|
||||
else
|
||||
_PASS
|
||||
fi
|
||||
}
|
||||
_CHECK_ERRORLOG() {
|
||||
_SUBTEST "Checking if errorlog is empty..."
|
||||
if [[ -z "$(cat errorlog)" ]]; then
|
||||
_PASS
|
||||
else
|
||||
_FAIL "Non-empty errorlog"
|
||||
fi
|
||||
}
|
||||
|
||||
# If not found (should be cached in travis) download ngrok
|
||||
if [[ ! -e "ngrok/ngrok" ]]; then
|
||||
(
|
||||
mkdir -p ngrok
|
||||
cd ngrok
|
||||
if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
|
||||
wget -O ngrok.zip https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
|
||||
elif [ "${TRAVIS_OS_NAME}" = "osx" ]; then
|
||||
wget -O ngrok.zip https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip
|
||||
else
|
||||
echo "No ngrok for ${TRAVIS_OS_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
unzip ngrok.zip ngrok
|
||||
chmod +x ngrok
|
||||
)
|
||||
fi
|
||||
|
||||
# Run ngrok and grab temporary url from logfile
|
||||
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp.log &
|
||||
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp2.log &
|
||||
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp3.log &
|
||||
sleep 2
|
||||
TMP_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp.log | head -1 | cut -d':' -f2)"
|
||||
TMP2_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp2.log | head -1 | cut -d':' -f2)"
|
||||
TMP3_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp3.log | head -1 | cut -d':' -f2)"
|
||||
if [[ -z "${TMP_URL}" ]] || [[ -z "${TMP2_URL}" ]] || [[ -z "${TMP3_URL}" ]]; then
|
||||
echo "Couldn't get an url from ngrok, not a dehydrated bug, tests can't continue."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run python webserver in .acme-challenges directory to serve challenge responses
|
||||
mkdir -p .acme-challenges/.well-known/acme-challenge
|
||||
(
|
||||
cd .acme-challenges
|
||||
python -m SimpleHTTPServer 8080 > /dev/null 2> /dev/null
|
||||
) &
|
||||
|
||||
# Generate config and create empty domains.txt
|
||||
echo 'CA="https://testca.kurz.pw/directory"' > config
|
||||
echo 'CA_TERMS="https://testca.kurz.pw/terms"' >> config
|
||||
echo 'WELLKNOWN=".acme-challenges/.well-known/acme-challenge"' >> config
|
||||
echo 'RENEW_DAYS="14"' >> config
|
||||
touch domains.txt
|
||||
|
||||
# Check if help command is working
|
||||
_TEST "Checking if help command is working..."
|
||||
./dehydrated --help > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Default command: help"
|
||||
_CHECK_LOG "--help (-h)"
|
||||
_CHECK_LOG "--domain (-d) domain.tld"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Register account key without LICENSE set
|
||||
_TEST "Register account key without LICENSE set"
|
||||
./dehydrated --register > tmplog 2> errorlog && _FAIL "Script execution failed"
|
||||
_CHECK_LOG "To accept these terms"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Register account key and agreeing to terms
|
||||
_TEST "Register account key without LICENSE set"
|
||||
./dehydrated --register --accept-terms > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Registering account key"
|
||||
_CHECK_FILE accounts/*/account_key.pem
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Delete accounts and add LICENSE to config for normal operation
|
||||
rm -rf accounts
|
||||
echo 'LICENSE="https://testca.kurz.pw/terms/v1"' >> config
|
||||
|
||||
# Run in cron mode with empty domains.txt (should only generate private key and exit)
|
||||
_TEST "First run in cron mode, checking if private key is generated and registered"
|
||||
./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Registering account key"
|
||||
_CHECK_FILE accounts/*/account_key.pem
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Temporarily move config out of the way and try signing certificate by using temporary config location
|
||||
_TEST "Try signing using temporary config location and with domain as command line parameter"
|
||||
mv config tmp_config
|
||||
./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --accept-terms -f tmp_config > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_NOT_LOG "Checking domain name(s) of existing cert"
|
||||
_CHECK_LOG "Generating private key"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
|
||||
_CHECK_LOG "Challenge is valid!"
|
||||
_CHECK_LOG "Creating fullchain.pem"
|
||||
_CHECK_LOG "Done!"
|
||||
_CHECK_ERRORLOG
|
||||
mv tmp_config config
|
||||
|
||||
# Add third domain to command-lime, should force renewal.
|
||||
_TEST "Run in cron mode again, this time adding third domain, should force renewal."
|
||||
./dehydrated --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --domain "${TMP3_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Domain name(s) are not matching!"
|
||||
_CHECK_LOG "Forcing renew."
|
||||
_CHECK_LOG "Generating private key"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP3_URL}"
|
||||
_CHECK_LOG "Challenge is valid!"
|
||||
_CHECK_LOG "Creating fullchain.pem"
|
||||
_CHECK_LOG "Done!"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Prepare domains.txt
|
||||
# Modify TMP3_URL to be uppercase to check for upper-lower-case mismatch bugs
|
||||
echo "${TMP_URL} ${TMP2_URL} $(tr 'a-z' 'A-Z' <<<"${TMP3_URL}")" >> domains.txt
|
||||
|
||||
# Run in cron mode again (should find a non-expiring certificate and do nothing)
|
||||
_TEST "Run in cron mode again, this time with domain in domains.txt, should find non-expiring certificate"
|
||||
./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
|
||||
_CHECK_LOG "Skipping renew"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Disable private key renew
|
||||
echo 'PRIVATE_KEY_RENEW="no"' >> config
|
||||
|
||||
# Run in cron mode one last time, with domain in domains.txt and force-resign (should find certificate, resign anyway, and not generate private key)
|
||||
_TEST "Run in cron mode one last time, with domain in domains.txt and force-resign"
|
||||
./dehydrated --cron --force > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
|
||||
_CHECK_LOG "Ignoring because renew was forced!"
|
||||
_CHECK_NOT_LOG "Generating private key"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
|
||||
_CHECK_LOG "Requesting challenge for ${TMP3_URL}"
|
||||
_CHECK_LOG "Already validated!"
|
||||
_CHECK_LOG "Creating fullchain.pem"
|
||||
_CHECK_LOG "Done!"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Check if signcsr command is working
|
||||
_TEST "Running signcsr command"
|
||||
./dehydrated --signcsr certs/${TMP_URL}/cert.csr > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "BEGIN CERTIFICATE"
|
||||
_CHECK_LOG "END CERTIFICATE"
|
||||
_CHECK_NOT_LOG "ERROR"
|
||||
|
||||
# Check if renewal works
|
||||
_TEST "Run in cron mode again, to check if renewal works"
|
||||
echo 'RENEW_DAYS="300"' >> config
|
||||
./dehydrated --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
|
||||
_CHECK_LOG "Renewing!"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Check if certificate is valid in various ways
|
||||
_TEST "Verifying certificate..."
|
||||
_SUBTEST "Verifying certificate on its own..."
|
||||
openssl x509 -in "certs/${TMP_URL}/cert.pem" -noout -text > tmplog 2> errorlog && _PASS || _FAIL
|
||||
_CHECK_LOG "CN=${TMP_URL}"
|
||||
_CHECK_LOG "${TMP2_URL}"
|
||||
_SUBTEST "Verifying file with full chain..."
|
||||
openssl x509 -in "certs/${TMP_URL}/fullchain.pem" -noout -text > /dev/null 2>> errorlog && _PASS || _FAIL
|
||||
_SUBTEST "Verifying certificate against CA certificate..."
|
||||
curl -s https://testca.kurz.pw/acme/issuer-cert | openssl x509 -inform DER -outform PEM > ca.pem
|
||||
(openssl verify -verbose -CAfile "ca.pem" -purpose sslserver "certs/${TMP_URL}/fullchain.pem" 2>&1 || true) | (grep -v ': OK$' || true) >> errorlog 2>> errorlog && _PASS || _FAIL
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Revoke certificate using certificate key
|
||||
_TEST "Revoking certificate..."
|
||||
./dehydrated --revoke "certs/${TMP_URL}/cert.pem" --privkey "certs/${TMP_URL}/privkey.pem" > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
REAL_CERT="$(readlink -n "certs/${TMP_URL}/cert.pem")"
|
||||
_CHECK_LOG "Revoking certs/${TMP_URL}/${REAL_CERT}"
|
||||
_CHECK_LOG "Done."
|
||||
_CHECK_FILE "certs/${TMP_URL}/${REAL_CERT}-revoked"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Enable private key renew
|
||||
echo 'PRIVATE_KEY_RENEW="yes"' >> config
|
||||
echo 'PRIVATE_KEY_ROLLOVER="yes"' >> config
|
||||
|
||||
# Check if Rolloverkey creation works
|
||||
_TEST "Testing Rolloverkeys..."
|
||||
_SUBTEST "First Run: Creating rolloverkey"
|
||||
./dehydrated --cron --domain "${TMP2_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
CERT_ROLL_HASH=$(openssl rsa -in certs/${TMP2_URL}/privkey.roll.pem -outform DER -pubout 2>/dev/null | openssl sha -sha256)
|
||||
_CHECK_LOG "Generating private key"
|
||||
_CHECK_LOG "Generating private rollover key"
|
||||
_SUBTEST "Second Run: Force Renew, Use rolloverkey"
|
||||
./dehydrated --cron --force --domain "${TMP2_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
CERT_NEW_HASH=$(openssl rsa -in certs/${TMP2_URL}/privkey.pem -outform DER -pubout 2>/dev/null | openssl sha -sha256)
|
||||
_CHECK_LOG "Generating private key"
|
||||
_CHECK_LOG "Moving Rolloverkey into position"
|
||||
_SUBTEST "Verifying Hash Rolloverkey and private key second run"
|
||||
[[ "${CERT_ROLL_HASH}" = "${CERT_NEW_HASH}" ]] && _PASS || _FAIL
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# Test cleanup command
|
||||
_TEST "Cleaning up certificates"
|
||||
./dehydrated --cleanup > tmplog 2> errorlog || _FAIL "Script execution failed"
|
||||
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/cert-"
|
||||
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/chain-"
|
||||
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/fullchain-"
|
||||
_CHECK_ERRORLOG
|
||||
|
||||
# All done
|
||||
exit 0
|
||||
Reference in New Issue
Block a user