[issue-4] [add] check:tls_certificate

This commit is contained in:
Christian Fraß 2023-03-22 14:22:54 +01:00
parent 554ca49c49
commit 24ec4d53ed
9 changed files with 150 additions and 7 deletions

View file

@ -15,8 +15,6 @@
]
},
"includes": [
"script.hmdl.json",
"file_state.hmdl.json",
"generic_remote.hmdl.json"
"tls_certificate.hmdl.json"
]
}

View file

@ -0,0 +1,19 @@
{
"checks": [
{
"name": "test1",
"kind": "tls_certificate",
"parameters": {
"host": "greenscale.de",
"expiry_threshold": 50
}
},
{
"name": "test2",
"kind": "tls_certificate",
"parameters": {
"host": "chemnitz-gesundheit.de"
}
}
]
}

View file

@ -17,6 +17,8 @@
"checks.file_state.timestamp_implausible": "Datei ist scheinbar aus der Zukunft",
"checks.file_state.too_old": "Datei ist zu alt",
"checks.file_state.too_big": "Datei ist zu groß",
"checks.tls_certificate.not_obtainable": "TLS-Zertifikat nicht abrufbar; evtl. bereits augelaufen",
"checks.tls_certificate.expires_soon": "TLS-Zertifikat läuft bald aus",
"checks.generic_remote.overflow": "Laufwerk fast voll",
"checks.http_request.request_failed": "HTTP-Abfrage fehlgeschlagen",
"checks.http_request.status_code_mismatch": "Status-Code {{status_code_actual}} stimmt nicht mit dem erwarteten Wert {{status_code_expected}} überein",

View file

@ -17,6 +17,8 @@
"checks.file_state.timestamp_implausible": "file is apparently from the future",
"checks.file_state.too_old": "file is too old",
"checks.file_state.too_big": "file is too big",
"checks.tls_certificate.not_obtainable": "TLS certificate not obtainable; maybe already expired",
"checks.tls_certificate.expires_soon": "TLS certificate will expire soon",
"checks.generic_remote.overflow": "disk drive almost full",
"checks.http_request.request_failed": "HTTP request failed",
"checks.http_request.status_code_mismatch": "actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}",

View file

@ -0,0 +1,113 @@
'''
todo: allow_self_signed
todo: allow_bad_domain
'''
class implementation_check_kind_tls_certificate(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"host": {
"type": "string"
},
"strict": {
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
"type": "boolean",
"default": True
},
"expiry_threshold": {
"description": "in days; allowed amount of valid days before the certificate expires",
"type": ["null", "integer"],
"default": 7,
"minimum": 0
}
},
"required": [
"host"
]
}
'''
[implementation]
'''
def normalize_conf_node(self, node):
if (not "host" in node):
raise ValueError("missing mandatory field 'host'")
else:
return dict_merge(
{
"strict": True,
"expiry_threshold": 7,
# "allow_self_signed": False,
# "allow_bad_domain": False,
},
node
)
return node
'''
[implementation]
'''
def run(self, parameters):
context = _ssl.create_default_context()
try:
socket = _socket.create_connection((parameters["host"], 443, ))
socket_wrapped = context.wrap_socket(socket, server_hostname = parameters["host"])
version = socket_wrapped.version()
data = socket_wrapped.getpeercert(False)
except _ssl.SSLCertVerificationError as error:
version = None
data = None
if (data is None):
return {
"condition": (
enum_condition.critical
if parameters["strict"] else
enum_condition.concerning
),
"info": {
"host": parameters["host"],
"faults": [
translation_get("checks.tls_certificate.not_obtainable"),
],
"data": {
},
}
}
else:
# version == "TLSv1.3"
expiry_timestamp = _ssl.cert_time_to_seconds(data["notAfter"])
current_timestamp = get_current_timestamp()
days = _math.ceil((expiry_timestamp - current_timestamp) / (60 * 60 * 24))
if (days <= parameters["expiry_threshold"]):
return {
"condition": (
enum_condition.critical
if parameters["strict"] else
enum_condition.concerning
),
"info": {
"host": parameters["host"],
"faults": [
translation_get("checks.tls_certificate.expires_soon"),
],
"data": {
"expiry_timestamp": expiry_timestamp,
"days": days,
},
}
}
else:
return {
"condition": enum_condition.ok,
"info": {
}
}

View file

@ -227,9 +227,9 @@ def conf_normalize_schedule(
node
)
return {
"regular_interval": conf_normalize_interval(node["regular_interval"]),
"attentive_interval": conf_normalize_interval(node["attentive_interval"]),
"reminding_interval": conf_normalize_interval(node["reminding_interval"]),
"regular_interval": conf_normalize_interval(node_["regular_interval"]),
"attentive_interval": conf_normalize_interval(node_["attentive_interval"]),
"reminding_interval": conf_normalize_interval(node_["reminding_interval"]),
}

View file

@ -12,7 +12,11 @@ def state_decode(state_encoded):
"timestamp": state_encoded["timestamp"],
"condition": condition_decode(state_encoded["condition"]),
"count": state_encoded["count"],
"last_notification_timestamp": state_encoded["last_notification_timestamp"],
"last_notification_timestamp": (
state_encoded["last_notification_timestamp"]
if ("last_notification_timestamp" in state_encoded) else
None
),
}
@ -117,6 +121,7 @@ def main():
check_kind_implementations = {
"script": implementation_check_kind_script(),
"file_state": implementation_check_kind_file_state(),
"tls_certificate": implementation_check_kind_tls_certificate(),
"http_request": implementation_check_kind_http_request(),
"generic_remote" : implementation_check_kind_generic_remote(),
}

View file

@ -1,6 +1,7 @@
import sys as _sys
import os as _os
import subprocess as _subprocess
import math as _math
import hashlib as _hashlib
import tempfile as _tempfile
import argparse as _argparse
@ -11,3 +12,5 @@ import time as _time
import datetime as _datetime
import smtplib as _smtplib
from email.mime.text import MIMEText
import ssl as _ssl
import socket as _socket

View file

@ -44,6 +44,7 @@ def main():
_os.path.join(dir_source, "logic", "checks", "_interface.py"),
_os.path.join(dir_source, "logic", "checks", "script.py"),
_os.path.join(dir_source, "logic", "checks", "file_state.py"),
_os.path.join(dir_source, "logic", "checks", "tls_certificate.py"),
_os.path.join(dir_source, "logic", "checks", "http_request.py"),
_os.path.join(dir_source, "logic", "checks", "generic_remote.py"),
_os.path.join(dir_source, "logic", "channels", "_interface.py"),