#!/usr/bin/env python3 # File: alert.py # Location: https://gitlab.com/bgstack15/freeipa-cert-alert # Author: bgstack15 # Startdate: 2021-10-27 14:00 # SPDX-License-Identifier: GPL-3.0 # Title: Script that Alerts For Expiring Certs # Purpose: Send me alerts for certs that are about to expire # History: # 2022-12-18 added PASTDAYS option # 2024-05-03 add remove replaced certs option # Usage: # Set env: FREEIPA_SERVER FREEIPA_USERNAME FREEIPA_PASSWORD DAYS PASTDAYS FREEIPA_HIDE_REPLACED_CERTS # References: # https://python-freeipa.readthedocs.io/en/latest/ # https://stackoverflow.com/questions/72899/how-do-i-sort-a-list-of-dictionaries-by-a-value-of-the-dictionary/73050#73050 # https://stackoverflow.com/questions/6871016/adding-days-to-a-date-in-python/6871482#6871482 # https://stackoverflow.com/questions/24027863/convert-a-utc-time-to-epoch # https://stackoverflow.com/questions/9989334/create-nice-column-output-in-python/9996049#9996049 # Improve: # Dependencies: # Somehow this is not a requisite component of freeipa! Those are named python3-ipa* # fedora-req: python3-freeipa import python_freeipa, json, datetime, os, sys, re import dateutil.parser as dparser # Functions def show_list(inlist): col1max = 0 col2max = 0 col3max = 0 for i in inlist: col1max = max(len(i['valid_not_before']),col1max) col2max = max(len(i['valid_not_after']),col2max) col3max = max(len(i['subject']),col3max) col1max = col1max+2 col2max = col2max+2 if len(inlist) > 0: a = "Not valid before" b = "Not valid after" c = "Subject" print(f"{a:<{col1max}} {b:<{col2max}} {c:<{col3max}}") for i in inlist: print(f"{i['valid_not_before']:<{col1max}} {i['valid_not_after']:<{col2max}} {i['subject']:<{col3max}}") def hide_replaced_certs(certlist,client): """ Remove from certlist any certs that have been replaced already. This is defined as a cert whose subject name exists as a cert with a further-out validto date. Args: certlist: the list of objects from python_freeipa.cert_find() client: the python_freeipa client object Returns: list: certlist with any superseded certificates removed. """ # future: YYYY-mm-dd of the end date of the search that generated certlist. #print(f"Got certlist {certlist}") newlist = [] for i in certlist: # need to calculate expiration time of this cert, so we can look for a newer cert than it. var future is irrelevant here because it's only what generated this list. this_future = dparser.parse(i["valid_not_after"]).strftime("%F") b = client.cert_find(o_validnotafter_from = this_future,subject = re.sub(",O=.*$","",re.sub("^CN=","",i["subject"]))) if not ("count" in b and b["count"] > 0): newlist.append(i) return newlist def get_client(server, username, password): """ Returns the freeipa client object. Example: client = get_client(os.getenv("FREEIPA_SERVER"),os.getenv("FREEIPA_USERNAME"),os.getenv("FREEIPA_PASSWORD")) """ client = python_freeipa.ClientMeta(server) client.login(username,password) return client def get_expiring_certs(client, days, pastdays): days = abs(int(days)) pastdays = abs(int(pastdays)) today = str(datetime.date.today() + datetime.timedelta(days=-pastdays)) future = str(datetime.date.today() + datetime.timedelta(days=days)) results = client.cert_find(o_validnotafter_from=today,o_validnotafter_to=future) certs = results['result'] return certs def alert_certs(client, days, pastdays, _hide_replaced_certs = "false"): """ Given the client object, and number of days ahead, and pastdays (from today), find the certs that are expiring in this range. If you pass a truthy-like object to _hide_replaced_certs, then it will check if a newer cert matches the CN, and remove this cert from the list if it finds any newer ones. Example: alert_certs(client, DAYS, PASTDAYS, os.getenv("FREEIPA_HIDE_REPLACED_CERTS","false")) """ days = abs(int(days)) pastdays = abs(int(pastdays)) if type(_hide_replaced_certs) == str and _hide_replaced_certs.lower() in ["false","f","0",""]: _hide_replaced_certs = False elif _hide_replaced_certs in [False,0]: _hide_replaced_certs = False else: try: _hide_replaced_certs = bool(_hide_replaced_certs) except: _hide_replaced_certs = False certs = get_expiring_certs(client, days, pastdays) if _hide_replaced_certs: certs = hide_replaced_certs(certs,client) # Sort certs = sorted(certs,key=lambda d: int(dparser.parse(d['valid_not_after']).strftime('%s'))) if len(certs) > 0: today = str(datetime.date.today() + datetime.timedelta(days=-pastdays)) print(f"Certificates expiring within {days+pastdays} days from {today}") show_list(certs) if "__main__" == __name__: # Main DAYS = os.getenv("DAYS",default=60) try: DAYS = int(DAYS) except: DAYS = 60 PASTDAYS = os.getenv("PASTDAYS",default=0) try: PASTDAYS = int(PASTDAYS) except: PASTDAYS = 60 client = get_client(os.getenv("FREEIPA_SERVER"),os.getenv("FREEIPA_USERNAME"),os.getenv("FREEIPA_PASSWORD")) alert_certs(client, DAYS, PASTDAYS, os.getenv("FREEIPA_HIDE_REPLACED_CERTS","false"))