I published certx.sh - another small shell script to get and renew certificates via ACME v2.
~500 lines of POSIX shell with account rollover/deactivation, EAB, and multi-server deployment.
Single script, single conf file, DNS + HTTP challenges, written by human.
Note that "ACME v2" isn't really a thing; the Let's Encrypt API endpoint is named acme-v02 because it previously implemented a draft version of ACME, but there's only been one standardized and published version of the ACME spec.
btw I'd note that this uses curl and openssl as dependency. while those are ubiquitous in full distro they may not in other context like embedded/ inside a container etc
Edit: you already did, didn't visit git readme while typing this
Edit2: it doesn't support profile nor ARI yet: so don't use for shortlived ip certificate
You should probably change your days setting to be a percentage of life remaining.
The LE shortlived profile issues certs lasting less than 7 days. ARI will tell it when to renew. And, some catchall setting if ARI isn't working is fair. For shortlived certs (<10d life) LE recommends renewing after 1/2 of life elapsed.
For 90 day certs LE recommend renew after 2/3 of life remains elapsed. A percentage life avoids manually adjust a 'days' value as LE and the industry move to shorter lived certs. See this timeline: Decreasing Certificate Lifetimes to 45 Days - Let's Encrypt
Should you need further persuasion ... LE ignores rate limits for a cert renewed within the ARI window and which uses the ARI 'replaces' Cert ID. A fixed 'days' setting that operates before ARI window opens won't take advantage.
Do I understand correctly that 'days' is only used if a CA never returns a valid ARI window? That is, you'll persist a prior ARI window even if some ARI request(s) fail?
What's the benefit to 'days' rather than 'percentage'? If above is true it's mostly academic as the more common public CA support ARI. And, unlikely to never get a valid ARI after issuance before a 'days' value triggers.
Sorry to pester but when ARI was new its windows and fallback strategies fostered much discussion
no, prior ARI windows are not persisted. Each renew-all invocation makes a fresh ARI request.
If any ARI step fails (no ARI ID in config, CA directory lacks renewalInfo, the ARI request itself errors), it silently falls back to checking the stored certificate expiry date against days.
It is new to me, were introduced/implemented today
I need to read/think a little about renew strategies, 'days' is there from time before ARI support.
Yes, you need to request ARI at each renew check as its window may change. It will be in the past if the CA wants an immediate renewal such as a CA revocation event. LE has a wide window and leaves randomizing within that to you. GTS window is very narrow (1H last I checked) and changes each time giving a built-in randomization. That's just background since you are new to ARI.
When I said "persist" I was thinking about persisting across a single ARI query fail. That is, to use a prior (now "stale") ARI window for this cert rather than reverting to non-ARI. If ARI queries continue to fail some fallback plan is needed. And, dealing with that may be fancier than you need / want
Certainly if ARI is not advertised in directory you need an alternate plan.
Thank you for explaining. For shortlived certificates, keeping a stale ARI window is definitely better than fallback to days. If ARI queries continue to fail, the order will probably fail as well, so the fallback would be an alternate CA?
My personal opinion is that for shortlived certs (<10 days) a fallback CA should be part of the plan. And, you should check renewal at least twice day too. Even for 90 day (or upcoming 45 day) certs twice / day isn't unreasonable. (*1)
As for ARI failures implying order failure, at least for LE that may not be true. ARI is served through a CDN somewhat apart from the LE issuance related servers. ARI was designed to allow that knowing the query volumes could be extremely high.
I'm actually not sure what, if any, networking infra overlaps between those systems. But, there could be ARI failures while issuance proceeds normally. Or vice-versa. In practice ARI failures are likely to be rare apart from typical, and usually transient, comms problems.
(*1) The ARI retry-after response header limits the frequency of ARI checks. Currently LE is at 6 hours which implies at most 4 checks per day. That is excessive for 90 day (even 45 day) certs but is not unreasonable for shortlived certs. During this ARI "rest period" your client won't be making any requests to the CA for that cert. And, you need to make sure any error retry doesn't flood the CA. A twice/day frequency is more common and is what LE recommends in general. Dealing with shortlived certs takes some care. And, LE's docs say it is not for everyone. Being an active, engaged, admin with proper monitoring is necessary too.
As an aside on ARI support, it's possible in some situations (changed ca, changed account, recovered from backup etc) to get out of sync with the CA regarding what the most recent cert id is and attempt to replace a cert id that's already been replaced or is otherwise invalid.
This results in a self-imposed denial of service until you stop trying to give it the invalid "replaces" cert id. My preference there is if your order has failed to renew for any reason then drop the replaces cert id from the order. It's not ideal, but it works.
Thank you all for good information, I think this script got better.
With ARI support there are not much to configure, single cert can always be ordered but renew-all follows ARI.
Current 'renew-all' flow:
check ARI support (renewalInfo exists, authorityKeyIdentifier was on ordered cert)
with ARI support
check stored window start exists and reach - renew
make ARI request (skip if previous retry-after has not reached)
check window start and store it, renew if reached
on ARI request error
check stale start window, renew if reached
on order request error
remove 'replaces' from the next order
without ARI renews if the cert expires within 'days' days
Flaw I see is, when retry-after is greater than window start/end (request not made), can it be?
Maybe simple 'set retry-after min(retry-after, window_start)' is enough.
Edit: Added 'check-stored-start-reached' step before ARI request, this should fix
There is an obscure case not covered which is if you never get a good ARI response for a system that advertises ARI support. Maybe that's possible for a short-lived cert checking renewal just once a day but even that is very unlucky. Apart from egregious bugs hard to imagine that happening for 'normal' length duration certs.
Robust handling of shortlived failures is tricky anyway given the problems could be with issuance. Not sure how much I'd worry about that.
You could do a, gasp, hard-coded fail-safe renewal at 15% (or whatever) life remaining. Shortlived certs should be checking multiple times/day so that gives a few chances at least.
While 'days' can work I personally far prefer percentages. The LE profile dictates the life of the cert and using 'days' means having to adjust that figure whenever you change the profile. Seems error prone.
Google Trust Services supports notBefore and notAfter values so you can set whatever cert life you want. The industry is moving toward 47 day max certs in the coming years and a percentage will adapt much better. Having to track a 'days' setting with various CA migration schedules will be a nuisance.
Plenty of other clients focus on days so you are not alone. At least you aren't counting days from the start of the cert's life like one major ACME Client does - ouch!
I'd agree with @MikeMcQ that lifetime percentage based renewal is very worthwhile. We've deprecated day based renewal intervals in Certify The Web and will fallback to a percentage default if existing days based config will conflict with the lifetime of a cert. With 7 days cert (or less) it's just not worth having day based intervals any more.
certx.sh v26.2.7 supports percentage based renewal.
./certx.sh renew-all # renew via ARI, fallback to 20% of validity
./certx.sh renew-all 33% # renew at 33% of validity (ignores ARI)
./certx.sh renew-all 7 # renew 7 days before expiry (ignores ARI)