Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save countless-integers/af84686a3bf95cc5c42bc5151da12265 to your computer and use it in GitHub Desktop.
Save countless-integers/af84686a3bf95cc5c42bc5151da12265 to your computer and use it in GitHub Desktop.
A quick and dirty script to help with checking page certificate expiry dates. It's in Python2 for compatibility reasons, so logic was redacted to serve as a template and not ready-made solution.
#!/usr/bin/env python2
from os import environ
import datetime
import socket
import ssl
import logging
from urllib2 import Request, urlopen, URLError, HTTPError
import json
# @see: https://serverlesscode.com/post/ssl-expiration-alerts-with-lambda/
class AlreadyExpired(Exception):
pass
def ssl_expiry_datetime(hostname):
ssl_date_fmt = r'%b %d %H:%M:%S %Y %Z'
context = ssl.create_default_context()
conn = context.wrap_socket(
socket.socket(socket.AF_INET),
server_hostname=hostname,
)
conn.settimeout(3.0)
conn.connect((hostname, 443))
ssl_info = conn.getpeercert()
return datetime.datetime.strptime(ssl_info['notAfter'], ssl_date_fmt)
def ssl_valid_time_remaining(hostname):
expires = ssl_expiry_datetime(hostname)
logging.debug(
"SSL cert for %s expires at %s",
hostname, expires.isoformat()
)
return expires - datetime.datetime.utcnow()
def ssl_expires_in(hostname, buffer_days=14):
"""Check if certificate expires withing the specified time buffer.
Return False if not, datetime if yes, rais AlreadyExpired if past
"""
remaining = ssl_valid_time_remaining(hostname)
if remaining < datetime.timedelta(days=0):
raise AlreadyExpired("Cert expired %s days ago" % remaining.days)
elif remaining < datetime.timedelta(days=buffer_days):
return remaining
else:
return False
def notify_via_slack(report):
slack_channel = environ['SLACK_CHANNEL']
slack_message = {
'username': 'Cert Expiry Bot',
'icon_emoji': ':closed_lock_with_key:',
'channel': slack_channel,
'text': report
}
webhook_url = environ['SLACK_WEBHOOK']
req = Request(webhook_url, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Slack notification posted to %s", slack_channel)
except HTTPError as e:
logger.error("Slack request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Slack server connection failed: %s", e.reason)
if __name__ == "__main__":
logging.basicConfig(format='[%(asctime)-15s][%(levelname)s] %(message)s')
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
report = ['Domains expiring in the next {} days:'.format(args.expires_in)]
all_domains = [] # write your own logic to fill this in, e.g. use cli arguments
for domain in all_domains:
try:
result = ssl_expires_in(domain, args.expires_in)
if result:
warning = "expiry for {} in {} days (last update at {})".format(domain, result.days, updated_at)
logging.warning(warning)
report.append(warning)
except AlreadyExpired:
error = "{} already expired (last update at {})".format(domain, updated_at)
logging.error(error)
report.append(error)
except Exception as e:
exception_class = type(e).__name__
exception_message = "Exception ({}) during {} check: {}".format(exception_class, domain, str(e))
logging.error(exception_message)
report.append(exception_message)
if notify_via_slack:
report.append('Scanned {} domains'.format(len(all_domains)))
notify_via_slack("\n".join(report))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment