|
0 |
- |
1 |
#!/usr/bin/python2.7 |
1 |
-- |
2 |
# coding: utf-8 |
|
|
3 |
# |
4 |
# Univention Management Console module: |
5 |
# System Diagnosis UMC module |
6 |
# |
7 |
# Copyright 2016-2017 Univention GmbH |
8 |
# |
9 |
# http://www.univention.de/ |
10 |
# |
11 |
# All rights reserved. |
12 |
# |
13 |
# The source code of this program is made available |
14 |
# under the terms of the GNU Affero General Public License version 3 |
15 |
# (GNU AGPL V3) as published by the Free Software Foundation. |
16 |
# |
17 |
# Binary versions of this program provided by Univention to you as |
18 |
# well as other copyrighted, protected or trademarked materials like |
19 |
# Logos, graphics, fonts, specific documentations and configurations, |
20 |
# cryptographic keys etc. are subject to a license agreement between |
21 |
# you and Univention and not subject to the GNU AGPL V3. |
22 |
# |
23 |
# In the case you use this program under the terms of the GNU AGPL V3, |
24 |
# the program is provided in the hope that it will be useful, |
25 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 |
# GNU Affero General Public License for more details. |
28 |
# |
29 |
# You should have received a copy of the GNU Affero General Public |
30 |
# License with the Debian GNU/Linux or Univention distribution in file |
31 |
# /usr/share/common-licenses/AGPL-3; if not, see |
32 |
# <http://www.gnu.org/licenses/>. |
33 |
|
34 |
import struct |
35 |
import socket |
36 |
import random |
37 |
|
38 |
import ldap |
39 |
import ipaddr |
40 |
import dns.resolver |
41 |
from pyasn1.type import tag |
42 |
from pyasn1.type import char |
43 |
from pyasn1.type import univ |
44 |
from pyasn1.type import useful |
45 |
from pyasn1.type import namedtype |
46 |
import pyasn1.codec.der.encoder |
47 |
import pyasn1.codec.der.decoder |
48 |
import pyasn1.error |
49 |
|
50 |
from univention.config_registry import handler_set as ucr_set |
51 |
import univention.config_registry |
52 |
from univention.management.console.modules.diagnostic import Warning, Critical, ProblemFixed |
53 |
|
54 |
from univention.lib.i18n import Translation |
55 |
_ = Translation('univention-management-console-module-diagnostic').translate |
56 |
|
57 |
title = _('KDC service check') |
58 |
description = _('The check for the KDC reachability was succesful.') |
59 |
|
60 |
|
61 |
# This checks for the reachability of KDCs by sending a AS-REQ per TCP and UDP. |
62 |
# The AS-REQ is send with the fake user `kdc-reachability-check`. The KDCs will |
63 |
# respond in several ways: either with an KRB-ERROR (PREAUTH_REQUIRED, |
64 |
# PRINCIPAL_UNKNOWN or RESPONSE_TO_BIG) or a AS-REP with an anonymous ticket. |
65 |
# |
66 |
# If we do not receive one of the above, the connection is not accepted, the |
67 |
# socket is closed or an operation times out, we can assume, that the KDCs is |
68 |
# not reachable. |
69 |
# |
70 |
# This check will test the KDCs as specified in UCR `kerberos/kdc` with TCP and |
71 |
# UDP on port 88. If `kerberos/defaults/dns_lookup_kdc` is set, KDC discovery as |
72 |
# specified in section `7.2.3. KDC Discovery on IP Networks` [1] will be used. |
73 |
# In this case the ports as specified in the SRV records are used. |
74 |
# |
75 |
# This implements a minimal number of packages as defined in [1] and does not |
76 |
# rely on python-kerberos or python-krb5, as those are too high level and |
77 |
# outdated. |
78 |
# |
79 |
# Reachability checks of kpasswd servers are not implemented, as those are a |
80 |
# separate protocol. See [2]. |
81 |
# |
82 |
# [1]: https://tools.ietf.org/html/rfc4120 |
83 |
# [2]: https://tools.ietf.org/html/rfc3244 |
84 |
|
85 |
|
86 |
def add_lo_to_samba_interfaces(): |
87 |
configRegistry = univention.config_registry.ConfigRegistry() |
88 |
configRegistry.load() |
89 |
|
90 |
interfaces = configRegistry.get('samba/interfaces', '').split() |
91 |
interfaces.append('lo') |
92 |
ucr_set(['samba/interfaces={}'.format(' '.join(interfaces))]) |
93 |
return run(retest=True) |
94 |
|
95 |
|
96 |
def reset_kerberos_kdc(): |
97 |
ucr_set(['kerberos/kdc=127.0.0.1']) |
98 |
return run(retest=True) |
99 |
|
100 |
|
101 |
actions = { |
102 |
'add_lo_to_samba_interfaces': add_lo_to_samba_interfaces, |
103 |
'reset_kerberos_kdc': reset_kerberos_kdc, |
104 |
} |
105 |
|
106 |
|
107 |
def _c(n, t): |
108 |
return t.clone(tagSet=t.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, n)) |
109 |
|
110 |
|
111 |
class PrincipalName(univ.Sequence): |
112 |
componentType = namedtype.NamedTypes( |
113 |
namedtype.NamedType('name-type', _c(0, univ.Integer())), |
114 |
namedtype.NamedType('name-string', _c(1, univ.SequenceOf(componentType=char.GeneralString())))) |
115 |
|
116 |
|
117 |
class KdcReqBody(univ.Sequence): |
118 |
tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4) |
119 |
componentType = namedtype.NamedTypes( |
120 |
namedtype.NamedType('kdc-options', _c(0, univ.BitString())), |
121 |
namedtype.OptionalNamedType('cname', _c(1, PrincipalName())), |
122 |
namedtype.NamedType('realm', _c(2, char.GeneralString())), |
123 |
namedtype.OptionalNamedType('sname', _c(3, PrincipalName())), |
124 |
namedtype.NamedType('till', _c(5, useful.GeneralizedTime())), |
125 |
namedtype.NamedType('nonce', _c(7, univ.Integer())), |
126 |
namedtype.NamedType('etype', _c(8, univ.SequenceOf(componentType=univ.Integer())))) |
127 |
|
128 |
|
129 |
class PAData(univ.Sequence): |
130 |
componentType = namedtype.NamedTypes( |
131 |
namedtype.NamedType('padata-type', _c(1, univ.Integer())), |
132 |
namedtype.NamedType('padata-value', _c(2, univ.OctetString()))) |
133 |
|
134 |
|
135 |
class AsReq(univ.Sequence): |
136 |
tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 10) |
137 |
componentType = namedtype.NamedTypes( |
138 |
namedtype.NamedType('pvno', _c(1, univ.Integer())), |
139 |
namedtype.NamedType('msg-type', _c(2, univ.Integer())), |
140 |
namedtype.NamedType('padata', _c(3, univ.SequenceOf(componentType=PAData()))), |
141 |
namedtype.NamedType('req-body', KdcReqBody())) |
142 |
|
143 |
|
144 |
class AsRep(univ.Sequence): |
145 |
tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 11) |
146 |
componentType = namedtype.NamedTypes( |
147 |
namedtype.NamedType('pvno', _c(0, univ.Integer())), |
148 |
namedtype.NamedType('msg-type', _c(1, univ.Integer())) |
149 |
# some more omitted |
150 |
) |
151 |
|
152 |
|
153 |
class KrbError(univ.Sequence): |
154 |
tagSet = univ.Sequence.tagSet + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 30) |
155 |
componentType = namedtype.NamedTypes( |
156 |
namedtype.NamedType('pvno', _c(0, univ.Integer())), |
157 |
namedtype.NamedType('msg-type', _c(1, univ.Integer())) |
158 |
# some more omitted |
159 |
) |
160 |
|
161 |
|
162 |
class KerberosException(Exception): |
163 |
pass |
164 |
|
165 |
|
166 |
class ServerUnreachable(KerberosException): |
167 |
pass |
168 |
|
169 |
|
170 |
class InvalidResponse(KerberosException): |
171 |
pass |
172 |
|
173 |
|
174 |
class EmptyResponse(KerberosException): |
175 |
pass |
176 |
|
177 |
|
178 |
def build_kerberos_request(target_realm, user_name): |
179 |
req_body = KdcReqBody() |
180 |
req_body['kdc-options'] = "'01010000100000000000000000000000'B" |
181 |
|
182 |
req_body['cname'] = None |
183 |
req_body['cname']['name-type'] = 1 # NT_PRINCIPAL |
184 |
req_body['cname']['name-string'] = None |
185 |
req_body['cname']['name-string'][0] = user_name |
186 |
|
187 |
req_body['realm'] = target_realm |
188 |
|
189 |
req_body['sname'] = None |
190 |
req_body['sname']['name-type'] = 2 # NT_SRV_INST |
191 |
req_body['sname']['name-string'] = None |
192 |
req_body['sname']['name-string'][0] = 'krbtgt' |
193 |
req_body['sname']['name-string'][1] = target_realm |
194 |
|
195 |
req_body['till'] = '19700101000000Z' |
196 |
req_body['nonce'] = random.SystemRandom().getrandbits(31) |
197 |
req_body['etype'] = None |
198 |
req_body['etype'][0] = 18 # AES256_CTS_HMAC_SHA1_96 |
199 |
|
200 |
as_req = AsReq() |
201 |
as_req['pvno'] = 5 |
202 |
as_req['msg-type'] = 10 # AS-REQ |
203 |
as_req['padata'] = None |
204 |
as_req['req-body'] = req_body |
205 |
|
206 |
return pyasn1.codec.der.encoder.encode(as_req) |
207 |
|
208 |
|
209 |
def send_and_receive(kdc, port, protocol, as_req): |
210 |
socket_type = socket.SOCK_DGRAM if protocol == 'udp' else socket.SOCK_STREAM |
211 |
sock = socket.socket(socket.AF_INET, socket_type) |
212 |
sock.settimeout(1) |
213 |
|
214 |
if protocol == 'tcp': |
215 |
packed = struct.pack('>I', len(as_req)) + as_req |
216 |
else: |
217 |
packed = as_req |
218 |
|
219 |
try: |
220 |
sock.connect((kdc, port)) |
221 |
sock.sendall(packed) |
222 |
except socket.error, socket.timeout: |
223 |
sock.close() |
224 |
raise ServerUnreachable() |
225 |
|
226 |
received = '' |
227 |
num_received = 0 |
228 |
if protocol == 'udp': # fake the length field |
229 |
received += '\x00\x00\x00\x00' |
230 |
num_received += 4 |
231 |
while num_received < 128: |
232 |
try: |
233 |
(buf, addr) = sock.recvfrom(128) |
234 |
except socket.error, socket.timeout: |
235 |
buf = '' |
236 |
if not buf: |
237 |
break |
238 |
received += buf |
239 |
num_received += len(buf) |
240 |
|
241 |
if not received: |
242 |
raise EmptyResponse() |
243 |
|
244 |
return received |
245 |
|
246 |
|
247 |
def probe_kdc(kdc, port, protocol, target_realm, user_name): |
248 |
request = build_kerberos_request(target_realm, user_name) |
249 |
|
250 |
try: |
251 |
received = send_and_receive(kdc, port, protocol, request) |
252 |
except KerberosException: |
253 |
return False |
254 |
|
255 |
try: |
256 |
(error, _sub) = pyasn1.codec.der.decoder.decode(received, asn1Spec=KrbError()) |
257 |
except pyasn1.error.PyAsn1Error: |
258 |
pass |
259 |
else: |
260 |
return True |
261 |
|
262 |
try: |
263 |
(rep, _sub) = pyasn1.codec.der.decoder.decode(received, asn1Spec=AsRep()) |
264 |
except pyasn1.error.PyAsn1Error: |
265 |
return False |
266 |
|
267 |
return True |
268 |
|
269 |
|
270 |
def is_service_active(service): |
271 |
lo = univention.uldap.getMachineConnection() |
272 |
raw_filter = '(&(univentionService=%s)(cn=%s))' |
273 |
filter_expr = ldap.filter.filter_format(raw_filter, (service, socket.gethostname())) |
274 |
for (dn, _attr) in lo.search(filter_expr, attr=['cn']): |
275 |
if dn is not None: |
276 |
return True |
277 |
return False |
278 |
|
279 |
|
280 |
def run(retest=False): |
281 |
configRegistry = univention.config_registry.ConfigRegistry() |
282 |
configRegistry.load() |
283 |
|
284 |
target_realm = configRegistry.get('kerberos/realm') |
285 |
user_name = 'kdc-reachability-check' |
286 |
|
287 |
kdc_fqds = configRegistry.get('kerberos/kdc', '').split() |
288 |
dns_lookup_kdc = configRegistry.is_true('kerberos/defaults/dns_lookup_kdc', True) |
289 |
if not dns_lookup_kdc and not kdc_fqds: |
290 |
kerberos_dns_fqdn_tcp = '_kerberos._tcp.{}'.format(configRegistry.get('domainname')) |
291 |
result_tcp = dns.resolver.query(kerberos_dns_fqdn_tcp, 'SRV') |
292 |
kdc_to_check = [(r.target.to_text(True), r.port, 'tcp') for r in result_tcp] |
293 |
|
294 |
kerberos_dns_fqdn_udp = '_kerberos._udp.{}'.format(configRegistry.get('domainname')) |
295 |
result_udp = dns.resolver.query(kerberos_dns_fqdn_udp, 'SRV') |
296 |
kdc_to_check.extend((r.target.to_text(True), r.port, 'udp') for r in result_udp) |
297 |
else: |
298 |
kdc_to_check = [(kdc, 88, 'tcp') for kdc in kdc_fqds] |
299 |
kdc_to_check.extend((kdc, 88, 'udp') for kdc in kdc_fqds) |
300 |
|
301 |
kdc_reachabe = [(probe_kdc(kdc, port, protocol, target_realm, user_name), |
302 |
(kdc, port, protocol)) for (kdc, port, protocol) in kdc_to_check] |
303 |
reachable_kdc = [(kdc, port, protocol) for (reachable, (kdc, port, protocol)) |
304 |
in kdc_reachabe if reachable] |
305 |
unreachable_kdc = [(kdc, port, protocol) for (reachable, (kdc, port, protocol)) |
306 |
in kdc_reachabe if not reachable] |
307 |
|
308 |
error_descriptions = list() |
309 |
|
310 |
if unreachable_kdc: |
311 |
error = _('The following KDCs were unreachable: {}') |
312 |
unreach_string = ('{} {}:{}'.format(protocol, kdc, port) |
313 |
for (kdc, port, protocol) in unreachable_kdc) |
314 |
error_descriptions.append(error.format(', '.join(unreach_string))) |
315 |
|
316 |
if not reachable_kdc: |
317 |
is_dc = configRegistry.get('server/role') == 'domaincontroller_master' |
318 |
is_s4_dc = is_dc and is_service_active('Samba 4') |
319 |
if is_s4_dc and configRegistry.is_true('samba/interfaces/bindonly', False): |
320 |
local_included = False |
321 |
for interface in configRegistry.get('samba/interfaces', '').split(): |
322 |
try: |
323 |
addr = ipaddr.IPAddress(interface) |
324 |
except ValueError: |
325 |
local_included |= interface == 'lo' |
326 |
else: |
327 |
local_included |= addr.is_loopback or addr.is_unspecified |
328 |
error = _('samba/interfaces does not contain lo, 127.0.0.1 or 0.0.0.0.') |
329 |
error_descriptions.append(error) |
330 |
|
331 |
description = '\n'.join(error_descriptions) |
332 |
buttons = [{ |
333 |
'action': 'add_lo_to_samba_interfaces', |
334 |
'label': _('Add lo to samba/interfaces'), |
335 |
}, { |
336 |
'action': 'reset_kerberos_kdc', |
337 |
'label': _('Reset kerberos/kdc to 127.0.0.1'), |
338 |
}] |
339 |
raise Critical(description=description, buttons=buttons) |
340 |
|
341 |
error_descriptions.append(_('No reachable KDCs were found.')) |
342 |
description = '\n'.join(error_descriptions) |
343 |
raise Critical(description=description) |
344 |
|
345 |
if error_descriptions: |
346 |
error = '\n'.join(error_descriptions) |
347 |
raise Warning(description=error) |
348 |
|
349 |
if retest: |
350 |
raise ProblemFixed() |
351 |
|
352 |
|
353 |
if __name__ == '__main__': |
354 |
from univention.management.console.modules.diagnostic import main |
355 |
main() |
2 |
.../umc/python/diagnostic/de.po | 32 ++++++++++++++++++++-- |
356 |
.../umc/python/diagnostic/de.po | 32 ++++++++++++++++++++-- |
3 |
1 file changed, 30 insertions(+), 2 deletions(-) |
357 |
1 file changed, 30 insertions(+), 2 deletions(-) |