Test SSL certificate expiration.

This commit is contained in:
Bastian Kleineidam 2012-06-20 20:10:40 +02:00
parent da7c68981b
commit 4cce99a77d
11 changed files with 506 additions and 404 deletions

View file

@ -168,6 +168,12 @@
#localwebroot=/var/www/
# Windows example:
#localwebroot=/C|/public_html/
# Check that SSL certificates are at least the given number of days valid.
# The number must not be negative.
# If the number of days is zero a warning is printed only for certificates
# that are already expired.
# The default number of days is 14.
#sslcertwarndays=14
##################### filtering options ##########################

View file

@ -1,7 +1,8 @@
8.0 "" (released xx.xx.2012)
Features:
- checking: Verify SSL certificates for HTTPS connections.
- checking: Verify SSL certificates for HTTPS connections. Both the
hostname and the expiration date are checked.
- cmdline: Added Nagios plugin script.

417
doc/de.po

File diff suppressed because it is too large Load diff

View file

@ -131,6 +131,15 @@ Aneinanderfügen von Verzeichnissen benutzen. Und das angegebene Verzeichnis
muss mit einem Schrägstrich enden.
.br
Kommandozeilenoption: keine
.TP
\fBwarnsslcertdaysvalid=\fP\fINUMBER\fP
Prüfe ob SSL\-Zertifikate mindestens die angegebene Anzahl an Tagen gültig
sind. Die Anzahl darf nicht negativ sein. Falls die Anzahl Null ist wird
eine Warnung nur für Zertifikate ausgegeben, die schon abgelaufen sind.
.br
The Standardanzahl an Tagen ist 14.
.br
Kommandozeilenoption: keine
.SS [filtering]
.TP
\fBignore=\fP\fIREGEX\fP (MULTILINE)

View file

@ -123,6 +123,16 @@ to join directories instead of a backslash.
And the given directory must end with a slash.
.br
Command line option: none
.TP
\fBwarnsslcertdaysvalid=\fP\fINUMBER\fP
Check that SSL certificates are at least the given number of days valid.
The number must not be negative.
If the number of days is zero a warning is printed only for certificates
that are already expired.
.br
The default number of days is 14.
.br
Command line option: none
.SS \fB[filtering]\fP
.TP
\fBignore=\fP\fIREGEX\fP (MULTILINE)

File diff suppressed because it is too large Load diff

View file

@ -17,9 +17,10 @@
"""
Handle https links.
"""
import time
from . import httpurl
from .const import WARN_HTTPS_CERTIFICATE
from .. import log, LOG_CHECK, strformat
class HttpsUrl (httpurl.HttpUrl):
@ -47,6 +48,7 @@ class HttpsUrl (httpurl.HttpUrl):
OpenSSL already checked the SSL notBefore and notAfter dates.
"""
cert = ssl_sock.getpeercert()
log.debug(LOG_CHECK, "Got SSL certificate %s", cert)
if not cert:
msg = _('empty or no certificate found')
self.add_ssl_warning(ssl_sock, msg)
@ -56,6 +58,11 @@ class HttpsUrl (httpurl.HttpUrl):
else:
msg = _('certificate did not include "subject" information')
self.add_ssl_warning(ssl_sock, msg)
if 'notAfter' in cert:
self.check_ssl_valid_date(ssl_sock, cert)
else:
msg = _('certificate did not include "notAfter" information')
self.add_ssl_warning(ssl_sock, msg)
def check_ssl_hostname(self, ssl_sock, cert, host):
"""Check the hostname against the certificate according to
@ -66,6 +73,26 @@ class HttpsUrl (httpurl.HttpUrl):
except CertificateError, msg:
self.add_ssl_warning(ssl_sock, msg)
def check_ssl_valid_date(self, ssl_sock, cert):
"""Check if the certificate is still valid, or if configured check
if it's at least a number of days valid.
"""
import ssl
checkDaysValid = self.aggregate.config["warnsslcertdaysvalid"]
notAfter = ssl.cert_time_to_seconds(cert['notAfter'])
curTime = time.time()
# Calculate seconds until certifcate expires. Can be negative if
# the certificate is already expired.
secondsValid = notAfter - curTime
if secondsValid < 0:
msg = _('certficate is expired on %s') % cert['notAfter']
self.add_ssl_warning(ssl_sock, msg)
elif checkDaysValid > 0 and \
secondsValid < (checkDaysValid * strformat.SECONDS_PER_DAY):
strSecondsValid = strformat.str_duration_long(secondsValid)
msg = _('certificate is only %s valid') % strSecondsValid
self.add_ssl_warning(ssl_sock, msg)
def add_ssl_warning(self, ssl_sock, msg):
"""Add a warning message about an SSL certificate error."""
cipher_name, ssl_protocol, secret_bits = ssl_sock.cipher()

View file

@ -221,6 +221,7 @@ class Configuration (dict):
self["useragent"] = UserAgent
self["debugmemory"] = False
self["localwebroot"] = None
self["warnsslcertdaysvalid"] = 14
from ..logger import Loggers
self.loggers = dict(**Loggers)

View file

@ -61,6 +61,17 @@ class LCConfigParser (ConfigParser.RawConfigParser, object):
if self.has_option(section, option):
self.config[option] = self.getboolean(section, option)
def read_int_option (self, section, option, key=None, allownegative=False):
"""Read an integer option."""
if self.has_option(section, option):
num = self.getint(section, option)
if not allownegative and num < 0:
raise LinkCheckerError(
_("invalid negative value for %s: %d\n") % (option, num))
if key is None:
key = option
self.config[key] = num
def read_output_config (self):
"""Read configuration options in section "output"."""
section = "output"
@ -112,12 +123,7 @@ class LCConfigParser (ConfigParser.RawConfigParser, object):
if self.has_option(section, "threads"):
num = self.getint(section, "threads")
self.config['threads'] = max(0, num)
if self.has_option(section, "timeout"):
num = self.getint(section, "timeout")
if num < 0:
raise LinkCheckerError(
_("invalid negative value for timeout: %d\n") % num)
self.config['timeout'] = num
self.read_int_option(section, "timeout")
self.read_boolean_option(section, "anchors")
if self.has_option(section, "recursionlevel"):
num = self.getint(section, "recursionlevel")
@ -133,12 +139,7 @@ class LCConfigParser (ConfigParser.RawConfigParser, object):
self.config["nntpserver"] = self.get(section, "nntpserver")
if self.has_option(section, "useragent"):
self.config["useragent"] = self.get(section, "useragent")
if self.has_option(section, "pause"):
num = self.getint(section, "pause")
if num < 0:
raise LinkCheckerError(
_("invalid negative value for pause: %d\n") % num)
self.config["wait"] = num
self.read_int_option(section, "pause", key="wait")
self.read_check_options(section)
def read_check_options (self, section):
@ -155,6 +156,7 @@ class LCConfigParser (ConfigParser.RawConfigParser, object):
self.config['cookiefile'] = self.get(section, 'cookiefile')
if self.has_option(section, "localwebroot"):
self.config['localwebroot'] = self.get(section, 'localwebroot')
self.read_int_option(section, "warnsslcertdaysvalid")
def read_authentication_config (self):
"""Read configuration options in section "authentication"."""

View file

@ -12,6 +12,8 @@ cookiefile=blablabla
useragent=Example/0.0
pause=99
debugmemory=1
localwebroot=foo
warnsslcertdaysvalid=99
[filtering]
ignore=

View file

@ -52,6 +52,8 @@ class TestConfig (unittest.TestCase):
self.assertEqual(config["useragent"], "Example/0.0")
self.assertEqual(config["wait"], 99)
self.assertEqual(config["debugmemory"], 1)
self.assertEqual(config["localwebroot"], "foo")
self.assertEqual(config["warnsslcertdaysvalid"], 99)
# filtering section
patterns = [x["pattern"].pattern for x in config["externlinks"]]
for prefix in ("ignore_", "nofollow_"):