Last year’s new years eve, I got a call from my client. They said their website was infected by a virus and no one can access it.
Now, my client runs a juice shop, and had no idea about how the web technically works, so I discarded the “virus” issue but he said site can’t be accessed so I fired up Firefox in my phone and I saw the Your connection is not secure
page.
Ever since Let’s Encrypt came out of beta, I’ve used it to convert all my and my clients’ sites to secure connections via HTTPS. I had set up a cron as instructed by certbot to renew the certificates regularly, but it used to fail every once in a while because I didn’t update the python packages, or something like that. Let’s Encrypt is kind enough to send a mail before expiring, but initial installations were done by an employee and so the mails were being sent to her address.
Since it was 31st of December, I was at a party and no where near my machine with which I could ssh into the server and slay this beast. I had JuiceSSH on my phone using which I SSHed into the server (thank god I had added my keys as authorized), updated the packages and renewed the certificates.
All this while people around me were counting backwards from 10.
This wasn’t even the first time this was happening so I decided then and there that I am going to solve this problem for myself. I carefully chose not to make it my new year’s resolution otherwise I’ll be telling the same story next year.
So on 1st January, I wrote a Ruby script (more about which in a minute) and kept improving it to catch all sorts of red flags with HTTPS installations including if a certificate is revoked, affected by the Symantec mess, is self-signed or is past/near the expiry date, validating the chain of trust, etc. The script eventually converted into a nice little piece of web app called Monitor Certificates, but let’s get back to talking about the script now.
First thing you’ll need to do is procure the certificate from a domain. Surprisingly, this was difficult to achieve. I am a big fan of [HTTParty](https://github.com/jnunemaker/httparty/)
for making requests with Ruby, but unfortunately I couldn't find a way to get it to return certificates.
So I went one layer down the abstraction and used Net::HTTP
. To get a certificate, you have to run
require 'net/http'require 'openssl'domain_name = "example.com"uri = URI::HTTPS.build(host: domain_name)response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)cert = response.peer_cert
This gives you an object of certificate in the cert
variable. This variable is of the type OpenSSL::X509::Certificate
. These are called X.509 certificates, and they are the standard for public key certificates.
X.509 certificates give us a lot of details about the certificate including who issued it, when it was issued, when it will expire, how to find if it is revoked, and a plethora of other useful information.
To find the date when the certificate will be invalid, you just need to call cert.not_after
. This returns a Time object which tells us when the certificate will expire.
So if you want to be notified everyday for 2 weeks before the certificate is supposed to expire, your script will look like -
require 'net/http'require 'openssl'domain_name = "example.com"uri = URI::HTTPS.build(host: domain_name)response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)cert = response.peer_certtwo_weeks = 14 * 86400 # 14 * One Dayif Time.now + two_weeks > cert.not_after # send remindersend
You can use lots of things to send these reminders. I’ve now implemented SMS, Emails (to multiple people), Slack and Stride in my app.
I have some friends who use this script to push mobiles notifications to themselves (using Pusher), web notifications, Geckoboard updates, adding to To-Do lists, adding to Trello boards, etc. Any communication platform is fair game.
For example, if you’re on a Mac, there’s a neat little command you can use to send a notification to yourself.
So I’ll incorporate that command in our script to send us notifications.
require 'net/http'require 'openssl'domain_name = "example.com"uri = URI::HTTPS.build(host: domain_name)response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)cert = response.peer_certtwo_weeks = 14 * 86400 # 14 * One Dayif Time.now + two_weeks > cert.not_after time_remaining = (cert.not_after - Time.now) days_remaining = time_remaining / 86400 `osascript -e 'display notification "Certificate for #{domain_name} will expire in #{days_remaining.to_i} days. Please renew soon." with title "#{domain_name}"'`end
I removed the if
condition and ran the script and the output looks like this today
Now all you need to do is add it to your crontab and it’ll keep reminding you when your certificates are getting close to renewal.
Obviously, the script that I’ve shown you isn’t the most elegant piece of software but it gets the job done. If you want to monitor multiple domains, just turn the script into a method and run the method against each element of your array of domain_names
.
We’ve just scratched the surface here but it’s a solid start to build your monitoring infrastructure and more features here to make sure your certificate hasn’t been revoked, or if it’s a paid certificate, who is the issuer.
I have added all this and more in my app to help keep on top of HTTPS issues.
Hope this was helpful for people like me who keep breaking their Let’s Encrypt automation, or forgetting that a cert is up for renewal and management forgot to tell you that they are getting mails from vendors to issue a new one. 😁
Happy Coding!