From 4e8fbf53338fd1df0bfd6b9ab8754d757c966996 Mon Sep 17 00:00:00 2001 From: Lukas Oyen Date: Tue, 30 May 2017 16:10:54 +0200 Subject: [PATCH 1/3] Bug #36748: umc-diagnostic: add python-pyasn1 as dependency --- .../univention-management-console-module-diagnostic/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/management/univention-management-console-module-diagnostic/debian/control b/management/univention-management-console-module-diagnostic/debian/control index a2e98d1..e02a876 100644 --- a/management/univention-management-console-module-diagnostic/debian/control +++ b/management/univention-management-console-module-diagnostic/debian/control @@ -17,6 +17,7 @@ Depends: ${misc:Depends}, python-pycurl, python-psutil, python-dnspython, + python-pyasn1, python-paramiko Description: System Diagnosis UMC module . -- 2.7.4 From 92d221ebea7c1be2b5a2be040f3ea1f2a6936020 Mon Sep 17 00:00:00 2001 From: Lukas Oyen Date: Mon, 22 May 2017 18:15:57 +0200 Subject: [PATCH 2/3] Bug #36748: umc-diagnostic: new check kdc_service --- .../umc/python/diagnostic/plugins/kdc_service.py | 355 +++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100755 management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/kdc_service.py diff --git a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/kdc_service.py b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/kdc_service.py new file mode 100755 index 0000000..08d5b12 --- /dev/null +++ b/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/kdc_service.py @@ -0,0 +1,355 @@ +#!/usr/bin/python2.7 +# coding: utf-8 +# +# Univention Management Console module: +# System Diagnosis UMC module +# +# Copyright 2016-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 +# . + +import struct +import socket +import random + +import ldap +import ipaddr +import dns.resolver +from pyasn1.type import tag +from pyasn1.type import char +from pyasn1.type import univ +from pyasn1.type import useful +from pyasn1.type import namedtype +import pyasn1.codec.der.encoder +import pyasn1.codec.der.decoder +import pyasn1.error + +from univention.config_registry import handler_set as ucr_set +import univention.config_registry +from univention.management.console.modules.diagnostic import Warning, Critical, ProblemFixed + +from univention.lib.i18n import Translation +_ = Translation('univention-management-console-module-diagnostic').translate + +title = _('KDC service check') +description = _('The check for the KDC reachability was succesful.') + + +# This checks for the reachability of KDCs by sending a AS-REQ per TCP and UDP. +# The AS-REQ is send with the fake user `kdc-reachability-check`. The KDCs will +# respond in several ways: either with an KRB-ERROR (PREAUTH_REQUIRED, +# PRINCIPAL_UNKNOWN or RESPONSE_TO_BIG) or a AS-REP with an anonymous ticket. +# +# If we do not receive one of the above, the connection is not accepted, the +# socket is closed or an operation times out, we can assume, that the KDCs is +# not reachable. +# +# This check will test the KDCs as specified in UCR `kerberos/kdc` with TCP and +# UDP on port 88. If `kerberos/defaults/dns_lookup_kdc` is set, KDC discovery as +# specified in section `7.2.3. KDC Discovery on IP Networks` [1] will be used. +# In this case the ports as specified in the SRV records are used. +# +# This implements a minimal number of packages as defined in [1] and does not +# rely on python-kerberos or python-krb5, as those are too high level and +# outdated. +# +# Reachability checks of kpasswd servers are not implemented, as those are a +# separate protocol. See [2]. +# +# [1]: https://tools.ietf.org/html/rfc4120 +# [2]: https://tools.ietf.org/html/rfc3244 + + +def add_lo_to_samba_interfaces(): + configRegistry = univention.config_registry.ConfigRegistry() + configRegistry.load() + + interfaces = configRegistry.get('samba/interfaces', '').split() + interfaces.append('lo') + ucr_set(['samba/interfaces={}'.format(' '.join(interfaces))]) + return run(retest=True) + + +def reset_kerberos_kdc(): + ucr_set(['kerberos/kdc=127.0.0.1']) + return run(retest=True) + + +actions = { + 'add_lo_to_samba_interfaces': add_lo_to_samba_interfaces, + 'reset_kerberos_kdc': reset_kerberos_kdc, +} + + +def _c(n, t): + return t.clone(tagSet=t.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, n)) + + +class PrincipalName(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('name-type', _c(0, univ.Integer())), + namedtype.NamedType('name-string', _c(1, univ.SequenceOf(componentType=char.GeneralString())))) + + +class KdcReqBody(univ.Sequence): + tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4) + componentType = namedtype.NamedTypes( + namedtype.NamedType('kdc-options', _c(0, univ.BitString())), + namedtype.OptionalNamedType('cname', _c(1, PrincipalName())), + namedtype.NamedType('realm', _c(2, char.GeneralString())), + namedtype.OptionalNamedType('sname', _c(3, PrincipalName())), + namedtype.NamedType('till', _c(5, useful.GeneralizedTime())), + namedtype.NamedType('nonce', _c(7, univ.Integer())), + namedtype.NamedType('etype', _c(8, univ.SequenceOf(componentType=univ.Integer())))) + + +class PAData(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('padata-type', _c(1, univ.Integer())), + namedtype.NamedType('padata-value', _c(2, univ.OctetString()))) + + +class AsReq(univ.Sequence): + tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 10) + componentType = namedtype.NamedTypes( + namedtype.NamedType('pvno', _c(1, univ.Integer())), + namedtype.NamedType('msg-type', _c(2, univ.Integer())), + namedtype.NamedType('padata', _c(3, univ.SequenceOf(componentType=PAData()))), + namedtype.NamedType('req-body', KdcReqBody())) + + +class AsRep(univ.Sequence): + tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 11) + componentType = namedtype.NamedTypes( + namedtype.NamedType('pvno', _c(0, univ.Integer())), + namedtype.NamedType('msg-type', _c(1, univ.Integer())) + # some more omitted + ) + + +class KrbError(univ.Sequence): + tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 30) + componentType = namedtype.NamedTypes( + namedtype.NamedType('pvno', _c(0, univ.Integer())), + namedtype.NamedType('msg-type', _c(1, univ.Integer())) + # some more omitted + ) + + +class KerberosException(Exception): + pass + + +class ServerUnreachable(KerberosException): + pass + + +class InvalidResponse(KerberosException): + pass + + +class EmptyResponse(KerberosException): + pass + + +def build_kerberos_request(target_realm, user_name): + req_body = KdcReqBody() + req_body['kdc-options'] = "'01010000100000000000000000000000'B" + + req_body['cname'] = None + req_body['cname']['name-type'] = 1 # NT_PRINCIPAL + req_body['cname']['name-string'] = None + req_body['cname']['name-string'][0] = user_name + + req_body['realm'] = target_realm + + req_body['sname'] = None + req_body['sname']['name-type'] = 2 # NT_SRV_INST + req_body['sname']['name-string'] = None + req_body['sname']['name-string'][0] = 'krbtgt' + req_body['sname']['name-string'][1] = target_realm + + req_body['till'] = '19700101000000Z' + req_body['nonce'] = random.SystemRandom().getrandbits(31) + req_body['etype'] = None + req_body['etype'][0] = 18 # AES256_CTS_HMAC_SHA1_96 + + as_req = AsReq() + as_req['pvno'] = 5 + as_req['msg-type'] = 10 # AS-REQ + as_req['padata'] = None + as_req['req-body'] = req_body + + return pyasn1.codec.der.encoder.encode(as_req) + + +def send_and_receive(kdc, port, protocol, as_req): + socket_type = socket.SOCK_DGRAM if protocol == 'udp' else socket.SOCK_STREAM + sock = socket.socket(socket.AF_INET, socket_type) + sock.settimeout(1) + + if protocol == 'tcp': + packed = struct.pack('>I', len(as_req)) + as_req + else: + packed = as_req + + try: + sock.connect((kdc, port)) + sock.sendall(packed) + except socket.error, socket.timeout: + sock.close() + raise ServerUnreachable() + + received = '' + num_received = 0 + if protocol == 'udp': # fake the length field + received += '\x00\x00\x00\x00' + num_received += 4 + while num_received < 128: + try: + (buf, addr) = sock.recvfrom(128) + except socket.error, socket.timeout: + buf = '' + if not buf: + break + received += buf + num_received += len(buf) + + if not received: + raise EmptyResponse() + + return received + + +def probe_kdc(kdc, port, protocol, target_realm, user_name): + request = build_kerberos_request(target_realm, user_name) + + try: + received = send_and_receive(kdc, port, protocol, request) + except KerberosException: + return False + + try: + (error, _sub) = pyasn1.codec.der.decoder.decode(received, asn1Spec=KrbError()) + except pyasn1.error.PyAsn1Error: + pass + else: + return True + + try: + (rep, _sub) = pyasn1.codec.der.decoder.decode(received, asn1Spec=AsRep()) + except pyasn1.error.PyAsn1Error: + return False + + return True + + +def is_service_active(service): + lo = univention.uldap.getMachineConnection() + raw_filter = '(&(univentionService=%s)(cn=%s))' + filter_expr = ldap.filter.filter_format(raw_filter, (service, socket.gethostname())) + for (dn, _attr) in lo.search(filter_expr, attr=['cn']): + if dn is not None: + return True + return False + + +def run(retest=False): + configRegistry = univention.config_registry.ConfigRegistry() + configRegistry.load() + + target_realm = configRegistry.get('kerberos/realm') + user_name = 'kdc-reachability-check' + + kdc_fqds = configRegistry.get('kerberos/kdc', '').split() + dns_lookup_kdc = configRegistry.is_true('kerberos/defaults/dns_lookup_kdc', True) + if not dns_lookup_kdc and not kdc_fqds: + kerberos_dns_fqdn_tcp = '_kerberos._tcp.{}'.format(configRegistry.get('domainname')) + result_tcp = dns.resolver.query(kerberos_dns_fqdn_tcp, 'SRV') + kdc_to_check = [(r.target.to_text(True), r.port, 'tcp') for r in result_tcp] + + kerberos_dns_fqdn_udp = '_kerberos._udp.{}'.format(configRegistry.get('domainname')) + result_udp = dns.resolver.query(kerberos_dns_fqdn_udp, 'SRV') + kdc_to_check.extend((r.target.to_text(True), r.port, 'udp') for r in result_udp) + else: + kdc_to_check = [(kdc, 88, 'tcp') for kdc in kdc_fqds] + kdc_to_check.extend((kdc, 88, 'udp') for kdc in kdc_fqds) + + kdc_reachabe = [(probe_kdc(kdc, port, protocol, target_realm, user_name), + (kdc, port, protocol)) for (kdc, port, protocol) in kdc_to_check] + reachable_kdc = [(kdc, port, protocol) for (reachable, (kdc, port, protocol)) + in kdc_reachabe if reachable] + unreachable_kdc = [(kdc, port, protocol) for (reachable, (kdc, port, protocol)) + in kdc_reachabe if not reachable] + + error_descriptions = list() + + if unreachable_kdc: + error = _('The following KDCs were unreachable: {}') + unreach_string = ('{} {}:{}'.format(protocol, kdc, port) + for (kdc, port, protocol) in unreachable_kdc) + error_descriptions.append(error.format(', '.join(unreach_string))) + + if not reachable_kdc: + is_dc = configRegistry.get('server/role') == 'domaincontroller_master' + is_s4_dc = is_dc and is_service_active('Samba 4') + if is_s4_dc and configRegistry.is_true('samba/interfaces/bindonly', False): + local_included = False + for interface in configRegistry.get('samba/interfaces', '').split(): + try: + addr = ipaddr.IPAddress(interface) + except ValueError: + local_included |= interface == 'lo' + else: + local_included |= addr.is_loopback or addr.is_unspecified + error = _('samba/interfaces does not contain lo, 127.0.0.1 or 0.0.0.0.') + error_descriptions.append(error) + + description = '\n'.join(error_descriptions) + buttons = [{ + 'action': 'add_lo_to_samba_interfaces', + 'label': _('Add lo to samba/interfaces'), + }, { + 'action': 'reset_kerberos_kdc', + 'label': _('Reset kerberos/kdc to 127.0.0.1'), + }] + raise Critical(description=description, buttons=buttons) + + error_descriptions.append(_('No reachable KDCs were found.')) + description = '\n'.join(error_descriptions) + raise Critical(description=description) + + if error_descriptions: + error = '\n'.join(error_descriptions) + raise Warning(description=error) + + if retest: + raise ProblemFixed() + + +if __name__ == '__main__': + from univention.management.console.modules.diagnostic import main + main() -- 2.7.4 From bc4899b3f8f1a3acf536e83ed0df1f4882d94250 Mon Sep 17 00:00:00 2001 From: Lukas Oyen Date: Tue, 30 May 2017 16:13:18 +0200 Subject: [PATCH 3/3] Bug #36748: umc-diagnostic: new check kdc_service (po) --- .../umc/python/diagnostic/de.po | 32 ++++++++++++++++++++-- 1 file changed, 30 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..7f2a26f 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-05-30 16:19+0200\n" "PO-Revision-Date: \n" "Last-Translator: Univention GmbH \n" "Language-Team: Univention GmbH \n" @@ -23,6 +23,10 @@ msgstr "" "Eine Zeitüberschreitung trat beim Erreichen des Nameservers auf (ist er " "online?)." +#: umc/python/diagnostic/plugins/kdc_service.py:334 +msgid "Add lo to samba/interfaces" +msgstr "Füge lo zu samba/interfaces hinzu" + #: umc/python/diagnostic/plugins/security_limits.py:31 msgid "Adjust to suggested limits" msgstr "An vorgeschlagene Limits anpassen" @@ -59,6 +63,10 @@ msgstr "" "{ucr} oder durch automatische Anpassung auf die empfohlenen Limits zu " "erhöhen." +#: umc/python/diagnostic/plugins/kdc_service.py:57 +msgid "KDC service check" +msgstr "KDC Erreichbarkeit" + #: umc/python/diagnostic/plugins/ssh_connection.py:55 #, python-format msgid "" @@ -97,6 +105,10 @@ msgstr "" msgid "Nameserver(s) are not responsive" msgstr "Nameserver sind nicht ansprechbar" +#: umc/python/diagnostic/plugins/kdc_service.py:341 +msgid "No reachable KDCs were found." +msgstr "Keine erreichbaren KDCs gefunden." + #: umc/python/diagnostic/plugins/package_status.py:11 msgid "Package status corrupt" msgstr "Paketstatus korrupt" @@ -129,6 +141,10 @@ msgstr "" msgid "Proxy server failure" msgstr "Proxy-Server-Fehler" +#: umc/python/diagnostic/plugins/kdc_service.py:337 +msgid "Reset kerberos/kdc to 127.0.0.1" +msgstr "Setze kerberos/kdc auf 127.0.0.1 zurück" + #: umc/python/diagnostic/plugins/ssh_connection.py:16 msgid "SSH connection to UCS server failed!" msgstr "SSH-Verbindung zu anderem UCS Server fehlgeschlagen!" @@ -141,6 +157,14 @@ msgstr "Sicherheitslimits überschritten" msgid "Test again" msgstr "Erneut testen" +#: umc/python/diagnostic/plugins/kdc_service.py:58 +msgid "The check for the KDC reachability was succesful." +msgstr "Erreichbarkeitstest der KDCs war erfolgreich." + +#: umc/python/diagnostic/plugins/kdc_service.py:311 +msgid "The following KDCs were unreachable: {}" +msgstr "Die folgenden KDCs waren nicht erreichbar: {}" + #: umc/python/diagnostic/plugins/ssh_connection.py:48 msgid "" "The following list shows the affected remote servers and the reason for the " @@ -260,6 +284,10 @@ 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/kdc_service.py:328 +msgid "samba/interfaces does not contain lo, 127.0.0.1 or 0.0.0.0." +msgstr "samba/interfaces enthält weder lo noch 127.0.0.1 noch 0.0.0.0." + #: umc/python/diagnostic/plugins/package_status.py:28 msgid "some" msgstr "einigen" -- 2.7.4