guest@mukunda.com:/blog# ./cat 2022/certbot-with-dns-challenge.txt Name: Certbot with DNS Challenge Date: July 23rd, 2022 Over time with my test environments I've slowly improved my SSL certificate setup. It comes naturally when you face odd problems during testing due to certificate validation errors, especially when the product you're dealing with keeps quiet about connection failure reasons. At some point I wrote a few scripts to easily issue certificates to test domains from a selfmade CA certificate, but the problem with this approach is issues with revocation lists (since the CA doesn't really exist) and getting the root certificate installed on client machines. Just a whole lot of hassle when testing. So, today I took the time to get a proper testing setup. Firstly, a real domain--I'll use mukunda.com, even for my local machines. Experienced admins will always warn you against using fake domains or TLDs for testing, and I've pretty much learned the hard way about .local, .lan, and other fake domains. Let's Encrypt provides automated certificate signing via the ACME protocol. With Certbot, you can script interaction with Let's Encrypt to automatically generate and update certificates. I'll be making a wildcard certificate for *.mukunda.com. Once certbot is installed, it's pretty straightforward, but there are a lot of modules to make things easier for certain setups or web servers. For this case, I just want the basic certificates for testing purposes. I'm on Windows, and I use it like this: certbot certonly --manual ` --manual-auth-hook "C:\me\certbot-auth-hook.py" ` -d *.mukunda.com This starts a wildcard certificate generation process. First you will get the ACME challenge, and you need to update a DNS record to satisfy it. Certbot ships with plugins that handle DNS updates for many hosts, but I didn't see one for Dreamhost. For Dreamhost, I wrote a small script to do the update via their API. The --manual-auth-hook option tells certbot to call a custom script to perform the DNS updates. It sets a few environment variables for the script to handle, mainly: CERTBOT_DOMAIN - The domain being challenged. CERTBOT_VALIDATION - The validation string to apply. My script looks something like this, where it basically updates my domain and then waits for the DNS propagation change (which certbot does not handle). <...> certbot_domain = os.environ.get("CERTBOT_DOMAIN") certbot_validation = os.environ.get("CERTBOT_VALIDATION") print("Domain:", certbot_domain) print("Validation string:", certbot_validation) if certbot_domain == "mukunda.com": update_dreamhost_dns("_acme-challenge.mukunda.com", "TXT", certbot_validation) print("Sleeping for 10 minutes to allow DNS to propagate.") time.sleep(600) else: print("Unhandled domain:", certbot_domain); If you want a reference to the full script, see here. While developing and testing, you can check the letsencrypt.log file to see any errors that your script generated during the last attempt. Keep in mind that there are limits enforced for ACME challenges from Let's Encrypt, mainly that you can only make 5 requests per hour, so you want to work out any script testing before making actual attempts. Once it succeeds, your crisp certificates will be saved to the default location. The domain generation parameters are saved in the certbot data folder under "renewal", so you can simply run certbot renew to re-run any commands to renew your certificates. The certificates otherwise expire after 3 months. On Windows, certbot automatically adds a system task to renew periodically. Here's the final output: Send the author a comment << Index