summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMagnus Ahltorp <map@kth.se>2017-06-30 11:01:52 +0200
committerLinus Nordberg <linus@nordu.net>2017-07-06 17:48:30 +0200
commitb09e878a21c09d8344ec8b2a896d7d1a8162387e (patch)
treebea544071d048abb9e4abc58e698940f3ef7eafa
parent4e44c1e8a4ea6ec4c5eeb2a1a05d93e076c7fd50 (diff)
Verify config files against schema
-rwxr-xr-xtools/compileconfig.py69
-rw-r--r--tools/readconfig.py71
2 files changed, 133 insertions, 7 deletions
diff --git a/tools/compileconfig.py b/tools/compileconfig.py
index 94e0b51..87d46c6 100755
--- a/tools/compileconfig.py
+++ b/tools/compileconfig.py
@@ -561,6 +561,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")
@@ -575,17 +634,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)