Last active
February 15, 2025 19:16
-
-
Save crashdump/5683952 to your computer and use it in GitHub Desktop.
Report how many days before and http ssl certificate expire. I've also provided a template if you want to use it with Zabbix as an External Check: - Configure ExternalScripts variable in zabbix_server.conf - Put the script in the external script folder (I've used /etc/zabbix/externalscripts/) - Import the template & assign it to your host. - Wat…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
__author__ = "Adrien Pujol - http://www.crashdump.fr/" | |
__copyright__ = "Copyright 2013, Adrien Pujol" | |
__license__ = "Mozilla Public License" | |
__version__ = "0.3" | |
__email__ = "[email protected]" | |
__status__ = "Development" | |
__doc__ = "Check a TLS certificate validity." | |
import argparse | |
import socket | |
from datetime import datetime | |
import time | |
try: | |
# Try to load pyOpenSSL first | |
# aptitude install python-dev && pip install pyopenssl | |
from OpenSSL import SSL | |
PYOPENSSL = True | |
except ImportError: | |
# Else, fallback on standard ssl lib (doesn't support SNI) | |
import ssl | |
PYOPENSSL = False | |
CA_CERTS = "/etc/ssl/certs/ca-certificates.crt" | |
def exit_error(errcode, errtext): | |
print errtext | |
exit(errcode) | |
def pyopenssl_check_callback(connection, x509, errnum, errdepth, ok): | |
''' callback for pyopenssl ssl check''' | |
if x509.get_subject().commonName == HOST: | |
if x509.has_expired(): | |
exit_error(1, 'Error: Certificate has expired!') | |
else: | |
print pyopenssl_check_expiration(x509.get_notAfter()) | |
if not ok: | |
return False | |
return ok | |
def pyopenssl_check_expiration(asn1): | |
''' Return the numbers of day before expiration. False if expired.''' | |
try: | |
expire_date = datetime.strptime(asn1, "%Y%m%d%H%M%SZ") | |
except: | |
exit_error(1, 'Certificate date format unknow.') | |
expire_in = expire_date - datetime.now() | |
if expire_in.days > 0: | |
return expire_in.days | |
else: | |
return False | |
def pyssl_check_hostname(cert, hostname): | |
''' Return True if valid. False is invalid ''' | |
if 'subjectAltName' in cert: | |
for typ, val in cert['subjectAltName']: | |
# Wilcard | |
if typ == 'DNS' and val.startswith('*'): | |
if val[2:] == hostname.split('.', 1)[1]: | |
return True | |
# Normal hostnames | |
elif typ == 'DNS' and val == hostname: | |
return True | |
else: | |
return False | |
def pyssl_check_expiration(cert): | |
''' Return the numbers of day before expiration. False if expired. ''' | |
if 'notAfter' in cert: | |
try: | |
expire_date = datetime.strptime(cert['notAfter'], | |
"%b %d %H:%M:%S %Y %Z") | |
except: | |
exit_error(1, 'Certificate date format unknow.') | |
expire_in = expire_date - datetime.now() | |
if expire_in.days > 0: | |
return expire_in.days | |
else: | |
return False | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('host', help='specify an host to connect to') | |
parser.add_argument('-p', '--port', help='specify a port to connect to', | |
type=int, default=443) | |
args = parser.parse_args() | |
global HOST, PORT | |
HOST = args.host | |
PORT = args.port | |
# Check the DNS name | |
try: | |
socket.getaddrinfo(HOST, PORT)[0][4][0] | |
except socket.gaierror as e: | |
exit_error(1, e) | |
# Connect to the host and get the certificate | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.connect((HOST, PORT)) | |
# If handled by python SSL library | |
if not PYOPENSSL: | |
try: | |
ssl_sock = ssl.wrap_socket(sock, cert_reqs=ssl.CERT_REQUIRED, | |
ca_certs=CA_CERTS, | |
ciphers=("HIGH:-aNULL:-eNULL:" | |
"-PSK:RC4-SHA:RC4-MD5")) | |
cert = ssl_sock.getpeercert() | |
if not pyssl_check_hostname(cert, HOST): | |
print 'Error: Hostname does not match!' | |
print pyssl_check_expiration(cert) | |
sock = ssl_sock.unwrap() | |
except ssl.SSLError as e: | |
exit_error(1, e) | |
# If handled by pyOpenSSL module | |
else: | |
try: | |
ctx = SSL.Context(SSL.TLSv1_METHOD) | |
ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, | |
pyopenssl_check_callback) | |
ctx.load_verify_locations(CA_CERTS) | |
ssl_sock = SSL.Connection(ctx, sock) | |
ssl_sock.set_connect_state() | |
ssl_sock.set_tlsext_host_name(HOST) | |
ssl_sock.do_handshake() | |
x509 = ssl_sock.get_peer_certificate() | |
x509name = x509.get_subject() | |
if x509name.commonName != HOST: | |
print 'Error: Hostname does not match!' | |
ssl_sock.shutdown() | |
except SSL.Error as e: | |
exit_error(1, e) | |
sock.close() | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<zabbix_export> | |
<version>2.0</version> | |
<date>2013-11-15T12:28:00Z</date> | |
<groups> | |
<group> | |
<name>Templates</name> | |
</group> | |
</groups> | |
<templates> | |
<template> | |
<template>Template External Check - SSL Cert Expire</template> | |
<name>Template External Check - SSL Cert Expire</name> | |
<groups> | |
<group> | |
<name>Templates</name> | |
</group> | |
</groups> | |
<applications> | |
<application> | |
<name>SSL certificate</name> | |
</application> | |
</applications> | |
<items> | |
<item> | |
<name>SSL certificate validity</name> | |
<type>10</type> | |
<snmp_community/> | |
<multiplier>0</multiplier> | |
<snmp_oid/> | |
<key>check-ssl-expire.py["{HOST.NAME}"]</key> | |
<delay>86400</delay> | |
<history>14</history> | |
<trends>365</trends> | |
<status>0</status> | |
<value_type>0</value_type> | |
<allowed_hosts/> | |
<units>days</units> | |
<delta>0</delta> | |
<snmpv3_contextname/> | |
<snmpv3_securityname/> | |
<snmpv3_securitylevel>0</snmpv3_securitylevel> | |
<snmpv3_authprotocol>0</snmpv3_authprotocol> | |
<snmpv3_authpassphrase/> | |
<snmpv3_privprotocol>0</snmpv3_privprotocol> | |
<snmpv3_privpassphrase/> | |
<formula>1</formula> | |
<delay_flex/> | |
<params/> | |
<ipmi_sensor/> | |
<data_type>0</data_type> | |
<authtype>0</authtype> | |
<username/> | |
<password/> | |
<publickey/> | |
<privatekey/> | |
<port/> | |
<description/> | |
<inventory_link>0</inventory_link> | |
<applications> | |
<application> | |
<name>SSL certificate</name> | |
</application> | |
</applications> | |
<valuemap/> | |
</item> | |
</items> | |
<discovery_rules/> | |
<macros> | |
<macro> | |
<macro>{$SSL_PORT}</macro> | |
<value>443</value> | |
</macro> | |
</macros> | |
<templates/> | |
<screens/> | |
</template> | |
</templates> | |
<triggers> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<1</expression> | |
<name>SSL certificate on {HOSTNAME} expired</name> | |
<url/> | |
<status>0</status> | |
<priority>5</priority> | |
<description/> | |
<type>0</type> | |
<dependencies/> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<7</expression> | |
<name>SSL certificate on {HOSTNAME} expires in less than 7 days ({ITEM.VALUE} days remaining)</name> | |
<url/> | |
<status>0</status> | |
<priority>4</priority> | |
<description/> | |
<type>0</type> | |
<dependencies> | |
<dependency> | |
<name>SSL certificate on {HOSTNAME} expired</name> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<1</expression> | |
</dependency> | |
</dependencies> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<15</expression> | |
<name>SSL certificate on {HOSTNAME} expires in less than 15 days ({ITEM.VALUE} days remaining)</name> | |
<url/> | |
<status>0</status> | |
<priority>3</priority> | |
<description/> | |
<type>0</type> | |
<dependencies> | |
<dependency> | |
<name>SSL certificate on {HOSTNAME} expires in less than 7 days ({ITEM.VALUE} days remaining)</name> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<7</expression> | |
</dependency> | |
</dependencies> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<30</expression> | |
<name>SSL certificate on {HOSTNAME} expires in less than 30 days ({ITEM.VALUE} days remaining)</name> | |
<url/> | |
<status>0</status> | |
<priority>2</priority> | |
<description/> | |
<type>0</type> | |
<dependencies> | |
<dependency> | |
<name>SSL certificate on {HOSTNAME} expires in less than 15 days ({ITEM.VALUE} days remaining)</name> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<15</expression> | |
</dependency> | |
</dependencies> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<60</expression> | |
<name>SSL certificate on {HOSTNAME} expires in less than 60 days ({ITEM.VALUE} days remaining)</name> | |
<url/> | |
<status>0</status> | |
<priority>1</priority> | |
<description/> | |
<type>0</type> | |
<dependencies> | |
<dependency> | |
<name>SSL certificate on {HOSTNAME} expires in less than 30 days ({ITEM.VALUE} days remaining)</name> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<30</expression> | |
</dependency> | |
</dependencies> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<90</expression> | |
<name>SSL certificate on {HOSTNAME} expires in less than 90 days ({ITEM.VALUE} days remaining)</name> | |
<url/> | |
<status>0</status> | |
<priority>0</priority> | |
<description/> | |
<type>0</type> | |
<dependencies> | |
<dependency> | |
<name>SSL certificate on {HOSTNAME} expires in less than 60 days ({ITEM.VALUE} days remaining)</name> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].last(0)}<60</expression> | |
</dependency> | |
</dependencies> | |
</trigger> | |
<trigger> | |
<expression>{Template External Check - SSL Cert Expire:check-ssl-expire.py["{HOST.NAME}"].nodata(86500)}=1</expression> | |
<name>SSL certificate on {HOSTNAME} No new data in the last 24h</name> | |
<url/> | |
<status>0</status> | |
<priority>2</priority> | |
<description>No data received for > 24h. Check the script.</description> | |
<type>0</type> | |
<dependencies/> | |
</trigger> | |
</triggers> | |
</zabbix_export> |
Hello i got the following error:
./ssl-exp.py <SERVER_FQDN>
1137
Traceback (most recent call last):
File "./ssl-exp.py", line 158, in
main()
File "./ssl-exp.py", line 126, in main
sock = ssl_sock.unwrap()
File "/usr/lib/python2.7/ssl.py", line 823, in unwrap
s = self._sslobj.shutdown()
socket.error: [Errno 0] Error
Try to install "python-pip" and you'll see that it works!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello i got the following error: