A Unix shell script implementing ACME client protocol
github.com
github.com
My idea of the simplest letsencrypt client is lego. One binary to rule them all.
> This is a work in progress. Please do NOT run this on a production server and please report any bugs you find!
And it's been in development for at least two years per the dates on the files - with hundreds of commits made at a reasonably continuous pace judging by the code frequency graph.
Either that's a ridiculously conservative warning, or I think there is a problem with something in this approach.
But if one is using a shell to launch that binary, then IMO there are two userland binaries: the shell and the binary being launched. In that case, the second binary depends on the first.
Lets assume one really wants to use only a single binary for whatever reason. Could she use busybox? Does busybox have an openssl-like function? I am not sure.
However I can confirm that the BSD equivalent of busybox can easily be compiled, statically, to include an http client, openssl and the other utilities needed for these shell scripts. I use such a binary for daily work.
Note I am not a LetsEncrypt user and have no comment on ACME or these shell scripts or other programs. I am only commenting as an avid shell scripter and user of static, "multi-call" binaries.
I do hear your criticism of shell scripting - I've written enough things in Bash and Bourne to be intimately familiar with the pitfalls myself. However the string handling required here is pretty basic (strings are encoded anyway as part of the ACME protocol so no escaping required there; and domain names are just ASCII characters without whitespace (even unicode domains just get compiled down to ASCII) so it's all pretty basic stuff). Plus curl and openssl are both solid tools and pretty standard on most systems (given openssl is used in the vast majority of web servers; you'd probably need that just to run your SSL certs anyway).
I don't have anything against lego either. In fact I'm quite an advocate of Go - having written a number of projects in that language myself. But I do think this is one situation that shell scripts are a technically valid option as well.
Duuuude! There's an awscli out there that does all that for you, is maintained by Amazon, covers all the edge cases and is forward compatible cause Amazon takes care of that. Instead we have hundreds of lines of gnarly shell script code to compute hashes and prepare HTTP requests :(
Meanwhile, the underlying API have been fine...
The awscli, on the other hand, is the official CLI from Amazon.
Not sure if "one binary to rule them all" really applies.
When I decided to use it, I was looking for something I could read and understand in less than a day. The one file approach helped this. Lots of code bases are designed for easy change, which often means many small files, so you find the right line easier if you know which file you have to go to.
Because of historic reasons, I use it mainly with Apache, which I configure manually. I know there are options for automatic update of the config, but these weren't there when I read the script first. For me personally there are two problems - maybe these are even addressed in new versions of acme.sh, I haven't checked yet.
1. Apache won't load the vhost config for https if it can't find the key and cert files. Which now means, I have to use a http-only config and another full featured http/https when the challenge is done. 2. My typos in (sub-)domains are one of the main sources of confusion when I try to get a new certificate. I think using dig or host and curl it should be possible to warn the user in this circumstances.
Looks a bit nightmarish to maintain.
1 : https://github.com/Neilpang/acmetest/blob/master/letest.sh
Take a look at how the tests are invoked :
for t in $(grep ^le_test_ $FILE_NAME | cut -d '(' -f 1)
do
if [ -z "$CASE" ] ; then
__green "Progress: "
[ "$_ret" = "0" ] && __green "$_ret" || __red "$_ret"
__green "/$num/$total"
printf "\n"
num=$(_math $num + 1)
fi
...
It basically parses the very same file and checks for functions with the name `le_test`. This means that a random comment containing `le_test_` will break the whole script. You need lots of discipline in order to make things work.Bash works perfectly with "Write programs that do one thing and do it well". Wouldn't be so surprised if this was a bunch of small test cases separated by file.
le_test_xxx # <- this is not a comment anyway
# le_test_xxx does something nice. # <- doesn't match ^le_test_The measure should be whether you can get your head around the whole script at once. I'm fairly sure I can do that. It's a clean example of getopt, makes full use of coreutils (and in an idiomatic way). It handles various OS and shell compatibility issues (and shows it has experience with where these arise). It does logging in a straightforward yet full-featured way.
As usual almost every comment here on HN is superficially critical.
Edit: In case people don't know about Certbot from the EFF.
It is:
- Small
- Written in C
- Can be statically linked against LibreSSL and curl
No nonsense, does what it says on the tin, works on many operating systems.
I run acme.sh in a FreeBSD jail (acme-client). It writes files that are picked up by another jail (acme-dns) that runs nsd. This jail is NOT one of my main authoritative name servers. It only runs _acme.mydomain.tld containing records like:
_acme-challenge.test IN TXT XXXXXXXXXXXXXXX
These records are pointed to from my main name servers with records like: _acme-challenge.test NS acme-dns.mydomain.tld.
(CNAME seems to work too, I will probably switch to that)All this gets me the following benefits:
a) Everything runs restricted by jails that only run for about 10 seconds when issuing or renewing certificates.
b) No need for the service to be publicly accessible. (no issues with firewalls, no need for public IP's)
c) No need for the service to be some kind of web-server (think smtp, imap, irc, xmpp, etc.)
d) The service does not need a public "A" record.
e) No risk of me or the script messing up any live/production configuration.
f) The only thing that needs to accept inbound connections is the "acme-dns" jail on port 53 for about 10 seconds when it is running.
There are still some things I need to find a good solution for. Like easier distribution of certs, keys, etc. I also want to generate the private keys elsewhere and only give the CSR's to the acme-client jail. (If this is possible with ACME. I think it is.)
This setup is not yet complete and I am still experimenting, but it seems to work well.
BTW: Remember to use letsencrypt-staging for testing.
Also, have a look at:
https://blog.crashed.org/letsencrypt-in-freebsd-org/
It was this that inspired me in the first place. I just added the separate subdomain and separate nameserver concept.
OpenBSD's acme-client [0] is a fork of Kristaps Dzonsons' project (formerly letskencrypt), it's a properly privilege separated ACME v1 client, written in C, using pledge(2) on OpenBSD, libseccomp on Linux.
E.g.:
_supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
EDIT: clarification; typo
ACME http-01 validations involve asking an HTTP server for a resource in the .well-known/ reserved URI space with an arbitrary token name, and expecting a reply which contains the token AND a magic value associated with an ACME account.
Ordinarily one configures the server manually each time to respond to requests for a token you know will be used for a single ACME validation you want to succeed.
"Stateless" mode configures the web server to always reply saying the validation is OK for your ACME account, to any request.
Bad Guys can't just use this stateless configuration to get certificates because they don't own your ACME account, if they try to use _their_ ACME account, the validations fail because "stateless" is configured for a single account.
However, if bad guys get your ACME account private key or trick you into configuring one they know, with "stateless" mode they can request certificates at any time and your server will validate the requests automatically.
Is this a unique stylistic thing to the author, or is _foo() versus foo() as a function name following an established convention?
./letsencrypt-auto --renew
nginx reloadHow about just making the domain owner publish which certs are valid on a url like /certificates.txt
Then LetsEncrypt could periodically check if the certs they issued are still endorsed by the domain owner. And if not revoke them.
Certificate revocation is not guaranteed. Yes, there's CRLs and OSCP, but there are plenty of clients that don't do either of those. Not to mention how big CRLs will get if certificates can be issued and revoked for free.
ACME avoids this by associating a specific CSR to a response, so an attacker could not insert their own certificate in the middle of the process and get it signed.
Also, Why would someone prefer this over https://github.com/lukas2511/dehydrated ?
Bash is GPL Licensed and does not ship with most UNIX-like systems. *BSDs don't ship it, macOS is stuck in the past, etc. As a BSD user myself, I install curl(1), but not bash(1) on my machines (or jails).
OpenSSL (an alias of LibreSSL when applicable - e.g. OpenBSD) is standard and available on virtually all systems.