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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
#!/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"))
|