Jan 01, 2016

SSL certificate expiry date calendar

There used to be a service provided by jcs (lobste.rs) that was able to offer the functionality in a webservice: domainical - add domains in batch and have a hosted .ics file on-line to sync to.

I learned of this mid itch-scratching. So it must be a common need that some handle by manually setting up email alerts. For a lot of (personal?) domains Let's Encrypt will probably amend this need, but there are still commercial EV certificates renewals not yet automated. I did get to know more about Pythons datetime and timezone functions indexing in Lists and file writing modes. The basis formed a script I made to convert a list of birthdays to an iCal file, importable by Thunderbird Lightning.

First, compile a list of hosts to watch into a file (domains.txt) and gather the expiry dates and some useful info from a bash script that writes a .csv

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/usr/bin/env bash

for host in $(cat domains.txt); do
    { echo 'hostname='$host;
    echo $host |\
    openssl s_client -connect $host:443 -servername $host 2>/dev/null |\
    openssl x509 -noout -dates -issuer -subject;
    whois $host | grep -E '(Admin|Tech|Billing) Email' | sed 's/\:/\=/';
    } |\
    paste -s -d';';
done

And go through that with the next script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env python3
#
# SSL Expiry Calendar: convert csv output of s_client and whois to .ics file, readable by thunderbird-lightning

#import ssl, time
#https://docs.python.org/3.4/library/ssl.html#module-ssl

import sys, csv, pytz, time
from datetime import datetime, tzinfo
from icalendar import Calendar, Event, vCalAddress, vText

cal = Calendar()
cal.add('prodid', '-//py3 csv2vcard thunderbird//sslcal.py//')
cal.add('version', '0.2')

with open(sys.argv[1], newline='') as csvfile, open('output.ics', 'wb') as icsfile:

    data = csv.reader(csvfile, delimiter=';', quotechar='|', skipinitialspace=True)
    #header = csv.DictReader(csvfile, fieldnames='Subject')

    for row in data:

        hostname = row[0].split('=')[1]
        cert_register = row[1].split('=')[1]
        cert_expiry = row[2].split('=')[1]

        issuer = row[3]
        subject = row[4]

        email = ''
        # saw how to do this at http://www.effbot.org/zone/python-list.htm
        for col in row[5:]:
             email += ' | ' + col

        notes = issuer + subject + email

        # Time relevant ops

        local_tz = pytz.timezone("Europe/Berlin")
        utc = pytz.utc
        fmt = "%b %d %H:%M:%S %Y %Z"
        date_register = datetime.strptime(cert_register, fmt).replace(tzinfo=utc)
        date_expiry = datetime.strptime(cert_expiry, fmt).replace(tzinfo=utc)

        # Writing the Event Data

        event = Event()

        event.add('summary', hostname + ' SSL expires')
        event.add('description', notes)
        event.add('dtstamp', date_register.astimezone(local_tz))
        event.add('dtstart', date_expiry.astimezone(local_tz))
        event.add('dtend', date_expiry.astimezone(local_tz))

        cal.add_component(event)

    icsfile.write(cal.to_ical())

Now to go more towards the use case, add attendees and alerting before cal.add_component

    attendee = vCalAddress('MAILTO:me@example.com')
    attendee.params['cn'] = vText('admin')
    attendee.params['ROLE'] = vText('REQ-PARTICIPANT')
    event.add('attendee', attendee, encode=0)

I haven't figured out the alert part yet. The properties get mentioned in the icalendar module though.

    #event.add('valarm', {'trigger': '-PT9D', 'action': 'alert', 'description': 'Reminder'})
    event.add('action', 'alert')
    event.add('trigger', '-PT9D')