Add collectd support
This commit is contained in:
parent
8f5ad667aa
commit
0366c1c5e4
|
@ -8,9 +8,7 @@ from pytz import utc as UTC
|
|||
from datetime import datetime
|
||||
|
||||
#collectd stuff
|
||||
#import collectd
|
||||
|
||||
import ipaddress
|
||||
import collectd
|
||||
|
||||
import pdb
|
||||
|
||||
|
@ -18,39 +16,125 @@ DOMAIN = "geekify.de"
|
|||
address_family = "ipv4"
|
||||
TIMEOUT_SECONDS = 1
|
||||
|
||||
plugin_config = {}
|
||||
|
||||
def configure(config):
|
||||
for c in config.children:
|
||||
print(c)
|
||||
machines = {}
|
||||
for config_child in config.children:
|
||||
key = config_child.key.lower()
|
||||
values = config_child.values
|
||||
# currently only "host" entries are supported
|
||||
if config_child.key.lower() == "host":
|
||||
# host can contain several hosts at once, even the ones containing additional config
|
||||
for machine_name in values:
|
||||
machine_details = {}
|
||||
# host entry contains advanced/additional config options
|
||||
for detail in config_child.children:
|
||||
key_name = detail.key.lower()
|
||||
machine_detail_values = []
|
||||
# TODO: all the difference here is casting value into an int or not. This can maybe be improved or reduced somehow?
|
||||
if key_name in ("addressfamily",):
|
||||
for value in detail.values:
|
||||
machine_detail_values.append(int(value))
|
||||
elif key_name in ("sninames",):
|
||||
machine_detail_values = []
|
||||
for value in detail.values:
|
||||
machine_detail_values.append(value)
|
||||
else:
|
||||
# additional keys are just ignored
|
||||
pass
|
||||
machine_details.update({key_name: machine_detail_values})
|
||||
machines.update({machine_name: machine_details})
|
||||
plugin_config.update({"machines": machines})
|
||||
|
||||
today = datetime.now(UTC)
|
||||
port = 443
|
||||
if address_family == "ipv4":
|
||||
af = socket.AF_INET
|
||||
elif address_family == "ipv6":
|
||||
af = socket.AF_INET6
|
||||
else:
|
||||
proto = None
|
||||
def check_hosts():
|
||||
for host, host_config in plugin_config.get("machines", {}).items():
|
||||
for address_family in host_config.get("addressfamily", [4, 6]):
|
||||
for sni_name in host_config.get("sninames", [host]):
|
||||
try:
|
||||
cert_details = get_tls_metrics(host, sni_name, address_family)
|
||||
for key in cert_details.keys():
|
||||
val = collectd.Values(host="", plugin="pyopenssl", plugin_instance="host_{}-sni_{}".format(host, sni_name), type_instance="ipv{}-{}".format(address_family, key))
|
||||
cert_detail = cert_details.get(key)
|
||||
if key in ("valid", "expired"):
|
||||
val.type = "gauge"
|
||||
val.dispatch(values=[1 if cert_detail else 0])
|
||||
elif key in ("notAfter", "notBefore"):
|
||||
val.type = "gauge"
|
||||
val.dispatch(values=[cert_detail.timestamp()])
|
||||
elif key in ("remaining", "active_since"):
|
||||
val.type = "gauge"
|
||||
val.dispatch(values=[int(cert_detail.total_seconds())])
|
||||
elif key in ("issuer", "digests"):
|
||||
# currently there is no way to store static strings with collectd
|
||||
pass
|
||||
except Exception as e:
|
||||
print("Failed to check host {} with SNI {} over IPv{}: ".format(host, sni_name, address_family), end="")
|
||||
print(e)
|
||||
pass
|
||||
|
||||
try:
|
||||
addrinfo = socket.getaddrinfo(DOMAIN, None, af, proto=socket.IPPROTO_TCP)
|
||||
def get_tls_metrics(host, sni_name, address_family):
|
||||
cert_details = {"valid": True}
|
||||
today = datetime.now(UTC)
|
||||
port = 443
|
||||
if address_family == 4:
|
||||
af = socket.AF_INET
|
||||
elif address_family == 6:
|
||||
af = socket.AF_INET6
|
||||
else:
|
||||
proto = None
|
||||
|
||||
addrinfo = socket.getaddrinfo(host, None, af, proto=socket.IPPROTO_TCP)
|
||||
|
||||
# addrinfo first contains an array of connections made, we want the first ([0]) and only one we made
|
||||
# this tuple then contains (family, type, proto, canonname, sockaddr) - we want sockaddr == [4]
|
||||
# this then can contain either (host, port) or (host, port, flowinfo, scopeid) depending on set address family. Since host is anyways at [0] we take this as final ip address
|
||||
host = addrinfo[0][4][0]
|
||||
except socket.gaierror:
|
||||
host = DOMAIN
|
||||
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((host, port), TIMEOUT_SECONDS) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=DOMAIN) as sslsock:
|
||||
der_cert = sslsock.getpeercert(True)
|
||||
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
||||
context = ssl.create_default_context()
|
||||
while True:
|
||||
try:
|
||||
with socket.create_connection((host, port), TIMEOUT_SECONDS) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=sni_name) as sslsock:
|
||||
der_cert = sslsock.getpeercert(True)
|
||||
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
||||
break
|
||||
except ssl.CertificateError as error:
|
||||
if context.verify_mode == ssl.CERT_REQUIRED and context.check_hostname:
|
||||
cert_details["valid"] = False
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
else:
|
||||
raise error
|
||||
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, der_cert)
|
||||
exp = x509.get_notAfter().decode("utf-8")
|
||||
expirary_date = date_parser.parse(exp)
|
||||
delta = expirary_date - today
|
||||
print(delta.total_seconds())
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, der_cert)
|
||||
notAfter = x509.get_notAfter().decode("utf-8")
|
||||
notAfter = date_parser.parse(notAfter)
|
||||
notBefore = x509.get_notBefore().decode("utf-8")
|
||||
notBefore = date_parser.parse(notBefore)
|
||||
remaining = notAfter - today
|
||||
active_since = today - notBefore
|
||||
|
||||
pdb.set_trace()
|
||||
issuer_components = x509.get_issuer().get_components()
|
||||
issuer_components_decoded = list(map(lambda bin_component: list(map(lambda keyvalue: keyvalue.decode("utf-8"), bin_component)), issuer_components))
|
||||
joined_components = list(map(lambda x: "=".join(x), issuer_components_decoded))
|
||||
issuer = ",".join(joined_components)
|
||||
|
||||
digests = {}
|
||||
for digest_type in ("md5", "sha1", "sha256"):
|
||||
digests.update({digest_type: x509.digest(digest_type).decode("utf-8")})
|
||||
|
||||
cert_details.update({
|
||||
"expired": x509.has_expired(),
|
||||
"notAfter": notAfter,
|
||||
"notBefore": notBefore,
|
||||
"remaining": remaining,
|
||||
"active_since": active_since,
|
||||
"issuer": issuer,
|
||||
"digests": digests,
|
||||
})
|
||||
|
||||
return cert_details
|
||||
|
||||
collectd.register_config(configure)
|
||||
collectd.register_read(check_hosts)
|
||||
|
|
Loading…
Reference in a new issue