Python Notes: server cert check
- The information presented here is intended for educational use.
- The information presented here is provided free of charge, as-is, with no warranty of any kind.
- Edit: 2025-10-24
server_cert_check
#!/bin/python3.9
'''
=============================================================================
title : /var/www/cgi-bin/server_cert_check_100.py
author : Neil Rieck
created: 2025-03-05
notes :
1) this is a quick hack which should be good enough for Bell-ATS use
2) This program can be run interatively or from cron
3) if we were a bit larger then I would read URL_LIST from a database
4) ping fails should be logged in a database (limit emails to one per day?)
ver who when what
--- --- ------ -----------------------------------------------------------
100 NSR 250305 derived from bell_ats_server_health_100.py
NSR 250306 started adding code to check certs
NSR 241024 changes for public use at my personal website
=============================================================================
'''
import platform
from time import sleep
import smtplib
import datetime
import sys
import email.utils # needed for rfc822 support
import requests
# 1) the next line fixed our python3.6 environment
# 2) it also fixed our python3.9 environment after I reinstalled python3.9
# 3) the next line is required when fetching from OpenVMS systems
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL:@SECLEVEL=1'
#
# ===========================================================================
#
# constants
#
PGM = "server_cert_check"
VER = "100"
#
# note: URL_LIST4 is all that is really required after I retire next month
#
URL_LIST4 = [ # 4 plus one alias
"ats.on.bell.ca", # alias
"kawc96.on.bell.ca", # OpenVMS prod (KAWC90)
"kawc09.on.bell.ca", # OpenVMS dvlp (KAWC09)
"kawc0f.on.bell.ca", # Linux prod
"kawc4n.on.bell.ca" # Linux dvlp
]
URL_LIST1 = [ # 1 address (program development)
"neilrieck.net"
]
#
# globals
#
this_host = ""
DEBUG = 2
#
# ===========================================================================
#
# code area begins
#
def test_cert(cert):
state = 0 # init
url = f"https://{cert}"
if DEBUG >= 1:
print(f"-i-debug: {DEBUG}")
print(f"-i-url: {url}")
try:
# CAVEATS:
# 1) If you connect with "verify=False" then the certificate is not loaded for testing.
# So at this point, I do not know if an expired certificate will ever be seen.
# 2) If the url fails with requests.get but works from your browser, then the web server's
# chain file (which contains the root and intermediate certs) must be updated.
# Browsers maintain a cache of known root servers; this subsystem does not)
with requests.get(url, stream=True, timeout=(5, 4)) as response:
certificate_info = response.raw.connection.sock.getpeercert()
state = 1 # fetch was successful
if type(certificate_info) != dict:
certificate_info = dict(certificate_info)
if DEBUG >= 2:
print("cert: ", certificate_info)
print("="*60)
subject = dict(x[0] for x in certificate_info['subject'])
print("commonName:", subject['commonName'])
issuer = dict(x[0] for x in certificate_info['issuer'])
print("issuer:", issuer['commonName'])
# ----------------------------------------
expiry = certificate_info['notAfter']
print("-i-cert expiry:", expiry) # rfc822 datetime stamp
tup = email.utils.parsedate_tz(expiry) #
utc = email.utils.mktime_tz(tup) # utc time stamp (integer)
dt1 = datetime.datetime.fromtimestamp(utc) #
dt0 = datetime.datetime.now() #
# ----------------------------------------
dt9 = dt1 - dt0 #
days = dt9.days #
if days > 30:
msg = f"-i-certificate '{cert}' is okay"
elif days > 0:
msg = f"-w-certificate '{cert}' will expire in {days} days"
else:
msg = f"-e-certificate '{cert}' is expired"
except Exception as e: #
state = 2 # some kind of error (connect, or verify)
msg = f"-e-error: {e} while fetching certificate '{cert}'"
return state, msg #
#
# the work horse
#
def test_url_list(this_host=""):
total = fail = 0
for cert in URL_LIST1:
for retry in range(1, 5): #
if retry < 4: #
print(f'-i-testing cert: {cert} try: {retry}')
state, msg = test_cert(cert) #
if DEBUG >= 1:
print(f"-i-debug-state: {state} msg: {msg}")
if state == 1: # we made a connection...
break # ...so exit from here
else:
print(f"-w-state: {state} so falling through")
else: # oops, no more counts remaining
msg = f"-e-could not connect/verify '{cert}' after {retry} tries"
print(msg) #
break #
print(f"-w-oops, something went wrong on try: {retry}")
sleep(1) #
# ------------------------------------------
if msg[0:3] == "-i-": #
print(f"-i-resp: {msg} (pass)")
else:
print(f"-e-resp: {msg} (fail)")
stamp = datetime.datetime.now().strftime("%Y%m%d.%H%M%S")
msg = (
f"reporting host: {this_host}\n"
f"program: {PGM}\n"
f"time: {stamp}\n"
f"message: {msg}"
)
mail_option = 1 # 0=none; 1=developer; 2=distribution list; 3=both
if (mail_option & 1) == 1:
send_mail(msg, cert, "n.rieck@bell.net")
if (mail_option & 2) == 2:
send_mail(msg, cert, "ats_adm_list")
fail += 1
total += 1
sleep(2)
print("\nreport card:")
print(f" total tests: {total}")
print(f" fails : {fail}")
print(f" passes : {total - fail}")
print("="*40)
if fail != 0:
return(1)
return(0)
#
# send mail
#
def send_mail(msg="no-message", url="", dst="neil.rieck@bell.ca"):
global this_host
if url == "":
details = ""
else:
details = f" for: {url}"
try:
print(f"-i-mailing to: {dst}")
sender = f"root@{this_host}"
receivers = dst
message = (
f"From: {sender}\n"
f"To: {receivers}\n"
f"Subject: certificate test failed {details}\n\n"
f"{msg}")
print(message)
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, message)
except Exception as e:
print(f"-e-error: {e}")
#
# that grand poobah
#
def main():
global this_host
print(f"\n{PGM}-{VER}")
print("="*40)
this_host = platform.node()
stamp = datetime.datetime.now().strftime("%Y%m%d.%H%M%S")
print(f"-i-running on host: {this_host} at: {stamp}")
rc = test_url_list(this_host)
print("adios")
sys.exit(rc)
#
# catch-all
#
if __name__ == "__main__":
main()
#
Links

Back to
Home
Neil Rieck
Waterloo, Ontario, Canada.