Closed Delicates closed 2 years ago
Please note, with DNS-01 challenge, it also would be fairly trivial for acme.sh to implement the DANE roll-over procedure and manage shared trust anchor TLSA records itself via the already configured DNS API interface.
acme.sh could maintain Current + Next shared trust anchor TLSA records (user configurable), e.g. tlsa._dane.example.net.
as per the example in Section 5.1 of RFC 7671.
All that sysadmins would have to do for different TLS servers that use the acme.sh provided certificate is set up CNAMEs pointing to the shared trust anchor TLSA record that acme.sh maintains.
This would be a nice-to-have cherry on top, leveraging all the work that has gone into acme.sh DNS API integrations, saving sysadmins from having to figure out how to work their corresponding DNS API themselves with --reloadcmd
and supporting/promoting/automating best practice that improves security of the Internet ecosystem overall.
But the key to this all is to start pre-generating future keys in the first place.
Without it can't do Current + Next roll-over even with --reloadcmd
.
But the key to this all is to start pre-generating future keys in the first place.
The pre-generating is ok, but all the current dns api hooks are just to add/remove TXT record, they are not able to process TLSA record.
please try with the tlsa
branch.
acme.sh --upgrade -b tlsa
There is a new env variable Le_Pre_Generated_Key
when reloadcmd is run.
Let me know if this is what you want.
Thanks @Neilpang
I've tested it out and the general gist of it works, but needs some minor polish.
Please see attached the patch with the following minor polish updates:
_info
and _err
messages clean up.*.key.prekey
to *.next.key
.$Le_Pre_Generated_Key
with $NEXT_KEY_PATH
.NEXT_KEY_PATH
also when running post hook and renew hook.Your pre-generated next key for future cert key change is in
.I've tested it successfully with a reloadcmd
script along these lines:
# DANE-EE SPKI SHA2-512 (TLSA 3 1 2) hash generation
TLSA312_CURRENT=$(openssl pkey -in "$CERT_KEY_PATH" -pubout -outform DER 2>/dev/null | sha512sum | cut -f1 -d ' ')
TLSA312_NEXT=$(openssl pkey -in "$NEXT_KEY_PATH" -pubout -outform DER 2>/dev/null | sha512sum | cut -f1 -d ' ')
cat << EOF | nsupdate -k /path/nsupdate.key
update del tlsa._dane.$Le_Domain 60 TLSA
update add tlsa._dane.$Le_Domain 60 TLSA 3 1 2 $TLSA312_CURRENT
update add tlsa._dane.$Le_Domain 60 TLSA 3 1 2 $TLSA312_NEXT
send
EOF
I've also identified some major security issues throughout existing code and raised a separate issue #3127 for it, which also affects this code - so you might want to use this branch for fixing them as well.
Outstanding
A bit of further work is required to fix the unlikely but possible issue with the below code in line with how you decide to fix issue #3127:
cat "$NEXT_KEY_PATH" >"$CERT_KEY_PATH"
Also would be good to export CERT_KEY_PATH
and NEXT_KEY_PATH
for all different hooks (not just post and renew) and also for all hook calls.
The hooks could do validation of TLSA records against:
to make sure nothing got broken during/after acme.sh
execution.
Example use case - it would be useful prior to generating a new certificate to run a pre-hook to check if a TLSA record matching the pre-generated next key already exists. If it doesn't exist, the pre-hook could:
acme.sh
is run.Similarly deployment hook can be used to validate that everything is as it should be and nothing got broken.
I didn't look at all of them until now, and raised a new issue #3128 about hook variables passing inconsistency that needs to be fixed.
After some further testing - there's an issue.
The key files get rotated even when cert issuance fails. This would lead to loss of the previously generated key that has been advertised in TLSA, which in turn would lead to an outage when the cert finally does succeed in being generated, until old TLSA record TTL expires and new TLSA records get propagated by DNS.
The pre-generation of new key should be happening after a successful cert issuance. If cert issuance fails - the old pre-generated key file should be left untouched for the next attempt.
@Delicates I'm not that good at shell scripting/programming, so not capable of doing this myself, yet eagerly waiting to automate TLSA generation/population, too. π So, may I ask if you could achieve any progress since your last comment?
@Neilpang Really nice feature, do you plan to merge it into the master branch?
@rdemendoza I need more time to check it.
@Neilpang Almost 1 year later - do you see any chance of getting that "more time to check it" within the next couple of weeks or at least months? I'm really looking forward to switch from TLSA with 2-1-1 to TLSA's with 3-1-1 without custom post-LE-approval scripting on my box... π
I have also updated to the TLSA branche. Run a --force to renew my cert, but didn't get a TLSA record. Do I have to do something extra besides running this command ?
"/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --force
maybe something could be achieved with this new tool https://letsdns.org/index.html, could be called within a hook.
Sorry guys, I will check it soon.
Eagerly awaiting your checks, @Neilpang π
@Neilpang Since the linked Wiki page isn't that much of an explanation regarding actual usage, can you please extend it?
I do get that the additional command line argument generates the new key & puts it into a file. But how am I supposed to consume it in order to store it as an TXT record over at my domain provider? Your current DNS API architecture only allows for the ACME challenge as an TXT record, not any TLSA key.
@Neilpang Since the linked Wiki page isn't that much of an explanation regarding actual usage, can you please extend it?
I do get that the additional command line argument generates the new key & puts it into a file. But how am I supposed to consume it in order to store it as an TXT record over at my domain provider? Your current DNS API architecture only allows for the ACME challenge as an TXT record, not any TLSA key.
The way I do it is with a custom script through cron (every month) that:
--issue --dns mydnsservice -d my domain.com -d *.mydomain.com --server letsencrypt --ocsp --ecc -k ec-384 -ak ec-384 --always-force-new-domain-key --force
# DANE-EE SPKI SHA2-512 (TLSA 3 1 2) hash generation
on his post)After 2 years I have finally moved from my own patched version to the latest master acme.sh and noticed the following problem:
@Neilpang On the initial certificate issuance, out of all variables passed to reloadcmd
for some reason two are blank, including $Le_Next_Domain_Key
(even though the nextkey file has been created).
On subsequent certificate renewals, the variables are not blank.
Also, looking at the logs, it seems the next key still gets pre-generated prior to successful certificate renewal, so I'm guessing the nasty key-loss issue I pointed out in my last message above remains?
@bolemo Thanks for the further help, but I still got some questions:
acme.sh --issue
instead of acme.sh --renew
?#
& DANE-EE
- otherwise, any (future) visitor of this issue might struggle finding @Delicates's comment.acme.sh
, removing old, keeping current & adding new ones?--deploy --deployhook <my hook>
to the usual acme.sh --renew [β¦]
one-liner, right?If the answer to the last question above would be true, then I'd be lucky, because the deploy hook I need to run already includes restarting Nginx. π
Really looking forward to your answers! π
@bolemo Thanks for the further help, but I still got some questions:
- Why do I have to run
acme.sh --issue
instead ofacme.sh --renew
?- Please, can you tweak your comment so that it includes a whitespace between
#
&DANE-EE
- otherwise, any (future) visitor of this issue might struggle finding @Delicates's comment.- How do I actually push the TLSA records to my DNS Service using
acme.sh
, removing old, keeping current & adding new ones?- Using your "tutorial", deploying can still be done via adding
--deploy --deployhook <my hook>
to the usualacme.sh --renew [β¦]
one-liner, right?If the answer to the last question above would be true, then I'd be lucky, because the deploy hook I need to run already includes restarting Nginx. π
Really looking forward to your answers! π
Hello @Eagle3386
--issue
, and I suppose --renew
would work fine. This is just how I set things for me, and my script was given as an example open to customization :)
# Push the DANE/TLSA keys to Dynu
>&2 echo '*** DANE/TLSA key generation and send to DNS (Dynu)'
# DANE-EE SPKI SHA2-256 (TLSA 3 1 2) hash generation
TLSA312_CURRENT=$(openssl pkey -in "$ACME_CRT_DIR/mydomain.com.key" -pubout -outform DER 2>/dev/null | sha256sum | cut -f1 -d ' ')
TLSA312_NEXT=$(openssl pkey -in "$ACME_CRT_DIR/mydomain.com.key.next" -pubout -outform DER 2>/dev/null | sha256sum | cut -f1 -d ' ')
&2 echo 'Getting Authorization Token...' Dynu_AT="$(curl -s -X GET https://api.dynu.com/v2/oauth2/token \ -H "accept: application/json" \ -u "$Dynu_ClientId:$Dynu_Secret" \ | jq -r .access_token)"
&2 echo 'Getting hostname ID...' Dynu_ID="$(curl -s -X GET https://api.dynu.com/v2/dns/getroot/mydomain.com \ -H "accept: application/json" \ -H "Authorization: Bearer $Dynu_AT" \ | jq -r .id)"
&2 echo 'Getting TLSA Records...' Dynu_TLSARecords="$(curl -s -X GET https://api.dynu.com/v2/dns/$Dynu_ID/record \ -H "accept: application/json" \ -H "Authorization: Bearer $Dynu_AT" \ | jq ".dnsRecords[] | select(.recordType == \"TLSA\")")"
Dynu_TLSARecToDel=$(echo "$Dynu_TLSARecords" | jq ". | select(.certificateAssociatedData != \"${TLSA312_CURRENT^^}\") | .id") Dynu_TLSACurExists=$(echo "$Dynu_TLSARecords" | jq ". | select(.certificateAssociatedData == \"${TLSA312_CURRENT^^}\") | .id")
Dynu_AddTLSARecord() { curl -s -X POST "https://api.dynu.com/v2/dns/$Dynu_ID/record" \ -H "accept: application/json" \ -H "Authorization: Bearer $Dynu_AT" \ -H "Content-Type: application/json" \ -d "{\"nodeName\":\"_443._tcp\",\"hostname\":\"_443._tcp.mydomain.com\",\"recordType\":\"TLSA\",\"ttl\":90,\"state\":true,\"certificateUsage\":3,\"selector\":1,\"matchingType\":1,\"certificateAssociatedData\":\"$1\"}" }
Dynu_DelTLSARecord() { curl -s -X DELETE "https://api.dynu.com/v2/dns/$Dynu_ID/record/$1" \ -H "accept: application/json" \ -H "Authorization: Bearer $Dynu_AT" }
if [ "$Dynu_TLSACurExists" ] then >&2 echo "Current TLSA Record exists." else >&2 echo "Current TLSA Record is missing, adding it..." Dynu_AddTLSARecord $TLSA312_CURRENT fi
&2 echo "Deleting records: $Dynu_TLSARecToDel" for REC in "$Dynu_TLSARecToDel"; do Dynu_DelTLSARecord $REC; done
&2 echo "Adding next TLSA Record..." Dynu_AddTLSARecord $TLSA312_NEXT
-------------------------------------
4. I suppose you could use the acme.sh hook, but I decided to not use it personally, and using acme.sh only to renew the certificate, and manage the rest from my script (my script is calling acme.sh while the logic of the hook is to have acme.sh to call a custom script).
@bolemo Awesome & almost near the finish line now, thanks! ππ»
Regarding your answers:
GET
gives you the full monty, yet a setting requires specifying "record pairs", e.g. TXT=<all current records that shall be kept + your new lines>
, as form-encoded data of a POST
request with that data not stored inside the request body, but within the URL as it would be a simple GET
requestβ¦ π).acme.sh
for this?
What I mean is which hook to use (--post-hook
, but how to check if renew succeeded then, or --reloadcmd
, --renew-hook
or is there yet another hook I should be better using) & how to use acme.sh
to call my DNS API plugin for setting the TLSA records?
Granted, I need to modify the plugin by defaulting to TXT
as the record type to be set, but also allow other types via adding an additional & optional argument to the corresponding method.Honestly, I thought Neil would give us something like a new --dane
argument that could then be used by supporting DNS API plugins to offer a method that gets the required values as arguments & just calls the DNS provider to set that data.
But right now, it seems like acme.sh
only supports the very bare minimum & everything else is left to any user crazy enough to deal with such "tinkering" of pushing TLSA record updates.
The entire process looks like something that should be implemented in acme.sh directly. Why is this not the case? It would be great to have a --dane
option or similar.
@StrangePeanut That's pretty much what I said in my comment right above your one. π ππ»
@Neilpang Any willingness to improve the situation here? Pretty please?! π₯Ήπ
Technically, acme.sh could automate pushing the DANE records, at least for all the DNS providers it already supports (as it already has API functions).
It would however require time and testing (& motivation), as there are 160 supported DNS API to adjust and test for that (math: ("headache" of a DNS API) x 160
)β¦
For the non supported DNS providers, a custom hook script (pretty much like some of you are doing) does the trick.
@Eagle3386, until acme.sh automates dane, if it ever does, you have no choice but to deal with ArtFiles API yourself, seems it is the only work left from you for this to work In your case.
@bolemo
Nice play on my "headache thing", therefore π.
However, the ππ» is for acme.sh
moving further, i.e. adding support for it.
But while we all wait for that to happen one day, can you please tell me (read: us Linux shell newbies) how I (we) can "call back" from such a "hook script" back into acme.sh
& most importantly into a DNS API plugin script so that its methods are used to add & remove the old & new DANE records? Because I simply don't know howβ¦ π
@bolemo
Nice play on my "headache thing", therefore π. However, the ππ» is for
acme.sh
moving further, i.e. adding support for it.But while we all wait for that to happen one day, can you please tell me (read: us Linux shell newbies) how I (we) can "call back" from such a "hook script" back into
acme.sh
& most importantly into a DNS API plugin script so that its methods are used to add & remove the old & new DANE records? Because I simply don't know howβ¦ π
Well, reread my post here.
I don't use acme.sh directly and I don't use any hook. What I do is using every month (cron) a custom script (and that script calls acme.sh only to renew the certs in a dane compliant way, nothing else, and then my script deals with deployment and pushing TLSA to my DNS provider).
my_custom_script
|
β’ β> acme.sh to generate/renew the certs
|
β’ β> deploy
|
β’ β> TLSA/DANE
|
β’ β> nginx, etc.
|
β’
exit
I am sure it can be done with little change with a post hook like --deployhook (using acme.sh primarily and it is acme.sh that calls a custom hook script to deal with TLSAβ¦)
acme.sh
|
β’ β> generate/renew the certs
|
β’ β> calling deploy hook script
. |
. β’ β> TLSA/DANE
. |
. β’ β> nginx, etc.
. |
β’ <β exit β’
|
β’
exit
You said you already use successfully a deploy hook to restart your nginx. So it seems all you have to do is add in that existing deploy hook script the code to push TLSA to your DNS provider. For that, you can use the logic of what I do in my script posted here and adapt the API calls for ArtFiles (the only headache part).
But while we all wait for that to happen one day, can you please tell me (read: us Linux shell newbies) how I (we) can "call back" from such a "hook script" back into
acme.sh
& most importantly into a DNS API plugin script so that its methods are used to add & remove the old & new DANE records? Because I simply don't know howβ¦ π
To reply to this more specifically, I donβt believe you can call back the acme.sh DNS API plugin yourself, as I donβt think they are able to deal with DANE specific TLSA records (what I was mentioning earlier about 160 x headaches). Unfortunately, you would need to do it using the DNS API in your own hook script.
@bolemo Thanks so much, this helped a lot!
Last question: given your 2nd scheme of running everything (where TLSA is done via hook script), are those variables containing username & password for the DNS API available in that very hook script? Because I neither want to have 2 files where they're stored nor want to deal with reading them for usage from disk twice.
You are welcome!
Last question: given your 2nd scheme of running everything (where TLSA is done via hook script), are those variables containing username & password for the DNS API available in that very hook script? Because I neither want to have 2 files where they're stored nor want to deal with reading them for usage from disk twice.
This is a very good question.
In my case, before I call acme.sh
in my script, I systematically set my dns provider credentials exporting the appropriate variables.
In fact, this has to be done only the first time, as after it is saved in ~/.acme.sh/account.conf
as per acme.sh documentation (same for ArtFiles: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_artfiles )
export AF_API_Username="api12345678"
export AF_API_Password="apiPassword"
So when using export β¦
before calling acme.sh
, the variables AF_API_Username
and AF_API_Password
will be available in the post hook script.
However, once they are set in ~/.acme.sh/account.conf
and you don't use the export
anymore, I believe it won't be available as is in the post hook.
So the best would be in your post hook script to add this line at the beginning (before you use the AF_API_β¦
variables):
. ~/.acme.sh/account.conf
That would add the content of ~/.acme.sh/account.conf
(hence the variables) to your post hook script, and AF_API_Username
, AF_API_Password
should then be available for you in your script.
Any success @Eagle3386 ?
Didn't have time due to private life tasks of higher priority. Planning on trying this in the next couple of weeks & will report back ASAP.
Planning on trying this in the next couple of weeks & will report back ASAP.
@Eagle3386, thanks so much for looking into it!
I had attempted to get a quick-and-dirty patch on PR #4087 β merging an old tlsa
branch into what at the time was the current main branch β which essentially did everything that was required. It just required quite a bit of polishing and a lot of testing with different DNS providers.
I still have the old code... somewhere :) ... but I'm the first one to admit being a terrible amateurish programmer with little or no knowledge of what I'm doing :) Nevertheless, whatever I did back then did work. Imagine to my surprise after @Neilpang closed that PR saying that it was "fixed", I let the auto-updater run by itself, and after a few months, I suddenly realised that all my emails were bouncing with invalid key validation errors...
AFAIK, right now, we just get an extra private key for the upcoming rotation cycle, which is just β of the job: a new public key needs to be created as well, and, using the DNS API, a TLSA record with the fingerprint needs to be published. So there is really a bit more that needs to be done.
Also note that I'm extremely limited in the number of external DNS providers (and their APIs) that I have access to; as such, my own patches were only tested on Cloudflare. They might not work elsewhere, especially on providers which don't give access to TLSA records from their APIs. Such providers will unfortunately have to be left out of this scenario (and let their users put some pressure to support it!). Nevertheless, I believe that a working solution that works for many providers is better than the current state-of-the-art, where it doesn't work with any of them, unless, of course, you either patch the code yourself, or create the external scripts as hooks as described earlier in this (now long!) thread.
For anyone interested using Cloudflare, I made a single binary tool in Go that takes in a current and next EC private key (generated by acme.sh) and automatically updates the TLSA records for a given domain using the Cloudflare API. I use it for my own mail server and it gives me a 100% score on Internet.nl's email test.
https://github.com/nixigaj/cf-tlsa-acmesh Instructions are in the readme.
--always-force-new-domain-key
should pre-generate the future (next) domain key pair after the new certificate is provisioned, so that--reloadcmd
can update TLSA records in advance of obtaining future certificates as part of the Current + Next DANE roll-over procedure. Pre-generated keys (if they exist) should be used for all future--always-force-new-domain-key
certificate provisioning.Key rotation requires future planning for DANE TLSA roll-over to account for DNS propagation delays and TLSA records TTL.
See slides 20-21 from the 2018 ICANN61 presentation by Viktor Dukhovni.
The Current + Next DANE roll-over procedure is: