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
|
from datetime import datetime
|
||||||
|
|
||||||
#collectd stuff
|
#collectd stuff
|
||||||
#import collectd
|
import collectd
|
||||||
|
|
||||||
import ipaddress
|
|
||||||
|
|
||||||
import pdb
|
import pdb
|
||||||
|
|
||||||
|
@ -18,39 +16,125 @@ DOMAIN = "geekify.de"
|
||||||
address_family = "ipv4"
|
address_family = "ipv4"
|
||||||
TIMEOUT_SECONDS = 1
|
TIMEOUT_SECONDS = 1
|
||||||
|
|
||||||
def configure(config):
|
plugin_config = {}
|
||||||
for c in config.children:
|
|
||||||
print(c)
|
|
||||||
|
|
||||||
today = datetime.now(UTC)
|
def configure(config):
|
||||||
port = 443
|
machines = {}
|
||||||
if address_family == "ipv4":
|
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})
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
af = socket.AF_INET
|
||||||
elif address_family == "ipv6":
|
elif address_family == 6:
|
||||||
af = socket.AF_INET6
|
af = socket.AF_INET6
|
||||||
else:
|
else:
|
||||||
proto = None
|
proto = None
|
||||||
|
|
||||||
try:
|
addrinfo = socket.getaddrinfo(host, None, af, proto=socket.IPPROTO_TCP)
|
||||||
addrinfo = socket.getaddrinfo(DOMAIN, None, af, proto=socket.IPPROTO_TCP)
|
|
||||||
|
|
||||||
# addrinfo first contains an array of connections made, we want the first ([0]) and only one we made
|
# 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 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
|
# 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]
|
host = addrinfo[0][4][0]
|
||||||
except socket.gaierror:
|
|
||||||
host = DOMAIN
|
|
||||||
|
|
||||||
context = ssl.create_default_context()
|
context = ssl.create_default_context()
|
||||||
with socket.create_connection((host, port), TIMEOUT_SECONDS) as sock:
|
while True:
|
||||||
with context.wrap_socket(sock, server_hostname=DOMAIN) as sslsock:
|
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)
|
der_cert = sslsock.getpeercert(True)
|
||||||
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
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)
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, der_cert)
|
||||||
exp = x509.get_notAfter().decode("utf-8")
|
notAfter = x509.get_notAfter().decode("utf-8")
|
||||||
expirary_date = date_parser.parse(exp)
|
notAfter = date_parser.parse(notAfter)
|
||||||
delta = expirary_date - today
|
notBefore = x509.get_notBefore().decode("utf-8")
|
||||||
print(delta.total_seconds())
|
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