Univention Bugzilla – Attachment 8931 Details for
Bug 40228
Add diagnostic test for SSL certificates
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
40228-diagnostic-certificate-check-420.patch
40228-diagnostic-certificate-check-420.patch (text/plain), 14.33 KB, created by
Lukas Oyen
on 2017-06-19 18:04 CEST
(
hide
)
Description:
40228-diagnostic-certificate-check-420.patch
Filename:
MIME Type:
Creator:
Lukas Oyen
Created:
2017-06-19 18:04 CEST
Size:
14.33 KB
patch
obsolete
>From e1e00a8bb2edb80227b0b173d1878d755513044c Mon Sep 17 00:00:00 2001 >From: Lukas Oyen <oyen@univention.de> >Date: Wed, 14 Jun 2017 18:25:29 +0200 >Subject: [PATCH 1/2] Bug #40228: umc-diagnostic: new check > certificate_check.py > >--- > .../debian/control | 3 + > .../python/diagnostic/plugins/certificate_check.py | 282 +++++++++++++++++++++ > 2 files changed, 285 insertions(+) > create mode 100755 management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/certificate_check.py > >diff --git a/management/univention-management-console-module-diagnostic/debian/control b/management/univention-management-console-module-diagnostic/debian/control >index a2e98d1..497aa62 100644 >--- a/management/univention-management-console-module-diagnostic/debian/control >+++ b/management/univention-management-console-module-diagnostic/debian/control >@@ -17,6 +17,9 @@ Depends: ${misc:Depends}, > python-pycurl, > python-psutil, > python-dnspython, >+ python-openssl, >+ python-dateutil, >+ python-requests, > python-paramiko > Description: System Diagnosis UMC module > . >diff --git a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/certificate_check.py b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/certificate_check.py >new file mode 100755 >index 0000000..c960106 >--- /dev/null >+++ b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/certificate_check.py >@@ -0,0 +1,282 @@ >+#!/usr/bin/python2.7 >+# coding: utf-8 >+# >+# Univention Management Console module: >+# System Diagnosis UMC module >+# >+# Copyright 2017 Univention GmbH >+# >+# http://www.univention.de/ >+# >+# All rights reserved. >+# >+# The source code of this program is made available >+# under the terms of the GNU Affero General Public License version 3 >+# (GNU AGPL V3) as published by the Free Software Foundation. >+# >+# Binary versions of this program provided by Univention to you as >+# well as other copyrighted, protected or trademarked materials like >+# Logos, graphics, fonts, specific documentations and configurations, >+# cryptographic keys etc. are subject to a license agreement between >+# you and Univention and not subject to the GNU AGPL V3. >+# >+# In the case you use this program under the terms of the GNU AGPL V3, >+# the program is provided 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 Affero General Public License for more details. >+# >+# You should have received a copy of the GNU Affero General Public >+# License with the Debian GNU/Linux or Univention distribution in file >+# /usr/share/common-licenses/AGPL-3; if not, see >+# <http://www.gnu.org/licenses/>. >+ >+import re >+import shutil >+import os.path >+import datetime >+import tempfile >+import subprocess >+import contextlib >+ >+import requests >+import dateutil.tz >+from OpenSSL import crypto >+ >+import univention.config_registry >+from univention.management.console.modules.diagnostic import Critical, Warning >+ >+from univention.lib.i18n import Translation >+_ = Translation('univention-management-console-module-diagnostic').translate >+ >+title = _('Check validity of SSL certificates') >+description = _('All SSL certificates valid.') >+links = [{ >+ 'name': 'sdb', >+ 'href': _('http://sdb.univention.de/1183'), >+ 'label': _('Univention Support Database - Renewing the TLS/SSL certificates') >+}] >+ >+ >+WARNING_PERIOD = datetime.timedelta(days=50) >+ >+ >+class CertificateWarning(Exception): >+ def __init__(self, path): >+ super(CertificateWarning, self).__init__(path) >+ self.path = path >+ >+ >+class CertificateWillExpire(CertificateWarning): >+ def __init__(self, path, remaining): >+ super(CertificateWillExpire, self).__init__(path) >+ self.remaining = remaining >+ >+ def __str__(self): >+ msg = _('Certificate {path!r} will expire in {days} days.') >+ days = int(self.remaining.total_seconds() / 60 / 60 / 24) >+ return msg.format(path=self.path, days=days) >+ >+ >+class CertificateError(CertificateWarning): >+ pass >+ >+ >+class CertificateNotYetValid(CertificateError): >+ def __str__(self): >+ msg = _('Found not yet valid certificate {path!r}.') >+ return msg.format(path=self.path) >+ >+ >+class CertificateExpired(CertificateError): >+ def __str__(self): >+ msg = _('Found expired certificate {path!r}.') >+ return msg.format(path=self.path) >+ >+ >+class CertificateInvalid(CertificateError): >+ def __init__(self, path, message): >+ super(CertificateInvalid, self).__init__(path) >+ self.message = message >+ >+ def __str__(self): >+ msg = _('Found invalid certificate {path!r}:\n{message}') >+ return msg.format(path=self.path, message=self.message) >+ >+ >+class CertificateVerifier(object): >+ def __init__(self, root_cert_path, crl_path): >+ self.root_cert_path = root_cert_path >+ self.crl_path = crl_path >+ >+ @staticmethod >+ def parse_generalized_time(generalized_time): >+ # ASN.1 GeneralizedTime >+ # Local time only. ``YYYYMMDDHH[MM[SS[.fff]]]'' >+ # Universal time (UTC time) only. ``YYYYMMDDHH[MM[SS[.fff]]]Z''. >+ # Difference between local and UTC times. ``YYYYMMDDHH[MM[SS[.fff]]]+-HHMM''. >+ >+ sans_mircoseconds = re.sub('\.\d{3}', '', generalized_time) >+ sans_difference = re.sub('[+-]\d{4}', '', sans_mircoseconds) >+ date_format = { >+ 10: '%Y%m%d%H', 12: '%Y%m%d%H%M', 14: '%Y%m%d%H%M%S', >+ 11: '%Y%m%d%HZ', 13: '%Y%m%d%H%MZ', 15: '%Y%m%d%H%M%SZ', >+ }.get(len(sans_difference)) >+ >+ if date_format is None: >+ raise ValueError('Unparsable generalized_time {!r}'.format(generalized_time)) >+ >+ date = datetime.datetime.strptime(sans_mircoseconds, date_format) >+ utc_difference = re.search('([+-])(\d{2})(\d{2})', sans_mircoseconds) >+ >+ if sans_mircoseconds.endswith('Z'): >+ return date.replace(tzinfo=dateutil.tz.tzutc()) >+ elif utc_difference: >+ (op, hours_str, minutes_str) = utc_difference.groups() >+ try: >+ (hours, minutes) = (int(hours_str), int(minutes_str)) >+ except ValueError: >+ raise ValueError('Unparsable generalized_time {!r}'.format(generalized_time)) >+ >+ if op == '+': >+ offset = datetime.timedelta(hours=hours, minutes=minutes) >+ else: >+ offset = datetime.timedelta(hours=-hours, minutes=-minutes) >+ with_offset = date.replace(tzinfo=dateutil.tz.tzoffset('unknown', offset)) >+ return with_offset.astimezone(dateutil.tz.tzutc()) >+ as_local = date.replace(tzinfo=dateutil.tz.tzlocal()) >+ return as_local.astimezone(dateutil.tz.tzutc()) >+ >+ def _verify_timestamps(self, cert_path): >+ now = datetime.datetime.now(dateutil.tz.tzutc()) >+ >+ with open(cert_path) as fob: >+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, fob.read()) >+ valid_from = self.parse_generalized_time(cert.get_notBefore()) >+ >+ if now < valid_from: >+ yield CertificateNotYetValid(cert_path) >+ >+ valid_until = self.parse_generalized_time(cert.get_notAfter()) >+ expires_in = valid_until - now >+ >+ if expires_in < datetime.timedelta(): >+ yield CertificateExpired(cert_path) >+ elif expires_in < WARNING_PERIOD: >+ yield CertificateWillExpire(cert_path, expires_in) >+ >+ def _openssl_verify(self, path): >+ # XXX It would be nice to do this in python. `python-openssl` has the >+ # capability to check against CRL since version 16.1.0, but >+ # unfortunately only version 0.14 is available in debian. >+ cmd = ('openssl', 'verify', '-CAfile', self.root_cert_path, >+ '-CRLfile', self.crl_path, '-crl_check', path) >+ verify = subprocess.Popen(cmd, stdout=subprocess.PIPE) >+ (stdout, stderr) = verify.communicate() >+ if verify.poll() != 0: >+ yield CertificateInvalid(path, stdout) >+ >+ def verify_root(self): >+ for error in self.verify(self.root_cert_path): >+ yield error >+ >+ def verify(self, cert_path): >+ for error in self._verify_timestamps(cert_path): >+ yield error >+ for error in self._openssl_verify(cert_path): >+ yield error >+ >+ >+def certificates(configRegistry): >+ fqdn = '{}.{}'.format(configRegistry.get('hostname'), configRegistry.get('domainname')) >+ default_certificate = '/etc/univention/ssl/{}/cert.pem'.format(fqdn) >+ yield configRegistry.get('apache2/ssl/certificate', default_certificate) >+ >+ saml_certificate = configRegistry.get('saml/idp/certificate/certificate') >+ if saml_certificate: >+ yield saml_certificate >+ >+ postfix_certificate = configRegistry.get('mail/postfix/ssl/certificate') >+ if postfix_certificate: >+ yield postfix_certificate >+ >+ if os.path.exists('/etc/univention/ssl/ucsCA/index.txt'): >+ with open('/etc/univention/ssl/ucsCA/index.txt') as fob: >+ for line in fob.readlines(): >+ try: >+ (status, _expiry, _revoked, serial, _path, _subject) = line.split('\t', 6) >+ except ValueError: >+ pass >+ else: >+ if status.strip() == 'V': >+ yield '/etc/univention/ssl/ucsCA/certs/{}.pem'.format(serial) >+ >+ >+@contextlib.contextmanager >+def download_tempfile(url): >+ with tempfile.NamedTemporaryFile() as fob: >+ response = requests.get(url, stream=True) >+ shutil.copyfileobj(response.raw, fob) >+ fob.flush() >+ yield fob.name >+ >+ >+@contextlib.contextmanager >+def convert_crl_to_pem(path): >+ with tempfile.NamedTemporaryFile() as fob: >+ convert = ('openssl', 'crl', '-inform', 'DER', '-in', path, '-outform', >+ 'PEM', '-out', fob.name) >+ subprocess.check_call(convert) >+ yield fob.name >+ >+ >+def verify_local(all_certificates): >+ with convert_crl_to_pem('/etc/univention/ssl/ucsCA/crl/ucsCA.crl') as crl: >+ verifier = CertificateVerifier('/etc/univention/ssl/ucsCA/CAcert.pem', crl) >+ for error in verifier.verify_root(): >+ yield error >+ for cert in all_certificates: >+ for error in verifier.verify(cert): >+ yield error >+ >+ >+def verify_from_master(master, all_certificates): >+ root_ca_uri = 'http://{}/ucs-root-ca.crt'.format(master) >+ crl_uri = 'http://{}/ucsCA.crl'.format(master) >+ with download_tempfile(root_ca_uri) as root_ca, download_tempfile(crl_uri) as crl: >+ with convert_crl_to_pem(crl) as crl_pem: >+ verifier = CertificateVerifier(root_ca, crl_pem) >+ for error in verifier.verify_root(): >+ yield error >+ for cert in all_certificates: >+ for error in verifier.verify(cert): >+ yield error >+ >+ >+def run(): >+ configRegistry = univention.config_registry.ConfigRegistry() >+ configRegistry.load() >+ >+ all_certificates = certificates(configRegistry) >+ is_local_check = configRegistry.get('server/role') in \ >+ ('domaincontroller_master', 'domaincontroller_backup') >+ >+ if is_local_check: >+ cert_verify = list(verify_local(all_certificates)) >+ else: >+ cert_verify = list(verify_from_master(configRegistry.get('ldap/master'), >+ all_certificates)) >+ >+ error_descriptions = [str(error) for error in cert_verify if >+ isinstance(error, CertificateWarning)] >+ >+ if error_descriptions: >+ error_descriptions.append(_('Please see {sdb} on how to renew certificates.')) >+ if any(isinstance(error, CertificateError) for error in cert_verify): >+ raise Critical(description='\n'.join(error_descriptions)) >+ raise Warning(description='\n'.join(error_descriptions)) >+ >+ >+if __name__ == '__main__': >+ from univention.management.console.modules.diagnostic import main >+ main() >-- >2.7.4 > > >From 78265e3f932c511aa1694dc057e8b50cf0158e0c Mon Sep 17 00:00:00 2001 >From: Lukas Oyen <oyen@univention.de> >Date: Mon, 19 Jun 2017 17:25:59 +0200 >Subject: [PATCH 2/2] Bug #40228: umc-diagnostic: new check > certificate_check.py (po) > >--- > .../umc/python/diagnostic/de.po | 45 +++++++++++++++++++++- > 1 file changed, 43 insertions(+), 2 deletions(-) > >diff --git a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po >index affad86..b6e4c6c 100644 >--- a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po >+++ b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po >@@ -2,8 +2,8 @@ > msgid "" > msgstr "" > "Project-Id-Version: univention-management-console-module-diagnostic\n" >-"Report-Msgid-Bugs-To: packages@univention.de\n" >-"POT-Creation-Date: 2016-01-14 12:19+0100\n" >+"Report-Msgid-Bugs-To: \n" >+"POT-Creation-Date: 2017-06-19 17:53+0200\n" > "PO-Revision-Date: \n" > "Last-Translator: Univention GmbH <packages@univention.de>\n" > "Language-Team: Univention GmbH <packages@univention.de>\n" >@@ -27,6 +27,34 @@ msgstr "" > msgid "Adjust to suggested limits" > msgstr "An vorgeschlagene Limits anpassen" > >+#: umc/python/diagnostic/plugins/certificate_check.py:53 >+msgid "All SSL certificates valid." >+msgstr "Alle SSL Zertifikate sind gültig." >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:76 >+msgid "Certificate {path!r} will expire in {days} days." >+msgstr "Zertifikat {path!r} wird in {days} Tagen ablaufen." >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:52 >+msgid "Check validity of SSL certificates" >+msgstr "Ãberprüfe Gültigkeit der SSL Zertifikate" >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:93 >+msgid "Found expired certificate {path!r}." >+msgstr "Abgelaufenes Zertifikat {path!r} gefunden." >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:103 >+msgid "" >+"Found invalid certificate {path!r}:\n" >+"{message}" >+msgstr "" >+"Ungültiges Zertifikat {path!r} gefunden:\n" >+"{message}" >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:87 >+msgid "Found not yet valid certificate {path!r}." >+msgstr "Noch nicht gültiges Zertifikat {path!r} gefunden." >+ > #: umc/python/diagnostic/plugins/gateway.py:11 > msgid "Gateway is not reachable" > msgstr "Gateway ist nicht erreichbar" >@@ -125,6 +153,11 @@ msgstr "" > "Der SSH Host-Key in /root/.ssh/known_hosts des entfernten Rechners muss auf " > "%(fqdn)s repariert werden." > >+#: umc/python/diagnostic/plugins/certificate_check.py:274 >+#, python-brace-format >+msgid "Please see {sdb} on how to renew certificates." >+msgstr "Siehe {sdb} für Informationen zum Erneuern von Zertifikaten." >+ > #: umc/python/diagnostic/plugins/proxy.py:15 > msgid "Proxy server failure" > msgstr "Proxy-Server-Fehler" >@@ -260,6 +293,14 @@ msgstr "" > "dass Authentifikations-Zugangsdaten (falls existierend) korrekt sind und die " > "ACL's des Proxy-Servers nicht verbieten, Anfragen an %s zu stellen." > >+#: umc/python/diagnostic/plugins/certificate_check.py:57 >+msgid "Univention Support Database - Renewing the TLS/SSL certificates" >+msgstr "Univention Support Database - Erneuern der TLS/SSL-Zertifikate" >+ >+#: umc/python/diagnostic/plugins/certificate_check.py:56 >+msgid "http://sdb.univention.de/1183" >+msgstr "http://sdb.univention.de/1000" >+ > #: umc/python/diagnostic/plugins/package_status.py:28 > msgid "some" > msgstr "einigen" >-- >2.7.4 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
Actions:
View
|
Diff
Attachments on
bug 40228
: 8931