@@ -, +, @@
dependency
---
.../univention-management-console-module-diagnostic/debian/control | 1 +
1 file changed, 1 insertion(+)
--- a/management/univention-management-console-module-diagnostic/debian/control
+++ a/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
.
--
---
.../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
--- a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/plugins/kdc_service.py
+++ a/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()
--
---
.../umc/python/diagnostic/de.po | 32 ++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
--- a/management/univention-management-console-module-diagnostic/umc/python/diagnostic/de.po
+++ a/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"
--