We’d like a user on our site to be able to map his own domain to our servers, and then we’d autoprovision an appropriate certificate for that domain. With SNI this was working well, but without SNI validation we’d like to try dns-01.
The workflow would be:
User enters his domain.
We request the validation TXT token from Let’s Encrypt.
We print to the user the record he needs to add to his DNS servers.
[Let’s Encrypt is asked to validate the record and issue a certificate.]
We use the issued certificate and key.
This works for a live call to certbot. For example, if I introduce a manual-auth-hook that merely sleeps for five minutes, then I manually add the TXT entry to the appropriate DNS, and then sleep finishes and Let’s Encrypt does validation, generating the ticket. So this captures the general flow, but of course it is handled via a single call to certbot. Is it possible to break this into two steps so that we can request the TXT; add it to our servers; and then make another request to create the certificate (using the existing validation key)?
Otherwise the other approach seems to be to use A record validation, asking the user to enter an A record for his host, pointing to ours, before we make the certbot call. Open to other ideas though.
Yes, this is definitely possible (for example, see the implementation at https://zerossl.com), however I do not believe it’s feasible with the Certbot client. It may take some coding on your end to set something like this up, most notably some form of state persistence. You could use any of the existing ACME libraries to build this.
acme.sh client works this way using dns challenge in manual mode. You perform the command in manual mode using the dns challenge for your domains and it gives you the tokens you should add to your dns and it stops. Once done you need to execute again the acme.sh client.
This is a valid approach but the customer needs to add a CNAME record.
Example:
Customer wants to issue a cert for customerdomain.tld and www.customerdomain.tld and let's say your domain would be auth.yourdnsserver.tld.
1.- Your customer must create 2 CNAME records pointing to your domain:
_acme-challenge.customerdomain.tld IN CNAME customerdomain.tld.auth.yourdnsserver.tld
_acme-challenge.www.customerdomain.tld IN CNAME www.customerdomain.tld.auth.yourdnsserver.tld
2.- When you try to issue a certificate and receive the tokens, you create a TXT for evey domain in your own dns server:
customerdomain.tld.auth.yourdnsserver.tld IN TXT "token1"
www.customerdomain.tld.auth.yourdnsserver.tld IN TXT "token2"
3.- When Let's Encrypt tries to validate the domains, it will follow the CNAME till your auth domain with the right token.
acme.sh follows the same protocol we’d like to follow, so thanks for that pointer. Unfortunately I’m having trouble finding a clean Javascript client that acts similarly, but I’m going to dig deeper into greenlock.js which, though (extremely) poorly documented, looks like a potential solution.
Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.
Please use dns api mode instead.
Is this a limitation of Let’s Encrypt or of acme.sh? A renewal requires generating a new challenge TXT record? Just clarifying.
This is how the ACME protocol, and certificate issuance in general works. You must continually demonstrate control of the domain. A single valid authorization (i.e. an instance of a TXT record being set) will only authorize you to issue certificates for that domain for some arbitrary period of time.
This affects all the challenge types - HTTP, DNS and TLS-SNI.
That is why @sahsanu suggested that the users point a CNAME record at your servers instead, as it would allow you to update the TXT record on your side, without bothering the user to update their DNS records every 60-90 days.
For your overall task it seems to me that you would greatly benefit from going over the ACME spec itself - it is not too hard to read and you are already kind of in the weeds when it comes to your integration.
We ended up going with HTTP, and I did read the specification. Other than the signing pieces it’s relatively straight-forward. Thanks for your feedback!