diff options
-rw-r--r-- | src/util/confvalidator/README | 25 | ||||
-rw-r--r-- | src/util/confvalidator/confparser.py | 144 | ||||
-rw-r--r-- | src/util/confvalidator/rules.yml | 13 | ||||
-rw-r--r-- | src/util/confvalidator/validator.conf | 2 | ||||
-rw-r--r-- | src/util/confvalidator/validator.py | 194 |
5 files changed, 378 insertions, 0 deletions
diff --git a/src/util/confvalidator/README b/src/util/confvalidator/README new file mode 100644 index 0000000..7bf7a10 --- /dev/null +++ b/src/util/confvalidator/README @@ -0,0 +1,25 @@ +validator.py is a command line tool for identifying invalid attributes, values and some formating problems in Kerberos configuration files. +The list of the valid attributes is created based on the “configuration variables” section in k5-int.h and user defined attributes from the rules file. + +Usage: + +validator.py path [-d defPath] [-r rulesPath] [-c validatorConfPath] + +Options: + +path – the path to the configuration file to validate + +-d defPath – path to the k5-int.h file. Starting from the 1.7 release this header holds the profile attribute names in the form #define KRB5_CONF_xxx ”ZZZ”. + +-r rulesPath - path the rules file in yaml format. It may be used to manage the list of the valid attributes and to define the additional validation rules. + +-c validatorConfPath – the same as -r and -d options, but in validator configuration file format. + +Example: + +python validator.py src/config-files/krb5.conf -r rules.yml -d src/include/k5-int.h +or +python validator.py src/config-files/krb5.conf -c validator.conf + +For more details please refer to the sample files validator.conf and rules.yml + diff --git a/src/util/confvalidator/confparser.py b/src/util/confvalidator/confparser.py new file mode 100644 index 0000000..2fea142 --- /dev/null +++ b/src/util/confvalidator/confparser.py @@ -0,0 +1,144 @@ +''' +Created on Jan 31, 2010 + +@author: tsitkova +''' +import re +import copy +import yaml + +class ConfParser(object): + def __init__(self, path): + self.configuration = self._parse(path) + + def walk(self): + for trio in self._walk(self.configuration): + yield trio + + def _parse(self, path): + comment_pattern = re.compile(r'(\s*[#].*)') + section_pattern = re.compile(r'^\s*\[(?P<section>\w+)\]\s+$') + empty_pattern = re.compile(r'^\s*$') + equalsign_pattern = re.compile(r'=') + + section = None + parser_stack = list() + result = dict() + value = None + f = open(path, 'r') + for (ln,line) in enumerate(f): + line = comment_pattern.sub('', line) + line = equalsign_pattern.sub(' = ',line,count=1) + if empty_pattern.match(line) is not None: + continue + m = section_pattern.match(line) + if m is not None: + section = m.group('section') + value = dict() + result[section] = value + continue + if section is None: + msg = 'Failed to determine section for line #%i' % ln + raise ValueError(msg) + try: + value = self._parseLine(value, line, parser_stack) + except: + print 'Error while parsing line %i: %s' % (ln+1, line) + raise + f.close() + + if len(parser_stack): + raise 'Parsing error.' + + return result + + def _parseLine(self, value, content, stack): + token_pattern = re.compile(r'(?P<token>\S+)(?=\s+)') + attr = None + token_stack = list() + + for m in token_pattern.finditer(content): + token = m.group('token') + if not self._validate(token): + raise ValueError('Invalid token %s' % token) + if token == '=': + if len(token_stack) == 0: + raise ValueError('Failed to find attribute.') + elif len(token_stack) == 1: + attr = token_stack.pop() + else: + value[attr] = token_stack[:-1] + attr = token_stack[-1] + token_stack = list() + elif token == '{': + if attr is None: + raise ValueError('Failed to find attribute.') + stack.append((attr,value)) + value = dict() + elif token == '}': + if len(stack) == 0: + raise ValueError('Failed to parse: unbalanced braces') + if len(token_stack): + if attr is None: + raise ValueError('Missing attribute') + value[attr] = token_stack + attr = None + token_stack = list() + (attr,parent_value) = stack.pop() + parent_value[attr] = value + value = parent_value + else: + token_stack.append(token) + if len(token_stack): + if attr is None: + raise ValueError('Missing attribute') + value[attr] = token_stack + + return value + + def _validate(self, token): + result = True + for s in ['{','}']: + if s in token and s != token: + result = False + + return result + + def _walk(self, parsedData, path='root'): + dirs = list() + av = list() + for (key, value) in parsedData.iteritems(): + if type(value) == dict: + new_path = path + '.' + key + for trio in self._walk(value, new_path): + yield trio + dirs.append(key) + else: + av.append((key,value)) + yield (path, dirs, av) + + + +class ConfParserTest(ConfParser): + def __init__(self): + self.conf_path = '../tests/krb5.conf' + super(ConfParserTest, self).__init__(self.conf_path) + + def run_tests(self): + self._test_walk() + + def _test_parse(self): + result = self._parse(self.conf_path) + print yaml.dump(result) + + def _test_walk(self): + configuration = self._parse(self.conf_path) + for (path,dirs,av) in self.walk(): + print path,dirs,av + + + + +if __name__ == '__main__': + tester = ConfParserTest() + tester.run_tests() diff --git a/src/util/confvalidator/rules.yml b/src/util/confvalidator/rules.yml new file mode 100644 index 0000000..c6ccc89 --- /dev/null +++ b/src/util/confvalidator/rules.yml @@ -0,0 +1,13 @@ +# Extend the list of the allowed enctypes and salts as needed +Types: + supported_enctypes: + '(aes256-cts-hmac-sha1-96|aes256-cts|aes128-cts-hmac-sha1-96|aes128-cts|des3-hmac-sha1|des3-cbc-raw|des3-cbc-sha1|des3-hmac-sha1|rc4-hmac|arcfour-hmac-md5)(:(normal|v4))?$' + default_tgs_enctypes: + '(aes256-cts-hmac-sha1-96|aes256-cts|aes128-cts-hmac-sha1-96|aes128-cts|des3-hmac-sha1|des3-cbc-raw|des3-cbc-sha1|des3-hmac-sha1|rc4-hmac|arcfour-hmac-md5)' + default_tkt_enctypes: + '(aes256-cts-hmac-sha1-96|aes256-cts|aes128-cts-hmac-sha1-96|aes128-cts|des3-hmac-sha1|des3-cbc-raw|des3-cbc-sha1|des3-hmac-sha1|rc4-hmac|arcfour-hmac-md5)' + +# Add all valid profile attributes that are not listed in k5-int.h +Attributes: + - logging + - dbmodules diff --git a/src/util/confvalidator/validator.conf b/src/util/confvalidator/validator.conf new file mode 100644 index 0000000..71e205c --- /dev/null +++ b/src/util/confvalidator/validator.conf @@ -0,0 +1,2 @@ +RulesPath=./rules.yml +HfilePath=../../include/k5-int.h diff --git a/src/util/confvalidator/validator.py b/src/util/confvalidator/validator.py new file mode 100644 index 0000000..d739bc0 --- /dev/null +++ b/src/util/confvalidator/validator.py @@ -0,0 +1,194 @@ +''' +Created on Jan 25, 2010 + +@author: tsitkova +''' +import os +import sys +import re +import yaml +from optparse import OptionParser +from confparser import ConfParser + +class Rule(object): + def __init__(self): + pass + + def validate(self,node): + (path,dirs,avs) = node + + +class Validator(object): + def __init__(self, kerberosPath, confPath=None, rulesPath=None, hfilePath=None): + self.parser = ConfParser(kerberosPath) + if confPath is not None: + content = self._readConfigFile(confPath) + rulesPath = content['RulesPath'] + hfilePath = content['HfilePath'] + if rulesPath is not None and hfilePath is not None: + self.rules = self._loadRules(rulesPath) + self.validKeys = SupportedKeys(hfilePath).validKeys.union(self.rules['Attributes']) + else: + raise ValueError('Invalid arguments for validator: no path to rules and definition files') + + self._attribute_pattern = re.compile(r'^\w+$') + self._lowercase_pattern = re.compile(r'[a-z]') + + def _readConfigFile(self,path): + f = open(path) + result = dict() + for line in f: + line = line.rstrip() + fields = line.split('=') + result[fields[0]] = fields[1] + + return result + + def _loadRules(self, path): + f = open(path) + rules = yaml.load(f) + f.close() + + return rules + + def validate(self): + typeInfo = self.rules['Types'] + + for node in self.parser.walk(): + self._validateTypes(node, typeInfo) + self._validateAttrubutes(node, self.validKeys) + # self._validateRealm(node) + + + def _validateTypes(self, node, typeInfo): + (path, dirs, avs) = node + for (key, value) in avs: + valid_type_pattern = typeInfo.get(key) + if valid_type_pattern is not None: + for t in value: + if re.match(valid_type_pattern, t) is None: + print 'Wrong type %s for attribute %s.%s' % (t,path,key) + + def _validateAttrubutes(self, node, validKeys): + (path, dirs, avs) = node + attributes = list() + for attr in dirs: + if self._attribute_pattern.match(attr) is not None: + attributes.append(attr) + for (attr, value) in avs: + if self._attribute_pattern.match(attr) is not None: + attributes.append(attr) + + for attr in attributes: + if attr not in validKeys: + print 'Unrecognized attribute %s at %s' % (attr, path) + +# def _validateRealm(self, node): +# (path, dirs, avs) = node +# if path == 'root.realms': +# for attr in dirs: +# if self._lowercase_pattern.search(attr) is not None: +# print 'Lower case letter in realm attribute: %s at %s' % (attr, path) + +class SupportedKeys(object): + def __init__(self, path): + self.validKeys = self.getKeysFromHfile(path) + + def getKeysFromHfile(self, path): + pattern = re.compile(r'^[#]define KRB5_CONF_\w+\s+["](\w+)["]') + f = open(path) + result = set() + for l in f: + l = l.rstrip() + m = pattern.match(l) + if m is not None: + result.add(m.groups()[0]) + f.close() + + return result + + +class ValidatorTest(Validator): + def __init__(self): + self.kerberosPath = '../tests/kdc1.conf' + self.rulesPath = '../tests/rules.yml' + self.hfilePath = '../tests/k5-int.h' + self.confPath = '../tests/validator.conf' + + super(ValidatorTest, self).__init__(self.kerberosPath, + rulesPath=self.rulesPath, + hfilePath=self.hfilePath) + + def run_tests(self): + self._test_validate() + + def _test__loadRules(self): + result = self._loadRules(self.rulesPath) + print result + + def _test_validate(self): + self.validate() + + def _test__readConfigFile(self): + result = self._readConfigFile(self.confPath) + print result + +class SupportedKeysTest(SupportedKeys): + def __init__(self): + self.path = '../tests/k5-int.h' + + def run_tests(self): + self._test_getKeysFromHFile() + + def _test_getKeysFromHFile(self): + result = set() + krb5keys = self.getKeysFromHfile(self.path) + for key in krb5keys: + print key + result.update(key) + print len(krb5keys) + + return result + +def _test(): + tester = ValidatorTest() + krb5keys = tester.run_tests() + +if __name__ == '__main__': + TEST = False + if TEST: + _test() + sys.exit() + + + usage = "\n\t%prog path [-d defPath] [-r rulesPath] [-c validatorConfPath]" + description = 'Description: validates kerberos configuration file' + parser = OptionParser(usage = usage, description = description) + parser.add_option("-c", dest="confPath", + help='path to validator config file') + parser.add_option("-d", dest="hfilePath", + help='path to h-file with attribute definition') + parser.add_option("-r", dest="rulesPath", + help='path to file with validation rules') + (options, args) = parser.parse_args() + + if len(args) != 1 and len(sys.argv) <= 3: + print '\n%s' % parser.get_usage() + sys.exit() + + validator = None + if options.confPath is not None: + validator = Validator(args[0], confPath=options.confPath) + elif options.hfilePath is not None and options.rulesPath is not None: + validator = Validator(args[0], hfilePath=options.hfilePath, rulesPath=options.rulesPath) + else: + print '\nMust specify either configuration file or paths to rules and definitions files' + print '%s' % parser.get_usage() + sys.exit() + + validator.validate() + + + + + |