From c110981461945fdf8ca2ad9ad29dc349128b3ed4 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 30 Jun 2017 11:01:52 +0200 Subject: Verify config files against schema --- tools/compileconfig.py | 69 ++++++++++++++++++++++++++++++++++++++++++++---- tools/readconfig.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/tools/compileconfig.py b/tools/compileconfig.py index c2dfcb3..10fc3d6 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -550,6 +550,65 @@ def printnodenames(config): print " ".join(frontendnodenames|storagenodenames|signingnodenames|mergenodenames|statusservernodenames) +localconfigschema = [ + ("nodename", "string", "nodename"), + ("frontendaddress", "string", "ip address"), + ("ctapiaddress", "string", "ip address"), + ("storageaddress", "string", "ip address"), + ("signingaddress", "string", "ip address"), + ("mergeaddress", "string", "ip address"), + ("publichttpaddress", "string", "ip address"), + ("configurl", "string", "url"), + ("logadminkey", "string", "key"), + ("dbbackend", "string", ["permdb", "fsdb"]), + ("paths/configdir", "string", "path"), + ("paths/db", "string", "path"), + ("paths/https_cacertfile", "string", "path"), + ("paths/https_certfile", "string", "path"), + ("paths/https_keyfile", "string", "path"), + ("paths/knownroots", "string", "path"), + ("paths/logpublickey", "string", "path"), + ("paths/privatekeys", "string", "path"), + ("paths/public_cacertfile", "string", "path"), + ("paths/publickeys", "string", "path"), + ("paths/verifycert_bin", "string", "path"), + ("paths/mergedb", "string", "path"), + ("paths/logprivatekey", "string", "path"), + ("ratelimits/add_chain", "string", "rate"), + ("merge/min-delay", "integer", "seconds"), + ("merge/backup-window-size", "integer", "number of entries"), + ("merge/backup-sendlog-chunksize", "integer", "number of entries"), + ("merge/backup-sendentries-chunksize", "integer", "number of entries"), + ("merge/dist-window-size", "integer", "number of entries"), + ("merge/dist-sendlog-chunksize", "integer", "number of entries"), + ("merge/dist-sendentries-chunksize", "integer", "number of entries"), +] + +globalconfigschema = [ + ("frontendnodes/[]/name", "string", "nodename"), + ("frontendnodes/[]/address", "string", "ip address"), + ("frontendnodes/[]/publicaddress", "string", "ip address"), + ("mergenodes/[]/name", "string", "nodename"), + ("mergenodes/[]/address", "string", "ip address"), + ("signingnodes/[]/name", "string", "nodename"), + ("signingnodes/[]/address", "string", "ip address"), + ("storagenodes/[]/name", "string", "nodename"), + ("storagenodes/[]/address", "string", "ip address"), + ("statusservers/[]/name", "string", "nodename"), + ("statusservers/[]/address", "string", "ip address"), + ("statusservers/[]/publicaddress", "string", "ip address"), + ("apikeys/[]/nodename", "string", "nodename"), + ("apikeys/[]/publickey", "string", "key"), + ("baseurl", "string", "url"), + ("primarymergenode", "string", "nodename"), + ("storage-quorum-size", "integer", "number of nodes"), + ("backup-quorum-size", "integer", "number of nodes"), + ("logpublickey", "string", "key"), + ("cafingerprint", "string", "fingerprint"), + ("version", "integer", "version"), + ("mmd", "integer", "seconds"), +] + def main(): parser = argparse.ArgumentParser(description="") parser.add_argument('--config', help="System configuration") @@ -564,17 +623,17 @@ def main(): sys.exit(1) if args.testmakefile: - config = readconfig.read_config(args.config) + config = readconfig.read_config(args.config, schema=globalconfigschema) gen_testmakefile(config, args.testmakefile) elif args.testshellvars: - config = readconfig.read_config(args.config) + config = readconfig.read_config(args.config, schema=globalconfigschema) gen_testmakefile(config, args.testshellvars, shellvars=True) elif args.getnodenames: - config = readconfig.read_config(args.config) + config = readconfig.read_config(args.config, schema=globalconfigschema) printnodenames(config) elif args.localconfig: - localconfig = readconfig.read_config(args.localconfig) - config = readconfig.verify_and_read_config(args.config, localconfig["logadminkey"]) + localconfig = readconfig.read_config(args.localconfig, schema=localconfigschema) + config = readconfig.verify_and_read_config(args.config, localconfig["logadminkey"], schema=globalconfigschema) gen_config(localconfig["nodename"], config, localconfig) else: diff --git a/tools/readconfig.py b/tools/readconfig.py index 028e319..15b9c61 100644 --- a/tools/readconfig.py +++ b/tools/readconfig.py @@ -50,14 +50,81 @@ def verify_config(rawconfig, signature, publickey_base64, filename): return errorhandlify(yaml.load(io.BytesIO(rawconfig), yaml.SafeLoader), filename) -def verify_and_read_config(filename, publickey_base64): +def verify_and_read_config(filename, publickey_base64, schema=None): rawconfig = open(filename).read() signature = open(filename + ".sig").read() verify_config(rawconfig, signature, publickey_base64, filename) config = yaml.load(io.BytesIO(rawconfig), yaml.SafeLoader) + if schema: + check_config_schema(config, schema) return errorhandlify(config, filename) -def read_config(filename): +def insert_schema_path(schema, path, datatype, highleveldatatype): + if len(path) == 1: + schema[path[0]] = (datatype, highleveldatatype) + else: + if path[0] not in schema: + schema[path[0]] = {} + insert_schema_path(schema[path[0]], path[1:], datatype, highleveldatatype) + +def transform_schema(in_schema): + schema = {} + for (rawpath, datatype, highleveldatatype) in in_schema: + path = rawpath.split("/") + insert_schema_path(schema, path, datatype, highleveldatatype) + return schema + +def check_config_schema(config, schema): + transformed_schema = transform_schema(schema) + error = check_config_schema_part(config, transformed_schema) + if error: + print >>sys.stderr, "error:", error + sys.exit(1) + +def check_config_schema_part(term, schema, path=[]): + joined_path = render_path(path) + if isinstance(term, basestring): + (schema_lowlevel, schema_highlevel) = schema + if schema_lowlevel != "string": + return "expected %s at %s, not a string" % (schema_lowlevel, joined_path,) + return None + elif isinstance(term, int): + (schema_lowlevel, schema_highlevel) = schema + if schema_lowlevel != "integer": + return "expected %s at %s, not an integer" % (schema_lowlevel, joined_path,) + return None + elif isinstance(term, dict): + if not isinstance(schema, dict): + return "expected %s at %s, not a key" % (schema, joined_path,) + for k, v in term.items(): + schema_part = schema.get(k) + if schema_part == None and len(schema.keys()) == 1 and schema.keys()[0].startswith("*"): + schema_part = schema[schema.keys()[0]] + if schema_part == None: + return "configuration key '%s' at %s unknown" % (k, joined_path) + result = check_config_schema_part(v, schema_part, path + [k]) + if result: + return result + return None + elif isinstance(term, list): + if not isinstance(schema, dict): + return "expected %s at %s, not a list" % (schema, joined_path,) + schema_part = schema.get("[]") + if schema_part == None: + return "expected dict at %s, not a list" % (joined_path,) + for i, e in enumerate(term, start=1): + result = check_config_schema_part(e, schema_part, path + ["item %d" % i]) + if result: + return result + return None + else: + print >>sys.stderr, "unknown type", type(term) + sys.exit(1) + + +def read_config(filename, schema=None): config = yaml.load(open(filename), yaml.SafeLoader) + if schema: + check_config_schema(config, schema) return errorhandlify(config, filename) -- cgit v1.1