Browse code

Add support for simple YAML/JSON data layout.

The initial implementation used to support only raw and full YAML/JSON data
layout when converting YAML/JSON to keytab.

Xavier G authored on 26/04/2020 16:33:32
Showing 1 changed files

  • yamltab index 567e6de..4edc92a 100755
... ...
@@ -7,6 +7,7 @@
7 7
 
8 8
 from io import BufferedReader, BytesIO
9 9
 import os
10
+import re
10 11
 import sys
11 12
 import json
12 13
 import yaml
... ...
@@ -398,8 +399,93 @@ def raw_keytab_to_binary(indata):
398 399
 			raise KeytabComposingError('Unknown record type in record #%d' % index)
399 400
 	return data
400 401
 
402
+def spn_to_principal(spn, name_type='KRB5_NT_PRINCIPAL'):
403
+	principal = {'name_type': name_type}
404
+	principal['name_type_raw'] = NAME_TYPES[name_type]
405
+	rem = re.match(r'((?P<comp1>[^/]+)/)?(?P<comp2>[^:]+)(?::(?P<comp3>[^@]+))?@(?P<realm>.+)', spn)
406
+	if not rem:
407
+		raise KeytabComposingError('Cannot parse SPN %s into principal' % spn)
408
+	principal['realm'] = rem.group('realm')
409
+	principal['components'] = []
410
+	for name, component in rem.groupdict():
411
+		if name.startswith('comp') and component is not None:
412
+			principal['components'].append(component)
413
+	return principal
414
+
415
+def simple_principal_to_full(inentry, index, entry):
416
+	if 'principal' in inentry:
417
+		principal = entry['principal'] = inentry['principal']
418
+		if 'name_type_raw' not in principal:
419
+			try:
420
+				principal['name_type_raw'] = NAME_TYPES[principal['name_type']]
421
+			except KeyError:
422
+				message = 'Invalid or unknown name_type specified in entry #%d'
423
+				message += '; use name_type_raw to enforce an arbitrary value'
424
+				raise KeytabParsingError(message % index)
425
+	elif 'spn' in inentry:
426
+		entry['principal'] = spn_to_principal(inentry['spn'])
427
+
428
+def simple_kvno_to_full(inentry, index, entry, record, kvno_in_tail=False):
429
+	if 'kvno' in inentry:
430
+		entry['actual_kvno'] = inentry['kvno']
431
+		kvno_too_big = inentry['kvno'] > 255
432
+		entry['kvno'] = 0 if kvno_too_big else inentry['kvno']
433
+		if kvno_in_tail:
434
+			entry['tail_kvno'] = inentry['kvno']
435
+			record['tail'] = hexlify(struct.pack('>I', inentry['kvno']))
436
+		elif kvno_too_big:
437
+			message = 'Cannot store kvno > 255 without kvno_in_tail in entry #%d'
438
+			raise KeytabComposingError(message % index)
439
+
440
+def simple_timestamp_to_full(inentry, index, entry):
441
+	if 'timestamp' in inentry:
442
+		entry['timestamp'] = inentry['timestamp']
443
+	elif 'date' in inentry:
444
+		if inentry['date'] == 'now':
445
+			entry['timestamp'] = now
446
+		else:
447
+			try:
448
+				parsed_date = datetime.strptime(inentry['date'], DATE_TIME_FORMAT)
449
+				entry['timestamp'] = int(parsed_date.timestamp())
450
+			except:
451
+				raise KeytabParsingError('Invalid date specified in entry #%d' % index)
452
+
453
+def simple_enctype_to_full(inentry, index,  entry):
454
+	if 'enctype_raw' in inentry:
455
+		entry['enctype_raw'] = inentry['enctype_raw']
456
+	elif 'enctype' in inentry:
457
+		try:
458
+			entry['enctype_raw'] = ENC_TYPES[inentry['enctype']]
459
+		except KeyError:
460
+			message = 'Invalid or unknown enctype specified in entry #%d'
461
+			message += '; use enctype_raw to enforce an arbitrary value'
462
+			raise KeytabParsingError(message % index)
463
+
464
+def simple_keytab_to_full(indata):
465
+	now = int(datetime.now().timestamp())
466
+	data = {
467
+		'version': indata.get('version', 2),
468
+		'kvno_in_tail': indata.get('kvno_in_tail', False),
469
+		'records': [],
470
+	}
471
+	for index, inentry in enumerate(indata.get('entries', [])):
472
+		entry = {}
473
+		record = {'entry': entry}
474
+		simple_principal_to_full(inentry, index, entry)
475
+		simple_kvno_to_full(inentry, index, entry, record, data['kvno_in_tail'])
476
+		simple_timestamp_to_full(inentry, index, entry)
477
+		simple_enctype_to_full(inentry, index, entry)
478
+		if 'key' in inentry:
479
+			entry['key'] = inentry['key']
480
+		data['records'].append(record)
481
+	return data
482
+
401 483
 def yaml_to_keytab(buf, args):
402 484
 	data = yaml.load(buf, Loader=yaml.SafeLoader)
485
+	if 'entries' in data:
486
+		# If the provided structure exposes "entries" rather than "records",
487
+		# then it very likely features the "simple" data layout.
488
+		data = simple_keytab_to_full(data)
403 489
 	result = raw_keytab_to_binary(data)
404 490
 	sys.stdout.buffer.write(result)
405 491