Launching those binaries (openssl, curl) seems like a point of failure, not to mention the cough unique challenges of doing simple things like managing an array of strings in a language like bash.
My idea of the simplest letsencrypt client is lego. One binary to rule them all.
I use a shell script ACME client on FreeBSD (called letsencrypt.sh; different from the one linked in this submission and is available in FreeBSD's repos) and have been for a couple of years now. It's worked flawlessly in that time and was an absolute doddle to use. In fact easier than the other ACME clients in FreeBSD's repos at that time.
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.
> 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.
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 :(
I don't know about awscli, but s3cmd has a number of times been backwards and forwards incompatible with itself making any kind of script using it have to check the s3cmd version or punt everything into a docker image.
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.
Ion shell is alpha but it has arrays, simple types and methods to process strings/arrays. I'm not the author but find it quite promising, since the shell ties so much of my userland together.
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.
I've just looked over the code and it's not that long. I could change the style and remove a few features and have it down to 2k lines.
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.
The test file seems fun, but overall it's not that bad. Bash is a fairly simple to parse syntax-wise, so as long as you know your coreutils and you know what you're looking for, making simple changes is fairly easy.
It doesn't look so simple, but definitely looks quirky.
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.
I have written a small plugin for the acme.sh dnsapi that works with my DNS setup.
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.
Maybe when it is a bit more complete. Currently too much is hardcoded for it to be useful outside my setup. But if you look at one of the existing dnsapi plugins you will notice that only two functions need to be implemented. The rest is described above.
BTW: Remember to use letsencrypt-staging for testing.
It's the seperate nameserver and subdomain that I can't quite get my head around - are you saying you can reply to a challenge for x.y.z.org from a nameserver at a.b.c.org?
I probably wouldn't trust implementing important network speaking services as a shell script, potentially running as root.
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.
acme-client is great and I use it for RSA certs, preferring it over acme-tiny [0]. However I prefer ECDSA (until we get EdDSA), so for those certs I use acme-tiny.
The list of DNS APIs supported is impressive. This is the main problem with automating wildcard certificates (DNS provider specific APIs that makes using a single client challenging).
I'm curious to know if anyone else has looked specifically at the stateless mode this script uses. I've pondered it a bit, and it seems reasonable to me superficially, but I haven't invested much time into deeply understanding ACME. I'm wondering if using stateless mode opens one up to any sort of risk that is not present in the other modes.
Yes, this adds a risk which you might judge worth taking.
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.
Yep. Works great and has a limit of 200 lines of code.
Support for ACME2 will be a different project (possibly with a limit of 256 lines of code from what I've heard)
I asked someone when I saw them doing similar things. They adopted the style after reading Google's style guide for shell scripts. Not sure if that's where this author got it from, but it's one possible source.
I think this is the perl-style "private" convention. Those functions are helpers that aren't intended to be used on their own so he prefixes them with _.
I am not going to hate on this tool for being big, because it's portable, that kind of comes with the territory. However, if you wanted something similar that was more portable with less dependencies, you could write it in Perl. There are pure perl libraries for whatever you need, including crypto, and Perl runs on systems like Windows without the need for an extra subsystem or environment.
Access to this /certificates.txt would need to be done over a TLS connection as an attacker could insert their own certs here.
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.
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.
But it does have dependencies (as any shell script tends to). e.g. it requires openssl for computing hmacs; and it needs either curl or wget for doing http requests.
Indeed. For a long time I was using my own fork of simp_le, but the code wasn't easy to split the file writing logic into per domain directories for certificates, etc.
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.
My idea of the simplest letsencrypt client is lego. One binary to rule them all.
https://github.com/xenolf/lego