dehydrated-io / dehydrated Goto Github PK
View Code? Open in Web Editor NEWletsencrypt/acme client implemented as a shell-script – just add water
Home Page: https://dehydrated.io
License: MIT License
letsencrypt/acme client implemented as a shell-script – just add water
Home Page: https://dehydrated.io
License: MIT License
The certs get put in the following directory structure
/etc/letsencrypt.sh/certs/{some.domain}
/etc/letsencrypt.sh/certs/{other.domain}
I have a requirement to keep them in one directory which is not dictated by the domain names. This is handy in circumstances when the server with letsencrypt may not actually use the certificates itself.
The fix is quite easy - I can do a pull request but wanted you to check the following and see if it would be accepted:
in config.sh add variable if required:
LOCDIR="storage"
In letsencrypt.sh add the following line - defaults to domain name if LOCDIR not set:
Create certificate for domain(s)
sign_domain() {
domain="${1}"
Replace all instances of
/certs/${domain}
with
/certs/${LOCDIR}
Hello,
I wonder what will happen after or around lets-encrypt-x1-cross-signed.pem
expiration. Shouldn't letsencrypt.sh always download fresh copy right after new certificate is created and put it into chain.pem
and fullchain.pem
?
Martin
I've seen in the code that this script prints output even if there is nothing to do.
I like my cron scripts to only print output (and thus sending an email notifying me) if there is an error. This could be controlled by a parameter (e.g. -q).
I am yet unsure about successful certificate renewal - maybe there could be another parameter if there is a message printed in that case.
Hi
When creating the fullchain.pem the order is wrong.
You are creating ChainCert -> Domain Cert instead the other way around.
Nginx is Failing with:
failed (SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)
as the privkey.pem doenst match the first found certificate.
As i have no Idea about Pull Requests, here the diff which fix this:
diff letsencrypt.sh letsencrypt.sh.orig 211d210 < cat "${BASEDIR}/certs/${domain}/cert-${timestamp}.pem" > "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem" 213c212 < cat "${BASEDIR}/certs/${ROOTCERT}" >> "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem" --- > cat "${BASEDIR}/certs/${ROOTCERT}" > "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem" 215c214 < cat "${SCRIPTDIR}/certs/${ROOTCERT}" >> "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem" --- > cat "${SCRIPTDIR}/certs/${ROOTCERT}" > "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem" 216a216 > cat "${BASEDIR}/certs/${domain}/cert-${timestamp}.pem" >> "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem"
thank you, great client by the way.
It would be nice to have additional config files per-certificate to allow for certificate-specific options like RENEW_DAYS and PRIVATE_KEY_RENEW.
This replaces issue #34.
I've pulled a copy of the letsencrypt.sh script from github earlier today on my CentOS 5-based system (SME Server 8.2; see contribs.org). I've created a config.sh and a domains.txt file. But when I run letsencrypt.sh, I get this:
[root@sme8-test letsencrypt.sh]# ./letsencrypt.sh -c
ERROR: This script requires curl.
curl is installed and is in $PATH:
[root@sme8-test ~]# curl -V
curl 7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5
Protocols: tftp ftp telnet dict ldap http file https ftps
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz
[root@sme8-test ~]# which curl
/usr/bin/curl
So I figured I'd comment out the line in letsencrypt.sh that checks for curl and run it again. That didn't work at all:
[root@sme8-test bin]# letsencrypt.sh -c
Generating account key...
sed: invalid option -- E
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
-n, --quiet, --silent
suppress automatic printing of pattern space
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script-file
add the contents of script-file to the commands to be executed
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
-c, --copy
use copy instead of rename when shuffling files in -i mode
(avoids change of input file ownership)
-l N, --line-length=N
specify the desired line-wrap length for the `l' command
--posix
disable all GNU extensions.
-r, --regexp-extended
use extended regular expressions in the script.
-s, --separate
consider files as separate rather than as a single continuous
long stream.
-u, --unbuffered
load minimal amounts of data from the input files and flush
the output buffers more often
--help display this help and exit
--version output version information and exit
If no -e, --expression, -f, or --file option is given, then the first
non-option argument is taken as the sed script to interpret. All
remaining arguments are names of input files; if no input files are
specified, then the standard input is read.
E-mail bug reports to: [email protected] .
Be sure to include the word sed'' somewhere in the
Subject:'' field.
sed: invalid option -- E
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
-n, --quiet, --silent
suppress automatic printing of pattern space
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script-file
add the contents of script-file to the commands to be executed
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if extension supplied)
-c, --copy
use copy instead of rename when shuffling files in -i mode
(avoids change of input file ownership)
-l N, --line-length=N
specify the desired line-wrap length for the `l' command
--posix
disable all GNU extensions.
-r, --regexp-extended
use extended regular expressions in the script.
-s, --separate
consider files as separate rather than as a single continuous
long stream.
-u, --unbuffered
load minimal amounts of data from the input files and flush
the output buffers more often
--help display this help and exit
--version output version information and exit
If no -e, --expression, -f, or --file option is given, then the first
non-option argument is taken as the sed script to interpret. All
remaining arguments are names of input files; if no input files are
specified, then the standard input is read.
E-mail bug reports to: [email protected] .
Be sure to include the word sed'' somewhere in the
Subject:'' field.
Details:
{"type":"urn:acme:error:serverInternal","status":500}[root@sme8-test bin]#
I'm not sure what to try from here--thoughts?
As evidenced here:
https://github.com/lukas2511/letsencrypt.sh/blob/d322e072d643dcee63785be1742a56a8178f74c0/import-certs.sh#L14
The actual paths checked by letsencrypt.sh:
https://github.com/lukas2511/letsencrypt.sh/blob/0e92aba206a60e00077d4b49e9479136f65587dc/letsencrypt.sh#L29
For integrating this script into a webserver with several seperated vhosts it would be great if it could gather the domain info also from files in a directory named domains.txt.d (if it exists). It would function like any other *.d config directory, i.e. concatenating all files with suffix txt.
The config example for nginx in the README.md doesn't work for me
location /.well-known/acme-challenge {
alias /var/www/letsencrypt;
}
I just get 403 forbidden errors when i try to access one of the challenge files via a browser and also letsencrypt.sh can't access the challenge files.
But the following config for nginx works for me:
location ^~ /.well-known/acme-challenge {
alias /var/www/letsencrypt;
}
Not sure if that should be changed or if there could be an other issue preventing the example config for nginx work correctly in my setup.
when there is just one domain name configured for a certificate, the CSR shoud be done without altname extension. Currelty requesting a cert for
foo.example.com
will result in
X509v3 Subject Alternative Name:
DNS:foo.example.com
being added to the CSR and in the resulting certificate.
Hello. Your script very nice and simple!
Have questions:
revoke
cert? For one domain\sub-domain. For all domain.renew
cert? For one domain\sub-domain. For all domain.letsencrypt.sh
to cron, no need params for script?The return code of the letsencrypt.sh is always 1:
root@default-ubuntu-1404:/etc/letsencrypt# sudo -u letsencrypt ./letsencrypt.sh && echo hallo
Using config file /etc/letsencrypt/config.sh
+ Generating account key...
+ Registering account key with letsencrypt...
root@default-ubuntu-1404:/etc/letsencrypt# ls -la
total 48
drwxr-xr-x 4 letsencrypt root 4096 Dec 16 10:47 .
drwxr-xr-x 75 root root 4096 Dec 16 10:46 ..
drwxr-xr-x 2 letsencrypt root 4096 Dec 16 10:46 .acme-challenges
drwxr-xr-x 2 letsencrypt root 4096 Dec 16 10:46 certs
-rwxr--r-- 1 root root 2416 Dec 16 10:46 config.sh
-rw-r--r-- 1 root root 0 Dec 16 10:46 domains.txt
-rwxr-xr-x 1 root root 20567 Dec 16 10:46 letsencrypt.sh
-rw------- 1 letsencrypt letsencrypt 3243 Dec 16 10:47 private_key.pem
Support for recovery keys might be a good idea (which is described here). This would enable to recover an account later on.
It would be very nice if the script will run in alternative shells. For example, in busybox ash.
Now it is not working.
Right now, the way it's written, hooks can't do normal IO then get immediate EOF.
While it's straightforward enough to run under expect/whatever, would be nice if a simple read/prompt would work.
Once I get a certificate using the production LetsEncrypt CA, the certificate doesn't seem valid and doesn't seem complete. For example, here is one I just created:
-----BEGIN CERTIFICATE-----
eyJ0eXBlIjoidXJuOmFjbWU6ZXJyb3I6dW5hdXRob3JpemVkIiwiZGV0YWlsIjoi
RXJyb3IgY3JlYXRpbmcgbmV3IGNlcnQgOjogQXV0aG9yaXphdGlvbnMgZm9yIHRo
ZXNlIG5hbWVzIG5vdCBmb3VuZCBvciBleHBpcmVkOiB2ZXJibGVyLmNvbSB3d3cu
dmVyYmxlci5jb20iLCJzdGF0dXMiOjQwM30=
-----END CERTIFICATE-----
Am I supposed to be doing something else with the final cert.pem
file once I the script finishes to make it a valid certificate?
The current script uses ${HOME}
, when in some cases it might not be set in the environment. The use case where it happens is using letsencrypt.sh as a systemd service/timer set.
This causes this issue:
letsencrypt.sh[]: [...]/letsencrypt.sh: line 30: HOME: unbound variable
As this is used to find plausible config file locations, I wouldn't know what's the best way to avoid this issue. The ${HOME}
location might be important for some cases, for mine it isn't.
As a stop-gap solution (for anybody setting up a systemd timer), it can be set explicitly.
[...]
[Service]
Environment="HOME=/root/"
[...]
Personally, I would use ~
instead of ${HOME}
which does always resolve to the current user's home, even when neither ${HOME}
or ${USER}
are in the environment. There is one caveat with the use of ~
, its properties around quoting are different than those of variables. I have not tested with a folder with spaces as a home folder, but I would guess that it would be even rarer than an execution of the script with a barebones environment. I think that with the current use, the quoting would still be fine, as long as it is used like so, unquoted: ~/.letsencrypt.sh
.
Here's a test case: /usr/local/bin/tildeexpansion.sh
#!/bin/bash
unset HOME
set -e
set -u
echo ~
echo ~/.letsencrypt.sh
echo "${HOME:-$(echo ~)}"
echo "${HOME:-$(echo ~)}/.letsencrypt.sh"
echo "${HOME:-~}"
echo "${HOME:-~}/.letsencrypt.sh"
echo "${HOME}"
echo "${HOME}/.letsencrypt.sh"
To run as a systemd service: /etc/systemd/system/tildeexpansion.service
[Unit]
Description=Tests tilde expansion
[Service]
Type=oneshot
ExecStart=/usr/local/bin/tildeexpansion.sh
It can be run with sudo systemctl daemon-reload; sudo systemctl start tildeexpansion
. /usr/local/bin/tildeexpansion.sh
needs to be executable. The output can be looked at using sudo journalctl --unit tildeexpansion
. This test case service will fail since the exit code of the script is a failure.
References:
I'm currently working on some Debian packaging for letsencrypt.sh and it would be really helpful to base this on releases (rather than some random commits in git).
Would you mind using some version schema and do a release from time to time?
hello
just FYI
on pfSense there is another one dependency: bash
and there something goes wrong
[2.2.5-RELEASE][root@gw.]/root/letsencrypt.sh: ./letsencrypt.sh
Using config file /root/letsencrypt.sh/config.sh
+ Generating account key...
+ Registering account key with letsencrypt...
Processing owncloud.example.com
+ Signing domains...
+ make directory /root/letsencrypt.sh/certs/owncloud.example.com ...
+ Generating private key...
+ Generating signing request...
cat: /dev/fd/63: No such file or directory
error on line -1 of /dev/fd/63
675592508:error:02001002:system library:fopen:No such file or directory:/usr/pfSensesrc/src.RELENG_2_2/secure/lib/libcrypto/../../../crypto/openssl/crypto/bio/bss_file.c:169:fopen('/dev/fd/63','rb')
675592508:error:2006D080:BIO routines:BIO_new_file:no such file:/usr/pfSensesrc/src.RELENG_2_2/secure/lib/libcrypto/../../../crypto/openssl/crypto/bio/bss_file.c:172:
675592508:error:0E078072:configuration file routines:DEF_LOAD:no such file:/usr/pfSensesrc/src.RELENG_2_2/secure/lib/libcrypto/../../../crypto/openssl/crypto/conf/conf_def.c:197:
Currently if you add or remove a domain from a line in domains.txt the script doesn't recognize the change and you'll just keep the existing certificate with the old set of domains. This should be improved.
It's possible to change domains.txt format to include webroot type domain aliases?
i.e.
/var/www/example.org/ apache example.org www.example.org
/var/www/example.net/ nginx example.net www.example.net other.example.net
/var/www/common/ mail mailhost.example.org webmail.example.org
and modify hook calling to pass webroot and type to it.
If one delimits the 'main' domain name and additional domain names by other whitespace characters (e.g. tab), certificate issuance works fine, but 'cron' age check fails...
This is because 'cut' only accepts one delimiter - space OR tab.
Probably add an explanatory comment line in domains.txt.example?
Subj.
I running letsencrypt.sh first time and using this instruction: https://www.metachris.com/2015/12/comparison-of-10-acme-lets-encrypt-clients/
# ls
config.sh config.sh.example domains.txt domains.txt.example letsencrypt.sh LICENSE README.md test.sh
# cat domains.txt
le.amet13.name
lebeta.amet13.name
# ./letsencrypt.sh
Unknown parameter detected:
Having libressl (LibreSSL 2.3.2
) in your path breaks the script since libressl does not support sha
.
openssl:Error: 'sha' is an invalid command.
Message Digest commands (see the 'dgst' command for more details)
gost-mac md4 md5 md_gost94
ripemd160 sha1 sha224 sha256
sha384 sha512 streebog256 streebog512
whirlpool
Maybe add the possibility to override the openssl binary path in the config file?
What I do right now is forcing the path manually:
sudo sh -c 'PATH="/usr/bin:$PATH";./letsencrypt.sh --cron --force -f config.sh'
So I am trying out this software just for kicks and seem to be getting a very odd error, that I can't find anything about:
Processing testdoman.com with alternative names: www.testdomain.com
I can't really seem to find the reason for this error, even by inspecting the script?
I am new to LE in general and while the description for using the challenge type is nice, it would be helpful to someone new to have an example usage.
If this script is used inside a cronjob it may interfere with a user using the script, so i think it would be a good idea to have some sort of lock-file to make sure it's at least not signing the same domain at the same time, or maybe even that it's not running at the same time.
letsencrypt.sh test for "file" for the privkey, but in fact it's a symlink. I fixed it with the following change:
diff --git a/letsencrypt.sh b/letsencrypt.sh
index dad4766..f52eb16 100755
--- a/letsencrypt.sh
+++ b/letsencrypt.sh
@@ -429,7 +429,7 @@ sign_domain() {
privkey="privkey.pem"
# generate a new private key if we need or want one
- if [[ ! -f "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
+ if [[ ! -r "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
echo " + Generating private key..."
privkey="privkey-${timestamp}.pem"
case "${KEY_ALGO}" in
The construct
<(cat "${OPENSSL_CNF}" <(printf "[SAN]\nsubjectAltName=%s" "${SAN}"))
leads to
No such file or directory:/usr/src/secure/lib/libcrypto/../../../crypto/openssl/crypto/bio/bss_file.c:126:fopen('/dev/fd/63','rb')
under FreeBSD, if fdescfs is not mounted.
A simple
cp "${OPENSSL_CNF}" /tmp/foo.cnf
printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> /tmp/foo.cnf
openssl req -new -sha256 -key "${BASEDIR}/certs/${domain}/${privkey}" -out "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config /tmp/foo.cnf
makes it work here.
I'm using my hook script to create an 'everything.pem' for software that requires the cert, chain, and key all in a single file.
It would be cleaner if the hook script was passed the timestamp so I can replace TIMESTAMP="$(readlink ${FULLCHAIN} | sed 's/fullchain-//;s/\.pem//;')"
with TIMESTAMP=$6
.
Alternatively, if letsencrypt.sh created everything.pem itself it would be nice, but that differs from upstream at present.
Without realising (what you mean RTFM?!) I hit the limit quite quickly
It would help to document this fact and use the staging server by default to test.
From a purely syntactic point of view, there are some things that can be enhanced:
#!/usr/bin/env bash
so your script is portable to other UNIX systems, such as *BSD that host the bash
binary at /usr/local/bin
$*
and replace it with $@
as the latter keeps arguments even if they contain spaces, whereas the former replaces "a b"
to "a" "b"
I'll keep the script in my watch list and use it once when I'm ready to do some webadmin ;)
result="$(signed_request "${challenge_uri}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}')"
status="$(printf '%s\n' "${result}" | get_json_string_value status)"
while [[ "${status}" = "pending" ]]; do
sleep 1
status="$(http_request get "${challenge_uri}" | get_json_string_value status)"
done
Would be nice if the full response would be saved in both the request and status inquiry and displayed if a failure occurs.
should be
curl -V ...
retcode="$?"
set -e
Is there any way to revoke created certs to remove them completely and create new ones ?
Any plans to add a license to this project?
Hi,
as far as i can see from the code it can happen that if "PRIVATE_KEY_RENEW = yes" a new key is generated and activated - even if we are not sure if a new certificate was issued.
Maybe the removal of the softlink an relinking should be done after the matching cert was issued.
letsencrypt.sh is not handling correctly error such as "Too many certificates already issued" on Requesting certificate block : if an error occur on signed_request then there is no reason to try to check the certificate and the program should report the error and try following certificates.
Output of letsencrypt.sh showing the error:
Using config file /Users/test/GIT/letsencrypt.sh/config.sh
Processing test.example.com
+ Checking domain name(s) of existing cert... unchanged.
+ Checking expire date of existing cert...
+ Valid till Apr 9 15:50:00 2016 GMT (Longer than 14 days). Ignoring because renew was forced!
+ Signing domains...
+ Generating signing request...
+ Requesting challenge for test.example.com...
+ Responding to challenge for test.example.com...
+ Challenge is valid!
+ Requesting certificate...
+ ERROR: An error occurred while sending post-request to https://acme-v01.api.letsencrypt.org/acme/new-cert (Status 429)
Details:
{"type":"urn:acme:error:rateLimited","detail":"Error creating new cert :: Too many certificates already issued for: example.com","status":429}
+ Checking certificate...
+ ERROR: failed to run x509 -text (Exitcode: 1)
Details:
unable to load certificate
140735245852752:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1198:
140735245852752:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:372:Type=X509
140735245852752:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:pem_oth.c:83:
On abort with "Challenge is invalid! (returned: invalid)", exit value 0 is returned - any other value would be expected.
If all references of
$ mktemp
usage: mktemp [-d] [-q] [-t prefix] [-u] template ...
mktemp [-d] [-q] [-u] -t prefix
but adding -t tmp will generate the same desired result:
$ mktemp -t tmp
/tmp/tmp.y4A4wnen
When letsencrypt.sh creates ${WELLKNOWN}, it should probably chmod 0711
it, otherwise it has 0700 mode.
Currently the regex for hostnames seems to be incorrectly escaped as hostnames like smokeping.example.com are incorrectly parsed.
I've doubled escaped the:
sed 's/^\s_//g;s/\s_$//g'
to:
sed 's/^\s_//g;s/\s_$//g'
and now it works for me.
Hello,
I use your script on old gentoo instalation and get this error:
./letsencrypt.sh: line 135: shasum: command not found
So i've just changed "shasum -a 256" to "sha256sum" and it works ! I know I use very old instalation, but that's why I don't/can't use official letsencrypt client.
When requesting a certificate for a domain... I get through all steps successfully, even challenges; however, I get a curl error when requesting the actual certificate:
+ Requesting certificate...
curl: (22) The requested URL returned error: 403 Forbidden
+ Done!
I'm seeing a bit of an issue with the handling of account private keys when switching from one CA to another. The process goes something like this:
The user can, of course, delete or rename the private_key.pem file, and then the script will run fine. I'd suggest, though, one of two ways of handling this in the script:
I think (1) is the better option of the two, but either would avoid this.
For setups that require a bit of work to get into a state that allows renewing (opening up holes for the well-known directory, restarting webservers, etc) it might be useful to be able to check if any certs will need renewing in a separate step from actually going through the renewal.
Proposal: have a "cert status" command that returns 0
if everything is fine and 1
if certs will need to be updated.
While it's true that this check can easily be done outside the script with an openssl -checkend
, having it in the script where RENEW_DAYS
and domains.txt
are already properly configured would be convenient.
Thoughts?
Hi! When i try to run the script without any parameters, it tries to use the "column" command, which may not be available on every system (but is found in the bsdmainutils package on debian/ubuntu, at least).
For both the command line and domains.txt
, it would be nice to handle the very common case of singing both example.tld
and www.example.tld
without the need to always specify both domains. Nginx uses a syntax of .example.tld
which automatically expands to example.tld
and www.example.tld
. Would be nice if letsencrypt.sh
supports this syntax the same way.
example.com www.example.com something.example.com
Current flow:
Proposed flow:
This is more useful with DNS challenges, since DNS may take extra time to update, and you can touch the DNS record once for all subdomains.
However, this can also apply to HTTP challenges, where maybe your upload/copy script can push all challenges once, or maybe you need to reload your web server config only once, etc.
Revocation of certificates would be a really nice feature to have.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.