diff --git a/checkman/check_crl b/checkman/check_crl
new file mode 100644
index 00000000000..d211b945845
--- /dev/null
+++ b/checkman/check_crl
@@ -0,0 +1,15 @@
+title: Check CRL expiry
+agents: active
+catalog: agentless
+license: GPL
+distribution: check_mk
+
+item:
+ The URL of the given CRL
+
+description:
+ This check screens the remaining validity of a CRL.
+ The Check can go into warning or critical if the remaining time
+ is too short.
+ The default tresholds are 7 days for warning and
+ 2 days for criticial.
diff --git a/checks/check_crl b/checks/check_crl
new file mode 100644
index 00000000000..9956456ccd1
--- /dev/null
+++ b/checks/check_crl
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+
+# author: Oguzhan Cicek, OpenSource Security GmbH - oguzhan(at)os-s.de
+
+
+def check_crl_arguments(params):
+ url = params["url"]
+ warn = params["time"][0]
+ crit = params["time"][1]
+ return f"-u {url} -w {warn} -c {crit}"
+
+
+def check_crl_description(params):
+ url = params["url"]
+ return f"CRL: {url}"
+
+
+active_check_info["crl"] = {
+ "command_line": "check_crl $ARG1$",
+ "argument_function": check_crl_arguments,
+ "service_description": check_crl_description,
+ "has_perfdata": False,
+}
diff --git a/cmk/gui/plugins/wato/active_checks.py b/cmk/gui/plugins/wato/active_checks.py
index 2ea8a7b5469..cda21661b3e 100644
--- a/cmk/gui/plugins/wato/active_checks.py
+++ b/cmk/gui/plugins/wato/active_checks.py
@@ -2490,3 +2490,33 @@ def _valuespec_active_checks_elasticsearch_query():
name="active_checks:elasticsearch_query",
valuespec=_valuespec_active_checks_elasticsearch_query,
))
+
+
+def _valuespec_active_checks_crl() -> Dictionary:
+ return Dictionary(
+ title=_("Check CRL expiry"),
+ help=_("Checks if a Certificate Revocation List is still valid"),
+ optional_keys=[],
+ elements=[("time",
+ Tuple(title=_("Time left"),
+ help=_("These levels make the check go warning or critical whenever the "
+ "remaining validity of the monitored CRL is too low."),
+ elements=[
+ Age(title=_("warning at"),
+ display=["days", "hours", "minutes"],
+ default_value=604800),
+ Age(title=_("critical at"),
+ display=["days", "hours", "minutes"],
+ default_value=172800)
+ ])),
+ ("url", TextAscii(title=_('Certificate Revocation List URL'),
+ allow_empty=False))])
+
+
+rulespec_registry.register(
+ HostRulespec(
+ group=RulespecGroupActiveChecks,
+ match_type="all",
+ name="active_checks:crl",
+ valuespec=_valuespec_active_checks_crl,
+ ))
diff --git a/omd/packages/nagios/skel/local/lib/nagios/plugins/check_crl b/omd/packages/nagios/skel/local/lib/nagios/plugins/check_crl
new file mode 100755
index 00000000000..7449fac7a94
--- /dev/null
+++ b/omd/packages/nagios/skel/local/lib/nagios/plugins/check_crl
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 - Remy van Elst
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# Mark Ruys - 2015-8-27
+# Changelog: - catch openssl parsing errors
+# - clean up temporary file on error
+# - add support for PEM CRL's
+# - fix message when CRL has been expired
+# - pretty print duration
+
+# Jeroen Nijhof
+# Changelog: - fixed timezone bug by comparing GMT with GMT
+# - changed hours to minutes for even more precision
+
+# Remy van Elst - raymii.org - 2012
+# 05.11.2012
+# Changelog: - check with hours instead of dates for more precision,
+# - URL errors are now also catched as nagios exit code.
+
+# Michele Baldessari - Leitner Technologies - 2011
+# 23.08.2011
+
+# Oguzhan Cicek - OpenSource Security GmbH - oguzhan(at)os-s.de - 2021
+# 05.04.2021
+# Changelog: - added argparse parsing
+# - added handling for DER-Certificates
+
+import time
+import datetime
+import getopt
+import os
+import pprint
+import subprocess
+import sys
+import tempfile
+import typing
+import argparse
+import urllib.request, urllib.parse, urllib.error
+
+
+def check_crl(url: str, warn: int, crit: int) -> (int, str):
+ tmpcrl = tempfile.mktemp(".crl")
+ #request = urllib.request.urlretrieve(url, tmpcrl)
+ try:
+ urllib.request.urlretrieve(url, tmpcrl)
+ except:
+ msg = "CRL could not be retrieved: %s" % url
+ os.remove(tmpcrl)
+ return 2, msg
+
+ try:
+ inform = 'DER'
+ crlfile = open(tmpcrl, "r")
+ try:
+ for line in crlfile:
+ if "BEGIN X509 CRL" in line:
+ inform = 'PEM'
+ break
+ except UnicodeDecodeError:
+ pass
+ crlfile.close()
+
+ ret = subprocess.check_output(["/usr/bin/openssl", "crl", "-inform", inform, "-noout", "-nextupdate", "-in", tmpcrl], stderr=subprocess.STDOUT)
+ except:
+ msg = "CRL could not be parsed: %s %s" % url
+ os.remove(tmpcrl)
+ return 3, msg
+
+ nextupdate = ret.strip().decode('utf-8').split("=")
+ os.remove(tmpcrl)
+ eol = time.mktime(time.strptime(nextupdate[1],"%b %d %H:%M:%S %Y GMT"))
+ today = time.mktime(datetime.datetime.utcnow().timetuple())
+ minutes = (eol - today) / 60
+ if abs(minutes) < 4 * 60:
+ expires = minutes
+ unit = "minutes"
+ elif abs(minutes) < 2 * 24 * 60:
+ expires = minutes / 60
+ unit = "hours"
+ else:
+ expires = minutes / (24 * 60)
+ unit = "days"
+ gmtstr = time.asctime(time.localtime(eol))
+ if minutes < 0:
+ msg = "CRL expired %d %s ago (on %s GMT)" % (-expires, unit, gmtstr)
+ exitcode = 2
+ elif minutes <= crit:
+ msg = "CRL expires in %d %s (on %s GMT)" % (expires, unit, gmtstr)
+ exitcode = 2
+ elif minutes <= warn:
+ msg = "CRL expires in %d %s (on %s GMT)" % (expires, unit, gmtstr)
+ exitcode = 1
+ else:
+ msg = "CRL expires in %d %s (on %s GMT)" % (expires, unit, gmtstr)
+ exitcode = 0
+
+ return exitcode, msg
+
+def usage():
+ sys.stdout.write("check_crl.py -h|--help -v|--verbose -u|--url= -w|--warning= -c|--critical=")
+ sys.stdout.write("")
+ sys.stdout.write("Example, if you want to get a warning if a CRL expires in 8 hours and a critical if it expires in 6 hours:")
+ sys.stdout.write("./check_crl.py -u \"http://domain.tld/url/crl.crl\" -w 480 -c 360")
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--url", "-u", required=True, type=str)
+ parser.add_argument("--warning", "-w", required=True, type=int)
+ parser.add_argument("--critical", "-c", required=True, type=int)
+ args = parser.parse_args()
+ if args.url== "":
+ usage()
+ return 3, "invalid parameter"
+ url = args.url
+ warning = int(args.warning / 60)
+ critical = int(args.critical / 60)
+ return check_crl(url, int(warning), int(critical))
+
+if __name__ == "__main__":
+ exitcode, info = main()
+ sys.stdout.write('%s\n' % info)
+ sys.exit(exitcode)
diff --git a/tests/unit/checks/test_check_crl.py b/tests/unit/checks/test_check_crl.py
new file mode 100644
index 00000000000..8fe5c23f466
--- /dev/null
+++ b/tests/unit/checks/test_check_crl.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
+# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
+# conditions defined in the file COPYING, which is part of this source code package.
+
+import pytest # type: ignore[import]
+from testlib import ActiveCheck # type: ignore[import]
+
+pytestmark = pytest.mark.checks
+
+
+@pytest.mark.parametrize("params,expected_args", [
+ (("foo", 222, 111, "bar", {}), ["--url=foo", "--warn=222", "--crit=111"]),
+])
+def test_check_crl_argument_parsing(params, expected_args):
+ """Tests if all required arguments are present."""
+ active_check = ActiveCheck("check_crl")
+ assert active_check.run_argument_function(params) == expected_args