Merge branch 'develop-issue_4' into 'master'

Neuer Check: TLS-Zertifikat

See merge request tools/heimdall!4
This commit is contained in:
Christian Fraß 2023-03-22 14:50:54 +00:00
commit c71e4a6bd4
9 changed files with 193 additions and 3 deletions

View file

@ -571,6 +571,49 @@
"parameters" "parameters"
] ]
}, },
{
"title": "check 'tls_certificate'",
"type": "object",
"unevaluatedProperties": false,
"properties": {
"kind": {
"type": "string",
"enum": [
"tls_certificate"
]
},
"parameters": {
"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"
]
}
},
"required": [
"kind",
"parameters"
]
},
{ {
"title": "check 'http_request'", "title": "check 'http_request'",
"type": "object", "type": "object",

View file

@ -15,8 +15,6 @@
] ]
}, },
"includes": [ "includes": [
"script.hmdl.json", "tls_certificate.hmdl.json"
"file_state.hmdl.json",
"generic_remote.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.timestamp_implausible": "Datei ist scheinbar aus der Zukunft",
"checks.file_state.too_old": "Datei ist zu alt", "checks.file_state.too_old": "Datei ist zu alt",
"checks.file_state.too_big": "Datei ist zu groß", "checks.file_state.too_big": "Datei ist zu groß",
"checks.tls_certificate.not_obtainable": "TLS-Zertifikat nicht abrufbar; evtl. bereits ausgelaufen",
"checks.tls_certificate.expires_soon": "TLS-Zertifikat läuft bald aus",
"checks.generic_remote.overflow": "Laufwerk fast voll", "checks.generic_remote.overflow": "Laufwerk fast voll",
"checks.http_request.request_failed": "HTTP-Abfrage fehlgeschlagen", "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", "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.timestamp_implausible": "file is apparently from the future",
"checks.file_state.too_old": "file is too old", "checks.file_state.too_old": "file is too old",
"checks.file_state.too_big": "file is too big", "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.generic_remote.overflow": "disk drive almost full",
"checks.http_request.request_failed": "HTTP request failed", "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}}", "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,121 @@
'''
todo: allow_self_signed
todo: allow_bad_domain
todo:
'''
class implementation_check_kind_tls_certificate(interface_check_kind):
'''
[implementation]
'''
def parameters_schema(self):
return {
"type": "object",
"additionalProperties": False,
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "integer",
"default": 443
},
"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,
"port": 443,
"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"], parameters["port"], ))
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"],
"port": parameters["port"],
"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"],
"port": parameters["port"],
"faults": [
translation_get("checks.tls_certificate.expires_soon"),
],
"data": {
"expiry_timestamp": expiry_timestamp,
"days": days,
},
}
}
else:
return {
"condition": enum_condition.ok,
"info": {
}
}

View file

@ -121,6 +121,7 @@ def main():
check_kind_implementations = { check_kind_implementations = {
"script": implementation_check_kind_script(), "script": implementation_check_kind_script(),
"file_state": implementation_check_kind_file_state(), "file_state": implementation_check_kind_file_state(),
"tls_certificate": implementation_check_kind_tls_certificate(),
"http_request": implementation_check_kind_http_request(), "http_request": implementation_check_kind_http_request(),
"generic_remote" : implementation_check_kind_generic_remote(), "generic_remote" : implementation_check_kind_generic_remote(),
} }

View file

@ -1,6 +1,7 @@
import sys as _sys import sys as _sys
import os as _os import os as _os
import subprocess as _subprocess import subprocess as _subprocess
import math as _math
import hashlib as _hashlib import hashlib as _hashlib
import tempfile as _tempfile import tempfile as _tempfile
import argparse as _argparse import argparse as _argparse
@ -11,3 +12,5 @@ import time as _time
import datetime as _datetime import datetime as _datetime
import smtplib as _smtplib import smtplib as _smtplib
from email.mime.text import MIMEText 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", "_interface.py"),
_os.path.join(dir_source, "logic", "checks", "script.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", "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", "http_request.py"),
_os.path.join(dir_source, "logic", "checks", "generic_remote.py"), _os.path.join(dir_source, "logic", "checks", "generic_remote.py"),
_os.path.join(dir_source, "logic", "channels", "_interface.py"), _os.path.join(dir_source, "logic", "channels", "_interface.py"),