Code Monkey home page Code Monkey logo

dehydrated's Introduction

dehydrated Donate

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, awk, mktemp (all found pre-installed on almost any system, cURL being the only exception).

Current features:

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

For getting started I recommend taking a look at docs/domains_txt.md, docs/wellknown.md and the Usage section on this page (you'll probably only need the -c option).

Generally you want to set up your WELLKNOWN path first, and then fill in domains.txt.

Please note that you should use the staging URL when experimenting with this script to not hit Let's Encrypt's rate limits. See docs/staging.md.

If you have any problems take a look at our Troubleshooting guide.

Config

dehydrated is looking for a config file in a few different places, it will use the first one it can find in this order:

  • /etc/dehydrated/config
  • /usr/local/etc/dehydrated/config
  • The current working directory of your shell
  • The directory from which dehydrated was run

Have a look at docs/examples/config to get started, copy it to e.g. /etc/dehydrated/config and edit it to fit your needs.

Usage:

Usage: ./dehydrated [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ...

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

Parameters:
 --accept-terms                   Accept CAs terms of service
 --full-chain (-fc)               Print full chain when using --signcsr
 --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 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
 --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

Chat

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.

dehydrated's People

Contributors

alexendoo avatar athienem avatar bahamat avatar bdossantos avatar btbn avatar crza avatar danimo avatar digint avatar fancycode avatar gboudrias avatar germeier avatar glensc avatar gstrauss avatar helmo avatar jantore avatar krayon avatar leonklingele avatar lschuermann avatar lukas2511 avatar mapreri avatar nielslaukens avatar nkovacne avatar o1oo11oo avatar petrkle avatar rogdham avatar rudis avatar simondeziel avatar tralafiti avatar typingartist avatar ymc-dabe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dehydrated's Issues

shasum: command not found

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.

lets-encrypt-x1-cross-signed.pem

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

Certificate invalid?

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?

New Key in place even if renewal failed

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.

Wrong order in the Fullchain (For Nginx)

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.

pfsense

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:

add altname extension only when needed

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.

Error when requesting ceritficate...

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!

Resign certificates if (sub)domains changed

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.

delimiter between domain and additional names in domain.txt is 'space only'

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?

Challenge is invalid

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

  • Signing domains...
  • Generating private key...
  • Generating signing request...
  • Requesting challenge for testdomain.com...
  • Responding to challenge for testdomain.com...
    ERROR: Challenge is invalid! (returned: invalid)

I can't really seem to find the reason for this error, even by inspecting the script?

Have questions..

Hello. Your script very nice and simple!
Have questions:

  1. how to revoke cert? For one domain\sub-domain. For all domain.
  2. how to renew cert? For one domain\sub-domain. For all domain.
  3. how to use email (to notify about the expiry dates) if already use in official client..
  4. need or no need (and how) import account data from official client if before already generate for domain certs?
  5. crontab - simple add letsencrypt.sh to cron, no need params for script?
  6. your client are stable and production ready?
    It would be nice if you added a FAQ for newbies (and not only).
    While that is all the questions)

RFE: domains.txt.d

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.

Add option to do a quick renewal check

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?

dns validation status retrieval discards all detail but 'invalid'

    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.

mktemp on older OS's needs -t tmp

If all references of $(mktemp) were changed to $(mktemp -t tmp) this would work universally, as some older OS's (FreeBSD 5.5 for example) will fail if no prefix option is provided:

$ 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

RFC: domains.txt format

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.

Add example for using DNS-01 challenge

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.

Pass ${timestamp} to hook script for extended certificate manipulation.

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.

letsencrypt.sh always generates a new key

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

$WELLKNOWN / challenge-response for nginx

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.

Please make releases

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?

letsencrypt.sh on CentOS 5 doesn't see curl

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

INFO: Using config file /etc/letsencrypt.sh/config.sh

  • 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 theSubject:'' 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 theSubject:'' field.

Details:
{"type":"urn:acme:error:serverInternal","status":500}[root@sme8-test bin]#

I'm not sure what to try from here--thoughts?

Deploy challenges once per domain, not once per altname

example.com www.example.com something.example.com

Current flow:

  1. Request example.com challenge
  2. Deploy example.com challenge response
  3. Verify example.com challenge response
  4. Clean example.com challenge response
  5. Request www.example.com challenge
  6. Deploy www.example.com challenge response
  7. Verify www.example.com challenge response
  8. Clean www.example.com challenge response
  9. Request something.example.com challenge
  10. Deploy something.example.com challenge response
  11. Verify something.example.com challenge response
  12. Clean something.example.com challenge response
  13. Request certificate

Proposed flow:

  1. Request example.com challenge
  2. Request www.example.com challenge
  3. Request something.example.com challenge
  4. Deploy example.com www.example.com something.example.com challenge responses
  5. Verify example.com challenge response
  6. Verify www.example.com challenge response
  7. Verify something.example.com challenge response
  8. Clean example.com www.example.com something.example.com challenge responses
  9. Request certificate

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.

return code is 0 on failure

On abort with "Challenge is invalid! (returned: invalid)", exit value 0 is returned - any other value would be expected.

License?

Any plans to add a license to this project?

revoke certs

Is there any way to revoke created certs to remove them completely and create new ones ?

Handling of CA account private keys

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:

  • Download letsencrypt.sh
  • Create config.sh, setting CA to https://acme-staging.api.letsencrypt.org/directory, and otherwise configuring as desired
  • Run letsencrypt.sh -c
  • It runs, notes no account, creates a private key, registers an account with the staging CA, and otherwise does its thing
  • (optionally) user makes tweaks to config, re-running letsencrypt.sh until results are as desired
  • User changes CA to https://acme-v01.api.letsencrypt.org/directory and runs letsencrypt.sh to generate "official" certs
  • Script errors out with {"type":"urn:acme:error:unauthorized","detail":"No registration exists matching provided key","status":403}

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:

  1. Rather than putting the private key in $BASEDIR/private_key.pem, put it in $BASEDIR/accounts/$CA_HOSTNAME/private_key.pem. When the user changes to a different CA, the script will see that there's no key, create a new one, and register it.
  2. If the script gets this error from the CA, rename private_key.pem to something else, create a new one, and make a new registration.

I think (1) is the better option of the two, but either would avoid this.

Fix storage directory

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

  • locdir="${LOCDIR:-$domain}"

Replace all instances of

/certs/${domain}

with

/certs/${LOCDIR}

hostname regex seems to be wrong

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.

per-certificate config files

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.

bash syntax

From a purely syntactic point of view, there are some things that can be enhanced:

  • the shebang should be #!/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
  • you may be able to change all and any use of $* 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 ;)

cron output

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.

Support `.example.tld` shorthand syntax

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.

unnecessary dependency to fdescfs

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.

work in alternative shells

It would be very nice if the script will run in alternative shells. For example, in busybox ash.
Now it is not working.

Dependency on HOME which may not be set

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:

Return code always 1

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

Add lockfile

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.

bad handling sign error

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:

LibreSSL support

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'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.