registry/utils/schema-check/dn42-schema.py

1146 lines
33 KiB
Python
Raw Normal View History

2017-10-24 01:45:11 +02:00
#!/usr/bin/env python3
2020-07-23 21:00:01 +02:00
"DN42 Schema Checker"
2017-10-24 01:45:11 +02:00
from __future__ import print_function
import re
import os
import sys
2020-07-23 21:00:01 +02:00
import time
2017-10-24 01:45:11 +02:00
import argparse
import glob
2020-07-23 21:00:01 +02:00
import urllib.parse
import http.client
import json
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
import log
2017-10-24 01:45:11 +02:00
SCHEMA_NAMESPACE = "dn42."
class SchemaDOM:
2020-07-23 21:00:01 +02:00
"schema"
2017-10-24 01:45:11 +02:00
def __init__(self, fn):
2018-01-06 01:24:57 +01:00
self.name = None
self.ref = None
self.primary = None
self.type = None
2017-10-24 01:45:11 +02:00
self.src = fn
f = FileDOM(fn)
self.schema = self.__parse_schema(f)
def __parse_schema(self, f):
schema = {}
2020-07-23 21:00:01 +02:00
for key, val, _ in f.dom:
if key == "ref":
self.ref = val
elif key == "schema":
self.name = val
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
if key != "key":
2017-10-24 01:45:11 +02:00
continue
2020-07-23 21:00:01 +02:00
val = val.split()
key = val.pop(0)
2017-11-10 22:57:21 +01:00
2017-10-24 01:45:11 +02:00
schema[key] = set()
2020-07-23 21:00:01 +02:00
for i in val:
2017-10-24 01:45:11 +02:00
if i == ">":
break
schema[key].add(i)
for k, v in schema.items():
2020-07-23 21:00:01 +02:00
if "schema" in v:
2017-11-10 22:57:21 +01:00
self.type = k
2020-07-23 21:00:01 +02:00
if "primary" in v:
2017-11-10 22:57:21 +01:00
self.primary = k
2017-10-24 01:45:11 +02:00
schema[k].add("oneline")
if "multiline" in v:
schema[k].remove("multiline")
schema[k].add("single")
if "multiple" in v:
schema[k].remove("multiple")
schema[k].add("required")
if "optional" in v:
schema[k].remove("optional")
if "recommend" in v:
schema[k].remove("recommend")
if "deprecate" in v:
schema[k].remove("deprecate")
2020-07-23 21:00:01 +02:00
if "oneline" not in v:
2017-10-24 01:45:11 +02:00
schema[k].add("multiline")
2020-07-23 21:00:01 +02:00
if "single" not in v:
2017-10-24 01:45:11 +02:00
schema[k].add("multiple")
return schema
def check_file(self, f, lookups=None):
2020-07-23 21:00:01 +02:00
"check file"
2017-10-24 01:45:11 +02:00
status = "PASS"
2018-01-06 01:24:57 +01:00
if not f.valid:
2020-07-23 21:00:01 +02:00
log.error("%s Line 0: File does not parse" % (f.src))
status = "FAIL"
2018-01-06 01:24:57 +01:00
2017-10-24 01:45:11 +02:00
for k, v in self.schema.items():
2020-07-23 21:00:01 +02:00
if "required" in v and k not in f.keys:
log.error("%s Line 0: Key [%s] not found and is required." % (f.src, k))
2017-10-24 01:45:11 +02:00
status = "FAIL"
2020-07-23 21:00:01 +02:00
elif "recommend" in v and k not in f.keys:
2017-10-24 01:45:11 +02:00
log.notice(
2020-07-23 21:00:01 +02:00
"%s Line 0: Key [%s] not found and is recommended." % (f.src, k)
)
2017-10-24 01:45:11 +02:00
status = "NOTE"
2020-07-23 21:00:01 +02:00
if "schema" in v and SCHEMA_NAMESPACE + f.dom[0][0] != self.ref:
2017-10-24 01:45:11 +02:00
log.error(
2020-07-23 21:00:01 +02:00
"%s Line 1: Key [%s] not found and is required as the first line."
% (f.src, k)
)
2017-10-24 01:45:11 +02:00
status = "FAIL"
2020-07-23 21:00:01 +02:00
if "single" in v and k in f.keys and len(f.keys[k]) > 1:
log.warning(
"%s Line %d: Key [%s] first defined here and has repeated keys."
% (f.src, f.keys[k][0], k)
)
2017-10-24 01:45:11 +02:00
for l in f.keys[k][1:]:
log.error(
2020-07-23 21:00:01 +02:00
"%s Line %d: Key [%s] can only appear once." % (f.src, l, k)
)
2017-10-24 01:45:11 +02:00
status = "FAIL"
2020-07-23 21:00:01 +02:00
if "oneline" in v and k in f.multi:
2017-10-24 01:45:11 +02:00
for l in f.keys[k]:
log.error(
2020-07-23 21:00:01 +02:00
"%s Line %d: Key [%s] can not have multiple lines."
% (f.src, l, k)
)
2017-10-24 01:45:11 +02:00
status = "FAIL"
for k, v, l in f.dom:
2020-07-23 21:00:01 +02:00
if k == self.primary and not f.src.endswith(
v.replace("/", "_").replace(" ", "")):
log.error(
"%s Line %d: Primary [%s: %s] does not match filename."
% (f.src, l, k, v)
)
2017-11-10 22:57:21 +01:00
status = "FAIL"
2017-10-27 19:41:12 +02:00
if k.startswith("x-"):
2017-10-27 22:31:41 +02:00
log.info("%s Line %d: Key [%s] is user defined." % (f.src, l, k))
2017-10-27 19:41:12 +02:00
elif k not in self.schema:
2017-10-24 01:45:11 +02:00
log.error("%s Line %d: Key [%s] not in schema." % (f.src, l, k))
status = "FAIL"
continue
else:
2020-07-23 21:00:01 +02:00
if "deprecate" in self.schema[k]:
2017-10-24 01:45:11 +02:00
log.info(
2020-07-23 21:00:01 +02:00
"%s Line %d: Key [%s] was found and is deprecated."
% (f.src, l, k)
)
2017-10-24 01:45:11 +02:00
status = "INFO"
if lookups is not None:
for o in self.schema[k]:
if o.startswith("lookup="):
refs = o.split("=", 2)[1].split(",")
val = v.split()[0]
found = False
for ref in refs:
if (ref, val) in lookups:
found = True
if not found:
2020-07-23 21:00:01 +02:00
log.error(
"%s Line %d: Key %s references object %s in %s but does not exist."
% (f.src, l, k, val, refs)
)
2017-10-24 01:45:11 +02:00
status = "FAIL"
2017-12-12 01:33:55 +01:00
if status != "FAIL":
ck = sanity_check(f)
if ck == "FAIL":
status = ck
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
print("CHECK\t%-54s\t%s\tMNTNERS: %s" % (f.src, status, ",".join(f.mntner)))
2017-10-24 01:45:11 +02:00
return status
class FileDOM:
2020-07-23 21:00:01 +02:00
"file"
2017-10-24 01:45:11 +02:00
def __init__(self, fn):
2018-01-06 01:24:57 +01:00
self.valid = True
self.dom = []
self.keys = {}
self.multi = {}
self.mntner = []
self.schema = None
self.src = fn
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
with open(fn, mode="r", encoding="utf-8") as f:
2018-01-06 01:24:57 +01:00
dom = []
keys = {}
multi = {}
mntner = []
last_multi = None
2017-10-24 01:45:11 +02:00
2018-01-06 01:24:57 +01:00
for lineno, i in enumerate(f.readlines(), 1):
2020-07-23 21:00:01 +02:00
if re.match(r"[ \t]", i):
2018-01-06 01:24:57 +01:00
if len(dom) == 0:
2020-07-23 21:00:01 +02:00
log.error("File %s does not parse properly" % (fn))
2018-01-06 01:24:57 +01:00
self.valid = False
return
2017-10-24 01:45:11 +02:00
dom[-1][1] += "\n" + i.strip()
if dom[-1][0] not in multi:
multi[dom[-1][0]] = []
if last_multi is None:
multi[dom[-1][0]].append(lineno)
last_multi = dom[-1][0]
else:
i = i.split(":")
if len(i) < 2:
continue
2020-07-23 21:00:01 +02:00
dom.append([i[0].strip(), ":".join(i[1:]).strip(), lineno - 1])
2017-10-24 01:45:11 +02:00
if i[0].strip() not in keys:
keys[i[0].strip()] = []
2017-12-31 07:01:03 +01:00
keys[i[0].strip()].append(len(dom) - 1)
2017-10-24 01:45:11 +02:00
last_multi = None
2020-07-23 21:00:01 +02:00
if dom[-1][0] == "mnt-by":
2017-10-24 01:45:11 +02:00
mntner.append(dom[-1][1])
self.dom = dom
self.keys = keys
self.multi = multi
self.mntner = mntner
2017-11-12 17:29:10 +01:00
self.schema = SCHEMA_NAMESPACE + dom[0][0]
2017-10-24 01:45:11 +02:00
2017-11-02 21:01:09 +01:00
def __str__(self):
2017-12-05 20:43:19 +01:00
length = 19
2017-11-02 21:01:09 +01:00
for i in self.dom:
if len(i[0]) > length:
length = len(i[0]) + 2
s = ""
for i in self.dom:
l = i[1].split("\n")
s += i[0] + ":" + " " * (length - len(i[0])) + l[0] + "\n"
for m in l[1:]:
2020-07-23 21:00:01 +02:00
s += " " * (length + 1) + m + "\n"
2017-11-02 21:01:09 +01:00
return s
2017-12-12 01:33:55 +01:00
def get(self, key, index=0, default=None):
2020-07-23 21:00:01 +02:00
"get value"
2017-12-12 01:33:55 +01:00
if key not in self.keys:
return default
2017-12-31 07:01:03 +01:00
if index >= len(self.keys[key]) or index <= -len(self.keys[key]):
2017-12-12 01:33:55 +01:00
return default
2017-12-31 07:01:03 +01:00
2017-12-12 01:33:55 +01:00
return self.dom[self.keys[key][index]][1]
2017-10-24 01:45:11 +02:00
def main(infile, schema):
2020-07-23 21:00:01 +02:00
"main command"
2017-10-24 01:45:11 +02:00
log.debug("Check File: %s" % (infile))
f = FileDOM(infile)
if schema is not None:
f.schema = schema
else:
f.schema = "schema/" + f.schema
if f.schema is None:
log.error("Schema is not defined for file")
return False
log.debug("Use Schema: %s" % (f.schema))
s = SchemaDOM(f.schema)
return s.check_file(f)
2020-07-23 21:00:01 +02:00
2017-10-24 01:45:11 +02:00
def check_schemas(path):
2020-07-23 21:00:01 +02:00
"check schemas"
2017-10-24 01:45:11 +02:00
schemas = {}
2020-07-23 21:00:01 +02:00
for fn in glob.glob(path + "/*"):
2017-10-24 01:45:11 +02:00
s = SchemaDOM(fn)
log.info("read schema: %s" % (s.name))
schemas[s.ref] = s
ok = True
c = schemas[SCHEMA_NAMESPACE + "schema"]
2020-07-23 21:00:01 +02:00
for s in schemas:
2017-10-24 01:45:11 +02:00
ck = c.check_file(s)
if not ck:
ok = False
return ok
2020-07-23 21:00:01 +02:00
2017-10-24 01:45:11 +02:00
def scan_index(infile, mntner=None):
2020-07-23 21:00:01 +02:00
"scan index"
2017-10-24 01:45:11 +02:00
idx = {}
schemas = {}
2020-07-23 21:00:01 +02:00
with open(infile, "r") as f:
2017-10-24 01:45:11 +02:00
for line in f.readlines():
line = line.split()
idx[(line[0], line[1])] = line[2:]
2020-07-23 21:00:01 +02:00
if line[0] == SCHEMA_NAMESPACE + "schema":
2017-10-24 01:45:11 +02:00
s = SchemaDOM(line[2])
log.info("read schema: %s" % (s.name))
schemas[s.ref] = s
return __scan_index(idx, schemas, mntner)
2020-07-23 21:00:01 +02:00
2017-11-12 17:29:10 +01:00
def scan_files(path, mntner=None, use_file=None):
2020-07-23 21:00:01 +02:00
"scan files"
2017-11-12 17:29:10 +01:00
arr = __index_files(path, use_file)
2017-10-24 01:45:11 +02:00
idx = {}
schemas = {}
2018-01-06 01:24:57 +01:00
for dom in arr:
2020-07-23 21:00:01 +02:00
line = (
dom.schema,
dom.src.split("/")[-1].replace("_", "/"),
dom.src,
",".join(dom.mntner),
dom,
)
2018-01-06 01:24:57 +01:00
2017-10-24 01:45:11 +02:00
idx[(line[0], line[1])] = line[2:]
2020-07-23 21:00:01 +02:00
if line[0] == SCHEMA_NAMESPACE + "schema":
2017-10-24 01:45:11 +02:00
s = SchemaDOM(line[2])
schemas[s.ref] = s
2017-11-12 17:29:10 +01:00
return __scan_index(idx, schemas, mntner, use_file)
2020-07-23 21:00:01 +02:00
def __scan_index(idx, schemas, mntner, use_file=None):
2017-10-24 01:45:11 +02:00
ok = True
for k, v in idx.items():
2017-11-12 17:29:10 +01:00
if use_file is not None and use_file != v[0]:
continue
2017-10-24 01:45:11 +02:00
s = schemas.get(k[0], None)
if s is None:
log.error("No schema found for %s" % (k[1]))
2020-07-23 21:00:01 +02:00
print("CHECK\t%-54s\tFAIL\tMNTNERS: UNKNOWN" % (v[2].src))
2018-01-06 01:24:57 +01:00
ok = "FAIL"
2017-10-24 01:45:11 +02:00
2018-01-06 01:24:57 +01:00
else:
mlist = []
if len(v) > 1:
mlist = v[1].split(",")
if mntner is not None and mntner not in mlist:
continue
c = v[2]
ck = s.check_file(c, idx.keys())
if ck == "INFO" and ok != "FAIL":
ok = ck
if ck == "FAIL":
ok = ck
2017-10-24 01:45:11 +02:00
return ok
2020-07-23 21:00:01 +02:00
def __index_files(path, use_file=None):
2017-10-24 01:45:11 +02:00
xlat = {
2020-07-23 21:00:01 +02:00
"dns/": SCHEMA_NAMESPACE + "domain",
"inetnum/": SCHEMA_NAMESPACE + "inetnum",
"inet6num/": SCHEMA_NAMESPACE + "inet6num",
"route/": SCHEMA_NAMESPACE + "route",
"route6/": SCHEMA_NAMESPACE + "route6",
"aut-num/": SCHEMA_NAMESPACE + "aut-num",
"as-set/": SCHEMA_NAMESPACE + "as-set",
"as-block/": SCHEMA_NAMESPACE + "as-block",
2017-10-24 01:45:11 +02:00
"organisation/": SCHEMA_NAMESPACE + "organisation",
2020-07-23 21:00:01 +02:00
"mntner/": SCHEMA_NAMESPACE + "mntner",
"person/": SCHEMA_NAMESPACE + "person",
"role/": SCHEMA_NAMESPACE + "role",
"tinc-key/": SCHEMA_NAMESPACE + "tinc-key",
"tinc-keyset/": SCHEMA_NAMESPACE + "tinc-keyset",
"registry/": SCHEMA_NAMESPACE + "registry",
"schema/": SCHEMA_NAMESPACE + "schema",
"key-cert/": SCHEMA_NAMESPACE + "key-cert",
2017-10-24 01:45:11 +02:00
}
2020-07-23 21:00:01 +02:00
for root, _, files in os.walk(path):
2017-10-24 01:45:11 +02:00
ignore = True
2020-07-23 21:00:01 +02:00
for t in xlat:
if root + "/" == os.path.join(path, t):
ignore = False
break
2017-10-24 01:45:11 +02:00
if ignore:
2020-07-23 21:00:01 +02:00
continue
2017-10-24 01:45:11 +02:00
for f in files:
if f[0] == ".":
continue
2017-10-24 01:45:11 +02:00
dom = FileDOM(os.path.join(root, f))
2018-01-06 01:24:57 +01:00
yield dom
2017-10-24 01:45:11 +02:00
2017-11-12 17:29:10 +01:00
if use_file is not None:
2020-07-23 21:00:01 +02:00
dom = FileDOM(use_file)
yield dom
2017-10-24 01:45:11 +02:00
def index_files(path):
2020-07-23 21:00:01 +02:00
"index files"
2017-10-24 01:45:11 +02:00
idx = __index_files(path)
for i in idx:
print("%s\t%s\t%s\t%s" % i)
2017-11-06 17:52:54 +01:00
2020-07-23 21:00:01 +02:00
def http_get(server, url, query=None, headers=None):
"http get"
2017-11-06 17:52:54 +01:00
if headers is None:
headers = {}
2020-07-23 21:00:01 +02:00
if "User-Agent" not in headers:
headers["User-Agent"] = "curl"
if "Accept" not in headers:
headers["Accept"] = "application/json"
2017-11-06 17:52:54 +01:00
if query is None:
query = {}
http_client = http.client.HTTPSConnection(server)
2020-07-23 21:00:01 +02:00
full_url = url + "?" + urllib.parse.urlencode(query)
2017-11-06 17:52:54 +01:00
log.debug("GET " + full_url)
2020-07-23 21:00:01 +02:00
http_client.request("GET", full_url, headers=headers)
2017-11-06 17:52:54 +01:00
req = http_client.getresponse()
log.debug("HTTP Response: %d %s" % (req.status, req.reason))
if "application/json" in req.getheader("Content-Type", "application/json"):
if req.status > 299:
return {}
2017-11-12 16:15:57 +01:00
r = req.read()
if not isinstance(r, str):
2020-07-23 21:00:01 +02:00
r = r.decode("utf-8")
2017-11-12 16:15:57 +01:00
return json.loads(r)
2017-11-06 17:52:54 +01:00
if req.status > 299:
return ""
return req.read()
2020-07-23 21:00:01 +02:00
def find(fields=None, filters=None):
"find"
2017-12-11 21:54:38 +01:00
server = "registry.dn42.us"
2020-07-23 21:00:01 +02:00
url = "/v1/reg/reg.objects"
2017-11-06 17:52:54 +01:00
if fields is None:
fields = []
2020-07-23 21:00:01 +02:00
if filters is None:
filters = {}
query = {
"fields": ",".join(fields),
"filter": ",".join([k + "=" + v for k, v in filters.items()]),
}
2017-11-06 17:52:54 +01:00
return http_get(server, url, query)
2020-07-23 21:00:01 +02:00
2017-11-06 22:26:18 +01:00
def to_num(ip):
2020-07-23 21:00:01 +02:00
"ip to number"
ip = [int(i) for i in ip.split(".")]
2017-11-06 22:26:18 +01:00
return ip[3] + ip[2] * 256 + ip[1] * 256 ** 2 + ip[0] * 256 ** 3
2020-07-23 21:00:01 +02:00
2017-11-06 22:26:18 +01:00
def to_ip(num):
2020-07-23 21:00:01 +02:00
"number to ip"
return ".".join(
[str(i) for i in [num >> 24, (num >> 16) & 0xFF, (num >> 8) & 0xFF, num & 0xFF]]
)
2017-11-06 22:26:18 +01:00
2017-12-12 01:33:55 +01:00
def pretty_ip(addr):
2020-07-23 21:00:01 +02:00
"pretty ip"
2017-12-12 01:33:55 +01:00
if addr.startswith("00000000000000000000ffff"):
addr = addr[-8:]
addr = int(addr, 16)
return to_ip(addr)
2020-07-23 21:00:01 +02:00
return ":".join([addr[i:i + 4] for i in range(0, len(addr), 4)])
2017-12-12 01:33:55 +01:00
2017-11-06 22:26:18 +01:00
def expand_ipv6(addr):
2020-07-23 21:00:01 +02:00
"expand ip6"
2017-11-06 22:26:18 +01:00
addr = addr.lower()
if "::" in addr:
2020-07-23 21:00:01 +02:00
if addr.count("::") > 1:
2017-11-06 22:26:18 +01:00
return False
2020-07-23 21:00:01 +02:00
addr = addr.replace("::", ":" * (9 - addr.count(":")))
if addr.count(":") != 7:
2017-11-06 22:26:18 +01:00
return False
2020-07-23 21:00:01 +02:00
return "".join((i.zfill(4) for i in addr.split(":")))
2017-11-06 22:26:18 +01:00
2017-12-12 01:33:55 +01:00
def ip4_to_ip6(ip):
2020-07-23 21:00:01 +02:00
"ip4 to ip6"
return "::ffff:%04x:%04x" % (ip >> 16, ip & 0xFFFF)
2017-11-06 22:26:18 +01:00
def inetrange(inet):
2020-07-23 21:00:01 +02:00
"inet range"
ip, mask = inet.split("/")
2017-11-06 22:26:18 +01:00
mask = int(mask)
ip = to_num(ip) & (0xFFFFFFFF << 32 - mask)
2017-12-12 01:33:55 +01:00
ip6 = ip4_to_ip6(ip)
2017-11-06 22:26:18 +01:00
return inet6range("%s/%d" % (ip6, mask + 96))
2020-07-23 21:00:01 +02:00
2017-11-06 22:26:18 +01:00
def inet6range(inet):
2020-07-23 21:00:01 +02:00
"inet6 range"
ip, mask = inet.split("/")
2017-11-06 22:26:18 +01:00
mask = int(mask)
2017-11-08 19:36:05 +01:00
log.debug(ip)
2017-11-06 22:26:18 +01:00
ip = expand_ipv6(ip)
if mask == 128:
return ip, ip, mask
offset = int(ip[mask // 4], 16)
2020-07-23 21:00:01 +02:00
return (
"%s%x%s"
% (ip[: mask // 4], offset & (0xF0 >> mask % 4), "0" * (31 - mask // 4)),
"%s%x%s"
% (ip[: mask // 4], offset | (0xF >> mask % 4), "f" * (31 - mask // 4)),
mask,
)
2017-11-06 22:26:18 +01:00
2017-11-06 17:52:54 +01:00
def test_policy(obj_type, name, mntner):
2020-07-23 21:00:01 +02:00
"test policy"
2017-11-06 17:52:54 +01:00
log.debug([obj_type, name, mntner])
2020-07-23 21:00:01 +02:00
if obj_type in ["organisation",
"mntner",
"person",
"role",
"as-set",
"schema",
"dns",
"key-cert",
2020-07-23 21:00:01 +02:00
]:
2017-11-06 17:52:54 +01:00
if obj_type == "organisation" and not name.startswith("ORG-"):
2020-07-23 21:00:01 +02:00
log.error("%s does not start with 'ORG-'" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
elif obj_type == "mntner" and not name.endswith("-MNT"):
2020-07-23 21:00:01 +02:00
log.error("%s does not end with '-MNT'" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
elif obj_type == "dns" and not name.endswith(".dn42"):
2020-07-23 21:00:01 +02:00
log.error("%s does not end with '.dn42'" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
elif obj_type == "dns" and len(name.strip(".").split(".")) != 2:
2020-07-23 21:00:01 +02:00
log.error("%s is not a second level domain" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
elif obj_type in ["person", "role"] and not name.endswith("-DN42"):
2020-07-23 21:00:01 +02:00
log.error("%s does not end with '-DN42'" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
lis = find(["mnt-by"], {"@type": obj_type, "@name": name})
2017-11-13 17:01:27 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
if len(lis) == 0:
2020-07-23 21:00:01 +02:00
log.notice("%s does not currently exist" % (name))
2017-11-06 17:52:54 +01:00
return "PASS"
2020-07-23 21:00:01 +02:00
status = "FAIL"
2017-11-06 17:52:54 +01:00
for o in lis:
for n in o:
2017-11-13 17:01:27 +01:00
log.debug(n)
log.debug(mntner)
2017-11-06 17:52:54 +01:00
if n[0] == "mnt-by" and n[1] == mntner:
2020-07-23 21:00:01 +02:00
status = "PASS"
2017-11-06 17:52:54 +01:00
return status
2020-07-23 21:00:01 +02:00
log.error("%s does not have mnt for object" % (mntner))
2017-11-06 17:52:54 +01:00
return status
2020-07-23 21:00:01 +02:00
elif obj_type in ["inetnum", "inet6num"]:
2017-11-06 22:26:18 +01:00
log.info("Checking inetnum type")
lis = find(["mnt-by"], {"@type": "net", "cidr": name})
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
2017-11-06 22:26:18 +01:00
if len(lis) > 0:
2020-07-23 21:00:01 +02:00
status = "FAIL"
2017-11-06 22:26:18 +01:00
for o in lis:
for n in o:
if n[0] == "mnt-by" and n[1] == mntner:
2020-07-23 21:00:01 +02:00
status = "PASS"
log.notice("%s has mnt for current object" % (mntner))
2017-11-06 22:26:18 +01:00
return status
2020-07-23 21:00:01 +02:00
log.error("%s does not have mnt for current object" % (mntner))
2017-11-06 22:26:18 +01:00
return status
2020-07-23 21:00:01 +02:00
if obj_type == "inetnum":
2017-11-06 22:26:18 +01:00
Lnet, Hnet, mask = inetrange(name)
else:
Lnet, Hnet, mask = inet6range(name)
2017-12-12 01:33:55 +01:00
2020-07-23 21:00:01 +02:00
mask = "%03d" % (mask)
2017-11-06 22:26:18 +01:00
log.info([Lnet, Hnet, mask])
2020-07-23 21:00:01 +02:00
lis = find(
["inetnum", "inet6num", "policy", "@netlevel", "mnt-by", "mnt-lower"],
{
"@type": "net",
"@netmin": "le=" + Lnet,
"@netmax": "ge=" + Hnet,
"@netmask": "lt=" + mask,
},
)
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 22:26:18 +01:00
policy = {}
select = None
mntners = []
for n in lis:
obj = {}
for o in n:
obj[o[0]] = o[1]
if o[0].startswith("mnt-"):
mntners.append(o[1])
k = obj["@netlevel"]
policy[k] = obj
if select is None:
select = k
2020-07-23 21:00:01 +02:00
elif select <= k:
2017-11-06 22:26:18 +01:00
select = k
2020-07-23 21:00:01 +02:00
if select is None:
2017-11-06 22:26:18 +01:00
pass
2020-07-23 21:00:01 +02:00
elif policy.get(select, {}).get("policy", "closed") == "open":
2017-11-06 22:26:18 +01:00
log.notice("Policy is open for parent object")
return "PASS"
# 3. Check if mntner or mnt-lower for any as-block in the tree.
elif mntner in mntners:
2020-07-23 21:00:01 +02:00
log.notice("%s has mnt in parent object" % (mntner))
2017-11-06 22:26:18 +01:00
return "PASS"
2020-07-23 21:00:01 +02:00
elif obj_type in ["route", "route6"]:
2017-11-06 22:26:18 +01:00
log.info("Checking route type")
2020-07-23 21:00:01 +02:00
lis = find(["mnt-by"], {"@type": "route", obj_type: name})
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
2017-11-06 22:26:18 +01:00
if len(lis) > 0:
2020-07-23 21:00:01 +02:00
status = "FAIL"
2017-11-06 22:26:18 +01:00
for o in lis:
for n in o:
if n[0] == "mnt-by" and n[1] == mntner:
2020-07-23 21:00:01 +02:00
status = "PASS"
log.notice("%s has mnt for current object" % (mntner))
2017-11-06 22:26:18 +01:00
return status
2020-07-23 21:00:01 +02:00
log.error("%s does not have mnt for current object" % (mntner))
2017-11-06 22:26:18 +01:00
return status
2020-07-23 21:00:01 +02:00
if obj_type == "route":
2017-11-06 22:26:18 +01:00
Lnet, Hnet, mask = inetrange(name)
else:
Lnet, Hnet, mask = inet6range(name)
2020-07-23 21:00:01 +02:00
mask = "%03d" % (mask)
2017-11-06 22:26:18 +01:00
log.info([Lnet, Hnet, mask])
2020-07-23 21:00:01 +02:00
lis = find(
["inetnum", "inet6num", "policy", "@netlevel", "mnt-by", "mnt-lower"],
{
"@type": "net",
"@netmin": "le=" + Lnet,
"@netmax": "ge=" + Hnet,
"@netmask": "le=" + mask,
},
)
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 22:26:18 +01:00
policy = {}
select = None
mntners = []
for n in lis:
obj = {}
for o in n:
obj[o[0]] = o[1]
if o[0].startswith("mnt-"):
mntners.append(o[1])
k = obj["@netlevel"]
policy[k] = obj
if select is None:
select = k
2020-07-23 21:00:01 +02:00
elif select <= k:
2017-11-06 22:26:18 +01:00
select = k
2020-07-23 21:00:01 +02:00
if select is None:
2017-11-06 22:26:18 +01:00
pass
2020-07-23 21:00:01 +02:00
elif policy.get(select, {}).get("policy", "closed") == "open":
2017-11-06 22:26:18 +01:00
log.notice("Policy is open for parent object")
return "PASS"
# 3. Check if mntner or mnt-lower for any as-block in the tree.
elif mntner in mntners:
2020-07-23 21:00:01 +02:00
log.notice("%s has mnt in parent object" % (mntner))
2017-11-06 22:26:18 +01:00
return "PASS"
2020-07-23 21:00:01 +02:00
elif obj_type == "aut-num":
2017-11-06 17:52:54 +01:00
if not name.startswith("AS"):
2020-07-23 21:00:01 +02:00
log.error("%s does not start with AS" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
# 1. Check if they already have an object
lis = find(["mnt-by"], {"@type": "aut-num", "@name": name})
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
if len(lis) > 0:
2020-07-23 21:00:01 +02:00
status = "FAIL"
2017-11-06 17:52:54 +01:00
for o in lis:
for n in o:
if n[0] == "mnt-by" and n[1] == mntner:
2020-07-23 21:00:01 +02:00
status = "PASS"
log.notice("%s has mnt for current object" % (mntner))
2017-11-06 17:52:54 +01:00
return status
2020-07-23 21:00:01 +02:00
log.error("%s does not have mnt for current object" % (mntner))
2017-11-06 17:52:54 +01:00
return status
# 2. Check if the as-block has an open policy
asn = "AS{:0>9}".format(name[2:])
2020-07-23 21:00:01 +02:00
lis = find(
["as-block", "policy", "@as-min", "@as-max", "mnt-by", "mnt-lower"],
{"@type": "as-block", "@as-min": "le=" + asn, "@as-max": "ge=" + asn},
)
2017-11-06 17:52:54 +01:00
log.info(lis)
policy = {}
select = None
mntners = []
for n in lis:
obj = {}
for o in n:
obj[o[0]] = o[1]
if o[0].startswith("mnt-"):
mntners.append(o[1])
2020-07-23 21:00:01 +02:00
k = (obj["@as-min"], obj["@as-max"])
2017-11-06 17:52:54 +01:00
policy[k] = obj
if select is None:
select = k
2020-07-23 21:00:01 +02:00
elif select[0] <= k[0] or select[1] >= k[1]:
2017-11-06 17:52:54 +01:00
select = k
2020-07-23 21:00:01 +02:00
if policy.get(select, {}).get("policy", "closed") == "open":
2017-11-06 17:52:54 +01:00
log.notice("Policy is open for parent object")
return "PASS"
2017-11-08 19:05:00 +01:00
2017-11-06 17:52:54 +01:00
# 3. Check if mntner or mnt-lower for any as-block in the tree.
elif mntner in mntners:
2020-07-23 21:00:01 +02:00
log.notice("%s has mnt in parent object" % (mntner))
2017-11-06 17:52:54 +01:00
return "PASS"
2020-07-23 21:00:01 +02:00
elif obj_type == "as-block":
2017-11-06 17:52:54 +01:00
Lname, Hname = name.split("-")
Lname, Hname = Lname.strip(), Hname.strip()
if not Lname.startswith("AS") or not Hname.startswith("AS"):
2020-07-23 21:00:01 +02:00
log.error("%s does not start with AS for min and max" % (name))
2017-11-06 17:52:54 +01:00
return "FAIL"
# 1. Check if they already have an object
lis = find(["mnt-by"], {"@type": "as-block", "@name": name})
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
if len(lis) > 0:
2020-07-23 21:00:01 +02:00
status = "FAIL"
2017-11-06 17:52:54 +01:00
for o in lis:
for n in o:
if n[0] == "mnt-by" and n[1] == mntner:
2020-07-23 21:00:01 +02:00
status = "PASS"
log.notice("%s has mnt for current object" % (mntner))
2017-11-06 17:52:54 +01:00
return status
2020-07-23 21:00:01 +02:00
log.notice("%s does not have mnt for current object" % (mntner))
2017-11-06 17:52:54 +01:00
return status
# 2. Check if the parent as-blocks have an open policy
Lasn = "AS{:0>9}".format(Lname[2:])
Hasn = "AS{:0>9}".format(Hname[2:])
2017-11-08 19:05:00 +01:00
2017-11-06 17:52:54 +01:00
if Lasn > Hasn:
2020-07-23 21:00:01 +02:00
log.error("%s should come before %s" % (Lname, Hname))
2017-11-06 17:52:54 +01:00
2020-07-23 21:00:01 +02:00
lis = find(
["as-block", "policy", "@as-min", "@as-max", "mnt-by", "mnt-lower"],
{"@type": "as-block", "@as-min": "le=" + Lasn, "@as-max": "ge=" + Hasn},
)
2017-12-12 18:12:17 +01:00
log.debug(lis)
2017-11-06 17:52:54 +01:00
policy = {}
select = None
mntners = []
for n in lis:
obj = {}
for o in n:
obj[o[0]] = o[1]
if o[0].startswith("mnt-"):
mntners.append(o[1])
2020-07-23 21:00:01 +02:00
k = (obj["@as-min"], obj["@as-max"])
2017-11-06 17:52:54 +01:00
policy[k] = obj
if select is None:
select = k
2020-07-23 21:00:01 +02:00
elif select[0] <= k[0] or select[1] >= k[1]:
2017-11-06 17:52:54 +01:00
select = k
2017-11-06 22:26:18 +01:00
# Policy Open only applies to aut-nums. as-blocks must be defined by parent mntners only.
#
# if policy[select]["policy"] == "open":
# log.notice("Policy is open for parent object")
# return "PASS"
2017-11-06 17:52:54 +01:00
# 3. Check if mntner or mnt-lower for any as-block in the tree.
if mntner in mntners:
2020-07-23 21:00:01 +02:00
log.notice("%s has mnt in parent object" % (mntner))
2017-11-06 17:52:54 +01:00
return "PASS"
2020-07-23 21:00:01 +02:00
log.error("%s does not pass checks for %s %s" % (mntner, obj_type, name))
2017-11-08 19:05:00 +01:00
return "FAIL"
2017-11-06 17:52:54 +01:00
2020-07-23 21:00:01 +02:00
2017-12-12 01:33:55 +01:00
def sanity_check(dom):
2020-07-23 21:00:01 +02:00
"sanity check"
2017-12-12 01:33:55 +01:00
ck = "PASS"
if dom.schema == "dn42.inetnum":
cidr = dom.get("cidr")
2020-07-23 21:00:01 +02:00
Lnet, Hnet, _ = inetrange(cidr)
2017-12-31 00:18:06 +01:00
cidr_range = pretty_ip(Lnet) + "-" + pretty_ip(Hnet)
2017-12-12 01:33:55 +01:00
file_range = dom.get("inetnum")
2017-12-31 00:18:06 +01:00
file_range = re.sub(r"\s+", "", file_range, flags=re.UNICODE)
2017-12-12 01:33:55 +01:00
if cidr_range != file_range:
2020-07-23 21:00:01 +02:00
log.error(
"inetnum range [%s] does not match: [%s]" % (file_range, cidr_range)
)
2017-12-12 01:33:55 +01:00
ck = "FAIL"
if dom.schema == "dn42.inet6num":
cidr = dom.get("cidr")
2017-12-31 07:01:03 +01:00
log.info(cidr)
2020-07-23 21:00:01 +02:00
Lnet, Hnet, _ = inet6range(cidr)
2017-12-31 00:18:06 +01:00
cidr_range = pretty_ip(Lnet) + "-" + pretty_ip(Hnet)
2017-12-12 01:33:55 +01:00
file_range = dom.get("inet6num")
2017-12-31 00:18:06 +01:00
file_range = re.sub(r"\s+", "", file_range, flags=re.UNICODE)
2017-12-12 01:33:55 +01:00
if cidr_range != file_range:
2020-07-23 21:00:01 +02:00
log.error(
"inetnum range [%s] does not match: [%s]" % (file_range, cidr_range)
)
2017-12-12 01:33:55 +01:00
ck = "FAIL"
return ck
2020-07-23 21:00:01 +02:00
2017-10-24 01:45:11 +02:00
def get_args():
"""Get and parse command line arguments"""
parser = argparse.ArgumentParser(
2020-07-23 21:00:01 +02:00
description="Check Schema. Checks Schema of file for validity"
)
parser.add_argument(
"--merge-output",
help="Merge stderr into stdout (helps when reading output with pagers) [Default OFF]",
action="store_true",
)
parser.add_argument(
"-v",
"--verbose",
help="Enable verbose output [Default OFF]",
action="store_true",
)
parser.add_argument(
"-vv",
"--doubleVerbose",
help="Enable full verbose output [Default OFF]",
action="store_true",
)
subparsers = parser.add_subparsers(help="sub-command help", dest="command")
parser_file = subparsers.add_parser("check-file", help="Process a specific file")
2017-10-24 01:45:11 +02:00
parser_file.add_argument(
2020-07-23 21:00:01 +02:00
"-s",
"--use-schema",
nargs="?",
help="Override schema to validate [Default None]",
action="store",
)
parser_file.add_argument("infile", nargs="?", help="File to check", type=str)
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
parser_schema = subparsers.add_parser("check-schemas", help="Validate all schemas")
parser_schema.add_argument("path", nargs="?", help="Path for schemas", type=str)
2017-10-24 01:45:11 +02:00
2020-07-23 21:00:01 +02:00
parser_index = subparsers.add_parser("index", help="Generate index")
parser_index.add_argument("path", nargs="?", help="Path for dn42 data", type=str)
2017-10-24 01:45:11 +02:00
parser_scanindex = subparsers.add_parser(
2020-07-23 21:00:01 +02:00
"scan-index", help="Validate files in index"
)
2017-10-24 01:45:11 +02:00
parser_scanindex.add_argument(
2020-07-23 21:00:01 +02:00
"infile", nargs="?", help="Index file to scan", type=str
)
parser_scanindex.add_argument(
"-m",
"--use-mntner",
nargs="?",
help="Only scan files that has MNT [Default None]",
action="store",
)
parser_scan = subparsers.add_parser("scan", help="Validate files in index")
parser_scan.add_argument("path", nargs="?", help="Path for dn42 data", type=str)
2017-10-24 01:45:11 +02:00
parser_scan.add_argument(
2020-07-23 21:00:01 +02:00
"-m",
"--use-mntner",
nargs="?",
help="Only scan files that has a matching MNT [Default None]",
action="store",
)
parser_scan.add_argument(
"-f",
"--use-file",
nargs="?",
help="Only scan file given [Default None]",
action="store",
)
parser_fmt = subparsers.add_parser("fmt", help="Format file")
parser_fmt.add_argument(
"infile", nargs="?", help="Path for dn42 data file", type=str
)
2017-11-02 21:01:09 +01:00
parser_fmt.add_argument(
2020-07-23 21:00:01 +02:00
"-i", "--in-place", help="Format file in place", action="store_true"
)
2017-11-02 21:01:09 +01:00
2020-07-23 21:00:01 +02:00
parser_sane = subparsers.add_parser(
"sanity-check", help="Check the file for sane-ness"
)
2017-12-12 01:33:55 +01:00
parser_sane.add_argument(
2020-07-23 21:00:01 +02:00
"infile", nargs="?", help="Path for dn42 data file", type=str
)
2017-12-12 01:33:55 +01:00
2020-07-23 21:00:01 +02:00
parser_pol = subparsers.add_parser("policy", help="Format file")
parser_pol.add_argument("type", nargs="?", type=str, help="dn42 object type")
parser_pol.add_argument("name", nargs="?", type=str, help="dn42 object name")
parser_pol.add_argument("mntner", nargs="?", type=str, help="dn42 object mntner")
2017-11-06 17:52:54 +01:00
2020-07-23 21:00:01 +02:00
parser_mroute = subparsers.add_parser(
"match-routes", help="Match routes to inetnums"
)
_ = parser_mroute
2017-11-06 17:52:54 +01:00
2017-10-24 01:45:11 +02:00
return vars(parser.parse_args())
2020-07-23 21:00:01 +02:00
def run(args):
"run"
2017-10-24 01:45:11 +02:00
if args["merge_output"]:
log.OUTPUT = sys.stdout
if args["doubleVerbose"]:
log.default.level_console = log.VERB_DEBUG
log.default.level_full = True
if args["verbose"]:
log.default.level_console = log.VERB_INFO
log.debug(args)
valid = True
if args["command"] == "check-file":
valid = main(args["infile"], args["use_schema"])
if valid:
log.notice("Check %s: PASS" % (args["infile"]))
else:
log.fatal("Check %s: FAIL" % (args["infile"]))
elif args["command"] == "check-schemas":
valid = check_schemas(args["path"])
elif args["command"] == "index":
index_files(args["path"])
elif args["command"] == "scan-index":
scan_index(args["infile"], args["use_mntner"])
elif args["command"] == "scan":
2020-07-23 21:00:01 +02:00
log.notice(
"## Scan Started at %s"
% (time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()))
)
2017-11-12 17:29:10 +01:00
ck = scan_files(args["path"], args["use_mntner"], args["use_file"])
2020-07-23 21:00:01 +02:00
log.notice(
"## Scan Completed at %s"
% (time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()))
)
2017-10-25 23:23:34 +02:00
if ck == "INFO":
sys.exit(2)
elif ck == "FAIL":
sys.exit(1)
2017-11-02 21:01:09 +01:00
elif args["command"] == "fmt":
dom = FileDOM(args["infile"])
if args["in_place"]:
2020-07-23 21:00:01 +02:00
with open(args["infile"], "w+") as f:
2017-11-02 21:01:09 +01:00
f.write(str(dom))
2017-12-05 20:38:28 +01:00
else:
print(str(dom))
2017-11-06 17:52:54 +01:00
elif args["command"] == "policy":
if args["type"] is None:
log.fatal("Type should be provided")
if args["name"] is None:
log.fatal("Name should be provided")
if args["mntner"] is None:
log.fatal("Mntner should be provided")
2020-07-23 21:00:01 +02:00
if args["type"] in ["inetnum", "inet6num", "route", "route6"]:
args["name"] = args["name"].replace("_", "/")
2017-11-08 19:05:00 +01:00
status = test_policy(args["type"], args["name"], args["mntner"])
2017-11-06 17:52:54 +01:00
2020-07-23 21:00:01 +02:00
print(
"POLICY %-12s\t%-8s\t%20s\t%s"
% (args["mntner"], args["type"], args["name"], status)
)
2017-11-06 17:52:54 +01:00
if status != "PASS":
sys.exit(1)
2017-12-12 01:33:55 +01:00
elif args["command"] == "sanity-check":
dom = FileDOM(args["infile"])
ck = sanity_check(dom)
2020-07-23 21:00:01 +02:00
print("SANITY %-8s\t%20s\t%s" % (dom.schema.split(".")[1], args["infile"], ck))
2017-12-12 01:33:55 +01:00
if ck != "PASS":
sys.exit(1)
2017-11-06 17:52:54 +01:00
elif args["command"] == "match-routes":
2020-07-23 21:00:01 +02:00
lis = find(
["mnt-by", "cidr", "route", "@netlevel", "@netmin", "@netmax", "@uri"],
{"@family": "ipv4"},
)
2017-11-06 17:52:54 +01:00
def field(x, field):
for i in x:
if i[0] == field:
return i[1]
return None
def lvl(x):
for i in x:
if i[0] == "@netlevel":
return i[1]
def net(x):
for i in x:
if i[0] == "@netmin":
return i[1]
def is_net(x):
i = field(x, "cidr")
if i is not None:
2020-07-23 21:00:01 +02:00
return True
2017-11-06 17:52:54 +01:00
return False
def obj(x):
d = {}
2020-07-23 21:00:01 +02:00
for k, v in x:
2017-11-06 17:52:54 +01:00
if k in d:
d[k].append(v)
else:
d[k] = [v]
return d
inet = None
first = True
for n in sorted(sorted(lis, key=lvl), key=net):
o = obj(n)
if is_net(n):
if not first:
print()
first = True
inet = o
continue
ilvl = int(inet["@netlevel"][0])
rlvl = int(o["@netlevel"][0])
if ilvl + 1 != rlvl:
2020-07-23 21:00:01 +02:00
print(
"\nNo Parent > ",
o["route"][0],
" ",
rlvl,
" ",
",".join(o["mnt-by"]),
"Nearest INET ",
inet["cidr"][0],
" ",
ilvl,
" ",
",".join(inet["mnt-by"]),
)
2017-11-06 17:52:54 +01:00
first = True
continue
if inet["@netmin"][0] > o["@netmin"][0] or inet["@netmax"][0] < o["@netmax"][0]:
2020-07-23 21:00:01 +02:00
print(
"\nNo Parent > ",
o["route"][0],
" ",
rlvl,
" ",
",".join(o["mnt-by"]),
"Nearest INET ",
inet["cidr"][0],
" ",
ilvl,
" ",
",".join(inet["mnt-by"]),
)
2017-11-06 17:52:54 +01:00
first = True
continue
2020-07-23 21:00:01 +02:00
if __name__ == "__main__":
run(get_args())