I manage SSL certificate requests (and renewals) at work and the number of certs in use seems to be growing every year. There’s a nice cozy 3 year expiry on most of them which means to me just enough time for them to be potentially forgotten and cause a mad scurry come renewal time (or worse, have them expire and have a service outage as a result).
Our CA has a recently unveiled service which will scan our public IPS and report on detected certificates close to expiry which is handy, but we have a lot of servers on non-routable and / or firewalled addresses, so an internal scan is the only way to cover them all.
There seem to be at least a couple of other published approaches in the google including ssl certificate expiration check and the neat ssl-cert-check script at prefetch.net which will take a list of servers and express the expiry date. I wanted something slightly different (more minimalist): as we’re using the excellent zabbix for general system monitoring which especially likes system commands or scripts which take a single parameter and spit out a single return value.
In this case that parameter is a server name or IP address, returning either the number of days until SSL cert expiry, or the certificate issuer, depending on which version of the script is called. As I said, we’re using this with zabbix but this is just a command line script usable for quick on the spot checks or could easily be incorporated into another monitoring / alerting system.
A nice simple method I would probably use if I didn’t have zabbix would be to incorporate this into a quick and dirty loop script which periodically queries a list of ips or subnets and fires off an email if the ‘days to expiry’ is below a certain value (exactly what zabbix does now). It could be made a bit more elegant perhaps by using a bit of nmap to build a fast list of responding servers to query, but depends what you want, how much time you have, and how much of a stickler you are for efficiency =)
(Command line for all scripts is simply:
./script-name server.name.or.ip.address
)
Script: sslcheck-expiry
#!/bin/bash
# Simple SSL cert days-till-expiry check script
# by Glen Scott, www.glenscott.net
openssl_output=$(echo "
GET / HTTP/1.0
EOT" \
| openssl s_client -connect $1:443 2>&1);
if [[ "$openssl_output" = *"-----BEGIN CERTIFICATE-----"* ]]; then
cert_expiry_date=$(echo "$openssl_output" \
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \
| openssl x509 -enddate \
| awk -F= ' /notAfter/ { printf("%s\n",$NF); } ');
seconds_until_expiry=$(echo "$(date --date="$cert_expiry_date" +%s) - $(date +%s)" |bc);
days_until_expiry=$(echo "$seconds_until_expiry/(60*60*24)" |bc);
if [[ $days_until_expiry -ge 0 ]]; then
echo "$days_until_expiry";
exit 0
else
echo "EXPIRED ($days_until_expiry days)";
exit 0
fi
else
echo "NOT_FOUND";
exit 1
Checking the issuer as well as the expiry
Because we have a bunch of servers setup for dev, test or other purposes with self signed “snake oil” certs and I dont really care about the certs on those, I wanted a method to determine the issuer. (Zabbix then has some logic which only bothers to email us if a cert is about to expire AND is from a real CA.)
Script: sslcheck-issuer-o
Returns the “O” (Organisation) value from the issuer string.
#!/bin/bash
# Simple SSL cert get-issuer-O
# by Glen Scott, www.glenscott.net
openssl_output=$(echo "
GET / HTTP/1.0
EOT" \
| openssl s_client -connect $1:443 2>&1);
if [[ "$openssl_output" = *"-----BEGIN CERTIFICATE-----"* ]]; then
cert_issuer=$(echo "$openssl_output" \
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \
| openssl x509 -noout -issuer -nameopt sname \
| tr '/' '\n' | grep O= | cut -c3- );
echo "$cert_issuer";
exit 0
else
echo "NOT_FOUND";
exit 1
fi
Script: sslcheck-issuer-cn
If for some reason you want the CN value from the issuer string, use this instead.
#!/bin/bash
# Simple SSL cert get-issuer-CN
# by Glen Scott, www.glenscott.net
openssl_output=$(echo "
GET / HTTP/1.0
EOT" \
| openssl s_client -connect $1:443 2>&1);
if [[ "$openssl_output" = *"-----BEGIN CERTIFICATE-----"* ]]; then
cert_issuer=$(echo "$openssl_output" \
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \
| openssl x509 -noout -issuer -nameopt sname \
| tr '/' '\n' | grep CN= | cut -c4- );
echo "$cert_issuer";
exit 0
else
echo "NOT_FOUND";
exit 1
fi
Additional notes:
All scripts use the openssl s_client function to connect to the first string (assuming IP or server name) on default port 443. It echoes some HTTP GET requests and an EOT, otherwise openssl will sit there until timeout waiting for something to happen. If the remote connection sends a certificate down the pipe (identified by the presence of “”—–BEGIN CERTIFICATE—–“) it will process it, otherwise it returns the value ‘NOT FOUND’, which is a catch all for no certificate, a malformed certificate, a network timeout and so on.
A quick glance at these will reveal a lot of duplication: indeed the last two only differ by one line. This is on purpose; I actually started out building a do-everything script with a switch to determine behavior (like this script I subsequently found). This would be a lot more efficient in terms of network requests, you could retrieve the cert once and analyse it in multiple ways. Unfortunately it turns out zabbix really only likes dealing with the one parameter – a minor issue for which I will forgive it – so I’ve broken this out into three smaller scripts.
OpenSSL will only return the date in a format like “Sat Jan 1 17:15:00 WAST 2010”. I was starting to figure out how to chop it up into a usable format like DDMMYYYY using sed, awk, cut and the rest but discovered to my surprise that the unix date utility understands the string just fine as is. The date is converted to seconds since epoch and the ‘bc’ utility does some math on it, returning a rounded value in days.
Instead of awk in the last two I’ve used a handy-dandy utility called, simply, ‘text replace’ (tr). I actually don’t use awk, sed and regular expressions much and am correspondingly unfamiliar with the syntax, thus was happy to discover and use this nifty shortcut. It’s not as powerful as sed/awk but is great for a simple character replace like this, especially when the process of trying to manipulate both kinds of slashes in the issuer string and replace them with newlines
'\n'
is frying my brain.
As always, your mileage may vary, particularly regarding any differences in the basic syntax of the various utilities across platforms (I’m using RHEL). ‘date’ for example is very forgiving here, this might not be the same everywhere. I’ve also had issues in the past with the syntax of SED on OSX.
Hope someone finds this helpful, and I will write up a brief post on using zabbix to manage SSL cert expiry at some point as well.