From 831f59eb43b56642b00f82e07722836d2f9593ab Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 20 Mar 2013 14:31:10 -0700 Subject: [PATCH] [master] add dnssec-coverage tool 3528. [func] New "dnssec-coverage" command scans the timing metadata for a set of DNSSEC keys and reports if a lapse in signing coverage has been scheduled inadvertently. (Note: This tool depends on python; it will not be built or installed on systems that do not have a python interpreter.) [RT #28098] --- CHANGES | 7 + bin/python/.gitignore | 2 + bin/python/Makefile.in | 14 +- bin/python/dnssec-coverage.docbook | 228 ++++++ bin/python/dnssec-coverage.py.in | 737 ++++++++++++++++++ bin/tests/system/conf.sh.in | 5 +- .../system/coverage/01-ksk-inactive/README | 10 + .../system/coverage/01-ksk-inactive/expect | 6 + .../system/coverage/02-zsk-inactive/README | 10 + .../system/coverage/02-zsk-inactive/expect | 6 + .../system/coverage/03-ksk-unpublished/README | 10 + .../system/coverage/03-ksk-unpublished/expect | 7 + .../system/coverage/04-zsk-unpublished/README | 10 + .../system/coverage/04-zsk-unpublished/expect | 7 + .../coverage/05-ksk-unpub-active/README | 12 + .../coverage/05-ksk-unpub-active/expect | 7 + .../coverage/06-zsk-unpub-active/README | 12 + .../coverage/06-zsk-unpub-active/expect | 7 + bin/tests/system/coverage/07-ksk-ttl/README | 4 + bin/tests/system/coverage/07-ksk-ttl/expect | 7 + bin/tests/system/coverage/08-zsk-ttl/README | 4 + bin/tests/system/coverage/08-zsk-ttl/expect | 7 + bin/tests/system/coverage/clean.sh | 6 + bin/tests/system/coverage/setup.sh | 106 +++ bin/tests/system/coverage/tests.sh | 67 ++ bin/tests/system/dnssec/tests.sh | 21 + configure | 8 +- configure.in | 5 + doc/arm/Bv9ARM-book.xml | 2 + 29 files changed, 1326 insertions(+), 8 deletions(-) create mode 100644 bin/python/dnssec-coverage.docbook create mode 100755 bin/python/dnssec-coverage.py.in create mode 100644 bin/tests/system/coverage/01-ksk-inactive/README create mode 100644 bin/tests/system/coverage/01-ksk-inactive/expect create mode 100644 bin/tests/system/coverage/02-zsk-inactive/README create mode 100644 bin/tests/system/coverage/02-zsk-inactive/expect create mode 100644 bin/tests/system/coverage/03-ksk-unpublished/README create mode 100644 bin/tests/system/coverage/03-ksk-unpublished/expect create mode 100644 bin/tests/system/coverage/04-zsk-unpublished/README create mode 100644 bin/tests/system/coverage/04-zsk-unpublished/expect create mode 100644 bin/tests/system/coverage/05-ksk-unpub-active/README create mode 100644 bin/tests/system/coverage/05-ksk-unpub-active/expect create mode 100644 bin/tests/system/coverage/06-zsk-unpub-active/README create mode 100644 bin/tests/system/coverage/06-zsk-unpub-active/expect create mode 100644 bin/tests/system/coverage/07-ksk-ttl/README create mode 100644 bin/tests/system/coverage/07-ksk-ttl/expect create mode 100644 bin/tests/system/coverage/08-zsk-ttl/README create mode 100644 bin/tests/system/coverage/08-zsk-ttl/expect create mode 100644 bin/tests/system/coverage/clean.sh create mode 100644 bin/tests/system/coverage/setup.sh create mode 100644 bin/tests/system/coverage/tests.sh diff --git a/CHANGES b/CHANGES index 4c987efd83..431b65bc20 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +3528. [func] New "dnssec-coverage" command scans the timing + metadata for a set of DNSSEC keys and reports if a + lapse in signing coverage has been scheduled + inadvertently. (Note: This tool depends on python; + it will not be built or installed on systems that + do not have a python interpreter.) [RT #28098] + 3527. [compat] Add a URI to allow applications to explicitly request a particular XML schema from the statistics channel, returning 404 if not supported. [RT #32481] diff --git a/bin/python/.gitignore b/bin/python/.gitignore index 7df1706762..f770f2396e 100644 --- a/bin/python/.gitignore +++ b/bin/python/.gitignore @@ -1,2 +1,4 @@ dnssec-checkds dnssec-checkds.py +dnssec-coverage +dnssec-coverage.py diff --git a/bin/python/Makefile.in b/bin/python/Makefile.in index fe647fdde7..12695ed586 100644 --- a/bin/python/Makefile.in +++ b/bin/python/Makefile.in @@ -22,17 +22,19 @@ top_srcdir = @top_srcdir@ PYTHON = @PYTHON@ -TARGETS = dnssec-checkds -SRCS = dnssec-checkds.py +TARGETS = dnssec-checkds dnssec-coverage +SRCS = dnssec-checkds.py dnssec-coverage.py -MANPAGES = dnssec-checkds.8 -HTMLPAGES = dnssec-checkds.html +MANPAGES = dnssec-checkds.8 dnssec-coverage.8 +HTMLPAGES = dnssec-checkds.html dnssec-coverage.html MANOBJS = ${MANPAGES} ${HTMLPAGES} @BIND9_MAKE_RULES@ dnssec-checkds: dnssec-checkds.py +dnssec-coverage: dnssec-coverage.py + doc man:: ${MANOBJS} docclean manclean maintainer-clean:: @@ -44,10 +46,12 @@ installdirs: install:: ${TARGETS} installdirs ${INSTALL_PROGRAM} dnssec-checkds@EXEEXT@ ${DESTDIR}${sbindir} + ${INSTALL_PROGRAM} dnssec-coverage@EXEEXT@ ${DESTDIR}${sbindir} ${INSTALL_DATA} ${srcdir}/dnssec-checkds.8 ${DESTDIR}${mandir}/man8 + ${INSTALL_DATA} ${srcdir}/dnssec-coverage.8 ${DESTDIR}${mandir}/man8 clean distclean:: rm -f ${TARGETS} distclean:: - rm -f dnssec-checkds.py + rm -f dnssec-checkds.py dnssec-coverage.py diff --git a/bin/python/dnssec-coverage.docbook b/bin/python/dnssec-coverage.docbook new file mode 100644 index 0000000000..f78c4cbe20 --- /dev/null +++ b/bin/python/dnssec-coverage.docbook @@ -0,0 +1,228 @@ +]> + + + + + April 16, 2012 + + + + dnssec-coverage + 8 + BIND9 + + + + dnssec-coverage + checks future DNSKEY coverage for a zone + + + + + 2012 + Internet Systems Consortium, Inc. ("ISC") + + + + + + dnssec-coverage + + + + + + + zone + + + + + DESCRIPTION + dnssec-coverage + verifies that the DNSSEC keys for a given zone or a set of zones + have timing metadata set properly to ensure no future lapses in DNSSEC + coverage. + + + If is specified, then keys found in + the key repository matching that zone are scanned, and an ordered + list is generated of the events scheduled for that key (i.e., + publication, activation, inactivation, deletion). The list of + events is walked in order of occurrence. Warnings are generated + if any event is scheduled which could cause the zone to enter a + state in which validation failures might occur: for example, if + the number of published or active keys for a given algorithm drops + to zero, or if a key is deleted from the zone too soon after a new + key is rolled, and cached data signed by the prior key has not had + time to expire from resolver caches. + + + If is not specified, then all keys in the + key repository will be scanned, and all zones for which there are + keys will be analyzed. (Note: This method of reporting is only + accurate if all the zones that have keys in a given repository + share the same TTL parameters.) + + + + + OPTIONS + + + + -f file + + + If a is specified, then the zone is + read from that file; the largest TTL and the DNSKEY TTL are + determined directly from the zone data, and the + and options do + not need to be specified on the command line. + + + + + + -K directory + + + Sets the directory in which keys can be found. Defaults to the + current working directory. + + + + + + -m maximum TTL + + + Sets the value to be used as the maximum TTL for the zone or + zones being analyzed when determining whether there is a + possibility of validation failure. When a zone-signing key is + deactivated, there must be enough time for the record in the + zone with the longest TTL to have expired from resolver caches + before that key can be purged from the DNSKEY RRset. If that + condition does not apply, a warning will be generated. + + + The length of the TTL can be set in seconds, or in larger units + of time by adding a suffix: 'mi' for minutes, 'h' for hours, + 'd' for days, 'w' for weeks, 'mo' for months, 'y' for years. + + + This option is mandatory unless the has + been used to specify a zone file. (If has + been specified, this option may still be used; it will overrde + the value found in the file.) + + + + + + -d DNSKEY TTL + + + Sets the value to be used as the DNSKEY TTL for the zone or + zones being analyzed when determining whether there is a + possibility of validation failure. When a key is rolled (that + is, replaced with a new key), there must be enough time + for the old DNSKEY RRset to have expired from resolver caches + before the new key is activated and begins generating + signatures. If that condition does not apply, a warning + will be generated. + + + The length of the TTL can be set in seconds, or in larger units + of time by adding a suffix: 'mi' for minutes, 'h' for hours, + 'd' for days, 'w' for weeks, 'mo' for months, 'y' for years. + + + This option is mandatory unless the has + been used to specify a zone file, or a default key TTL was + set with the to + dnssec-keygen. (If either of those is true, + this option may still be used; it will overrde the value found + in the zone or key file.) + + + + + + -r resign interval + + + Sets the value to be used as the resign interval for the zone + or zones being analyzed when determining whether there is a + possibility of validation failure. This value defaults to + 22.5 days, which is also the default in + named. However, if it has been changed + by the option in + named.conf, then it should also be + changed here. + + + The length of the interval can be set in seconds, or in larger + units of time by adding a suffix: 'mi' for minutes, 'h' for hours, + 'd' for days, 'w' for weeks, 'mo' for months, 'y' for years. + + + + + + -c compilezone path + + + Specifies a path to a named-compilezone binary. + Used for testing. + + + + + + + + SEE ALSO + + + dnssec-checkds8 + , + + dnssec-dsfromkey8 + , + + dnssec-keygen8 + , + + dnssec-signzone8 + + + + + + AUTHOR + Internet Systems Consortium + + + + diff --git a/bin/python/dnssec-coverage.py.in b/bin/python/dnssec-coverage.py.in new file mode 100755 index 0000000000..06b91a38ab --- /dev/null +++ b/bin/python/dnssec-coverage.py.in @@ -0,0 +1,737 @@ +#!@PYTHON@ +############################################################################ +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +############################################################################ +import argparse +import os +import glob +import sys +import re +import time +import calendar +from collections import defaultdict +import pprint + +prog='dnssec-coverage' + +######################################################################## +# Class Event +######################################################################## +class Event: + """ A discrete key metadata event, e.g., Publish, Activate, Inactive, + Delete. Stores the date of the event, and identifying information about + the key to which the event will occur.""" + + def __init__(self, _what, _key): + now = time.time() + self.what = _what + self.when = _key.metadata[_what] + self.key = _key + self.keyid = _key.keyid + self.sep = _key.sep + self.zone = _key.zone + self.alg = _key.alg + + def __repr__(self): + return repr((self.when, self.what, self.keyid, self.sep, + self.zone, self.alg)) + + def showtime(self): + return time.strftime("%a %b %d %H:%M:%S UTC %Y", self.when) + + def showkey(self): + return self.key.showkey() + + def showkeytype(self): + return self.key.showkeytype() + +######################################################################## +# Class Key +######################################################################## +class Key: + """An individual DNSSEC key. Identified by path, zone, algorithm, keyid. + Contains a dictionary of metadata events.""" + + def __init__(self, keyname): + directory = os.path.dirname(keyname) + key = os.path.basename(keyname) + (zone, alg, keyid) = key.split('+') + keyid = keyid.split('.')[0] + key = [zone, alg, keyid] + key_file = directory + os.sep + '+'.join(key) + ".key" + private_file = directory + os.sep + '+'.join(key) + ".private" + + self.zone = zone[1:-1] + self.alg = int(alg) + self.keyid = int(keyid) + + kfp = file(key_file, "r") + for line in kfp: + if line[0] == ';': + continue + tokens = line.split() + if not tokens: + continue + + if tokens[1].lower() in ('in', 'ch', 'hs'): + septoken = 3 + self.ttl = args.keyttl + if not self.ttl: + vspace() + print("WARNING: Unable to determine TTL for DNSKEY %s." % + self.showkey()) + print("\t Using 1 day (86400 seconds); re-run with the -d " + "option for more\n\t accurate results.") + self.ttl = 86400 + else: + septoken = 4 + self.ttl = int(tokens[1]) if not args.keyttl else args.keyttl + + if (int(tokens[septoken]) & 0x1) == 1: + self.sep = True + else: + self.sep = False + kfp.close() + + pfp = file(private_file, "rU") + propDict = dict() + for propLine in pfp: + propDef = propLine.strip() + if len(propDef) == 0: + continue + if propDef[0] in ('!', '#'): + continue + punctuation = [propDef.find(c) for c in ':= '] + [len(propDef)] + found = min([ pos for pos in punctuation if pos != -1 ]) + name = propDef[:found].rstrip() + value = propDef[found:].lstrip(":= ").rstrip() + propDict[name] = value + + if("Publish" in propDict): + propDict["Publish"] = time.strptime(propDict["Publish"], + "%Y%m%d%H%M%S") + + if("Activate" in propDict): + propDict["Activate"] = time.strptime(propDict["Activate"], + "%Y%m%d%H%M%S") + + if("Inactive" in propDict): + propDict["Inactive"] = time.strptime(propDict["Inactive"], + "%Y%m%d%H%M%S") + + if("Delete" in propDict): + propDict["Delete"] = time.strptime(propDict["Delete"], + "%Y%m%d%H%M%S") + + if("Revoke" in propDict): + propDict["Revoke"] = time.strptime(propDict["Revoke"], + "%Y%m%d%H%M%S") + pfp.close() + self.metadata = propDict + + def showkey(self): + return "%s/%03d/%05d" % (self.zone, self.alg, self.keyid); + + def showkeytype(self): + return ("KSK" if self.sep else "ZSK") + + # ensure that the gap between Publish and Activate is big enough + def check_prepub(self): + now = time.time() + + if (not "Activate" in self.metadata): + debug_print("No Activate information in key: %s" % self.showkey()) + return False + a = calendar.timegm(self.metadata["Activate"]) + + if (not "Publish" in self.metadata): + debug_print("No Publish information in key: %s" % self.showkey()) + if a > now: + vspace() + print("WARNING: Key %s (%s) is scheduled for activation but \n" + "\t not for publication." % + (self.showkey(), self.showkeytype())) + return False + p = calendar.timegm(self.metadata["Publish"]) + + now = time.time() + if p < now and a < now: + return True + + if p == a: + vspace() + print ("WARNING: %s (%s) is scheduled to be published and\n" + "\t activated at the same time. This could result in a\n" + "\t coverage gap if the zone was previously signed." % + (self.showkey(), self.showkeytype())) + print("\t Activation should be at least %s after publication." + % duration(self.ttl)) + return True + + if a < p: + vspace() + print("WARNING: Key %s (%s) is active before it is published" % + (self.showkey(), self.showkeytype())) + return False + + if (a - p < self.ttl): + vspace() + print("WARNING: Key %s (%s) is activated too soon after\n" + "\t publication; this could result in coverage gaps due to\n" + "\t resolver caches containing old data." + % (self.showkey(), self.showkeytype())) + print("\t Activation should be at least %s after publication." % + duration(self.ttl)) + return False + + return True + + # ensure that the gap between Inactive and Delete is big enough + def check_postpub(self, timespan = None): + if not timespan: + timespan = self.ttl + + now = time.time() + + if (not "Delete" in self.metadata): + debug_print("No Delete information in key: %s" % self.showkey()) + return False + d = calendar.timegm(self.metadata["Delete"]) + + if (not "Inactive" in self.metadata): + debug_print("No Inactive information in key: %s" % self.showkey()) + if d > now: + vspace() + print("WARNING: Key %s (%s) is scheduled for deletion but\n" + "\t not for inactivation." % + (self.showkey(), self.showkeytype())) + return False + i = calendar.timegm(self.metadata["Inactive"]) + + if d < now and i < now: + return True + + if (d < i): + vspace() + print("WARNING: Key %s (%s) is scheduled for deletion before\n" + "\t inactivation." % (self.showkey(), self.showkeytype())) + return False + + if (d - i < timespan): + vspace() + print("WARNING: Key %s (%s) scheduled for deletion too soon after\n" + "\t deactivation; this may result in coverage gaps due to\n" + "\t resolver caches containing old data." + % (self.showkey(), self.showkeytype())) + print("\t Deletion should be at least %s after inactivation." % + duration(timespan)) + return False + + return True + +######################################################################## +# class Zone +######################################################################## +class Zone: + """Stores data about a specific zone""" + + def __init__(self, _name, _keyttl = None, _maxttl = None): + self.name = _name + self.keyttl = _keyttl + self.maxttl = _maxttl + + def load(self, filename): + if not args.compilezone: + sys.stderr.write(prog + ': FATAL: "named-compilezone" not found\n') + exit(1) + + if not self.name: + return + + maxttl = keyttl = None + + fp = os.popen("%s -o - %s %s 2> /dev/null" % + (args.compilezone, self.name, filename)) + for line in fp: + fields = line.split() + if not maxttl or int(fields[1]) > maxttl: + maxttl = int(fields[1]) + if fields[3] == "DNSKEY": + keyttl = int(fields[1]) + fp.close() + + self.keyttl = keyttl + self.maxttl = maxttl + +############################################################################ +# debug_print: +############################################################################ +def debug_print(debugVar): + """pretty print a variable iff debug mode is enabled""" + if not args.debug_mode: + return + if type(debugVar) == str: + print("DEBUG: " + debugVar) + else: + print("DEBUG: " + pprint.pformat(debugVar)) + return + +############################################################################ +# vspace: +############################################################################ +_firstline = True +def vspace(): + """adds vertical space between two sections of output text if and only + if this is *not* the first section being printed""" + global _firstline + if _firstline: + _firstline = False + else: + print + +############################################################################ +# vreset: +############################################################################ +def vreset(): + """reset vertical spacing""" + global _firstline + _firstline = True + +############################################################################ +# getunit +############################################################################ +def getunit(secs, size): + """given a number of seconds, and a number of seconds in a larger unit of + time, calculate how many of the larger unit there are and return both + that and a remainder value""" + bigunit = secs // size + if bigunit: + secs %= size + return (bigunit, secs) + +############################################################################ +# addtime +############################################################################ +def addtime(output, unit, t): + """add a formatted unit of time to an accumulating string""" + if t: + output += ("%s%d %s%s" % + ((", " if output else ""), + t, unit, ("s" if t > 1 else ""))) + + return output + +############################################################################ +# duration: +############################################################################ +def duration(secs): + """given a length of time in seconds, print a formatted human duration + in larger units of time + """ + # define units: + minute = 60 + hour = minute * 60 + day = hour * 24 + month = day * 30 + year = day * 365 + + # calculate time in units: + (years, secs) = getunit(secs, year) + (months, secs) = getunit(secs, month) + (days, secs) = getunit(secs, day) + (hours, secs) = getunit(secs, hour) + (minutes, secs) = getunit(secs, minute) + + output = '' + output = addtime(output, "year", years) + output = addtime(output, "month", months) + output = addtime(output, "day", days) + output = addtime(output, "hour", hours) + output = addtime(output, "minute", minutes) + output = addtime(output, "second", secs) + return output + +############################################################################ +# parse_time +############################################################################ +def parse_time(s): + """convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds""" + s = s.strip() + + # if s is an integer, we're done already + try: + n = int(s) + return n + except: + pass + + # try to parse as a number with a suffix indicating unit of time + r = re.compile('([0-9][0-9]*)\s*([A-Za-z]*)') + m = r.match(s) + if not m: + raise Exception("Cannot parse %s" % s) + (n, unit) = m.groups() + n = int(n) + unit = unit.lower() + if unit[0] == 'y': + return n * 31536000 + elif unit[0] == 'm' and unit[1] == 'o': + return n * 2592000 + elif unit[0] == 'w': + return n * 604800 + elif unit[0] == 'd': + return n * 86400 + elif unit[0] == 'h': + return n * 3600 + elif unit[0] == 'm' and unit[1] == 'i': + return n * 60 + elif unit[0] == 's': + return n + else: + raise Exception("Invalid suffix %s" % unit) + +############################################################################ +# algname: +############################################################################ +def algname(alg): + """return the mnemonic for a DNSSEC algorithm""" + names = (None, 'RSAMD5', 'DH', 'DSA', 'ECC', 'RSASHA1', + 'NSEC3DSA', 'NSEC3RSASHA1', 'RSASHA256', None, + 'RSASHA512', None, 'ECCGOST', 'ECDSAP256SHA256', + 'ECDSAP384SHA384') + name = None + if alg in range(len(names)): + name = names[alg] + return (name if name else str(alg)) + +############################################################################ +# list_events: +############################################################################ +def list_events(eventgroup): + """print a list of the events in an eventgroup""" + if not eventgroup: + return + print (" " + eventgroup[0].showtime() + ":") + for event in eventgroup: + print (" %s: %s (%s)" % + (event.what, event.showkey(), event.showkeytype())) + +############################################################################ +# process_events: +############################################################################ +def process_events(eventgroup, active, published): + """go through the events in an event group in time-order, add to active + list upon Activate event, add to published list upon Publish event, + remove from active list upon Inactive event, and remove from published + upon Delete event. Emit warnings when inconsistant states are reached""" + for event in eventgroup: + if event.what == "Activate": + active.add(event.keyid) + elif event.what == "Publish": + published.add(event.keyid) + elif event.what == "Inactive": + if event.keyid not in active: + vspace() + print ("\tWARNING: %s (%s) scheduled to become inactive " + "before it is active" % + (event.showkey(), event.showkeytype())) + else: + active.remove(event.keyid) + elif event.what == "Delete": + if event.keyid in published: + published.remove(event.keyid) + else: + vspace() + print ("WARNING: key %s (%s) is scheduled for deletion before " + "it is published, at %s" % + (event.showkey(), event.showkeytype())) + elif event.what == "Revoke": + # We don't need to worry about the logic of this one; + # just stop counting this key as either active or published + if event.keyid in published: + published.remove(event.keyid) + if event.keyid in active: + active.remove(event.keyid) + + return (active, published) + +############################################################################ +# check_events: +############################################################################ +def check_events(eventsList, ksk): + """create lists of events happening at the same time, check for + inconsistancies""" + active = set() + published = set() + eventgroups = list() + eventgroup = list() + keytype = ("KSK" if ksk else "ZSK") + + # collect up all events that have the same time + eventsfound = False + for event in eventsList: + # if checking ZSKs, skip KSKs, and vice versa + if (ksk and not event.sep) or (event.sep and not ksk): + continue + + # we found an appropriate (ZSK or KSK event) + eventsfound = True + + # add event to current eventgroup + if (not eventgroup or eventgroup[0].when == event.when): + eventgroup.append(event) + + # if we're at the end of the list, we're done. if + # we've found an event with a later time, start a new + # eventgroup + if (eventgroup[0].when != event.when): + eventgroups.append(eventgroup) + eventgroup = list() + eventgroup.append(event) + + if eventgroup: + eventgroups.append(eventgroup) + + for eventgroup in eventgroups: + (active, published) = \ + process_events(eventgroup, active, published) + + list_events(eventgroup) + + # and then check for inconsistencies: + if len(active) == 0: + print ("ERROR: No %s's are active after this event" % keytype) + return False + elif len(published) == 0: + sys.stdout.write("ERROR: ") + print ("ERROR: No %s's are published after this event" % keytype) + return False + elif len(published.intersection(active)) == 0: + sys.stdout.write("ERROR: ") + print (("ERROR: No %s's are both active and published " + + "after this event") % keytype) + return False + + if not eventsfound: + print ("ERROR: No %s events found in '%s'" % + (keytype, args.path)) + return False + + return True + +############################################################################ +# check_zones: +# ############################################################################ +def check_zones(eventsList): + """scan events per zone, algorithm, and key type, in order of occurrance, + noting inconsistent states when found""" + global foundprob + + foundprob = False + zonesfound = False + for zone in eventsList: + if args.zone and zone != args.zone: + continue + + zonesfound = True + for alg in eventsList[zone]: + vspace() + print("Checking scheduled KSK events for zone %s, algorithm %s..." % + (zone, algname(alg))) + if not check_events(eventsList[zone][alg], True): + foundprob = True + else: + print ("No errors found") + + vspace() + print("Checking scheduled ZSK events for zone %s, algorithm %s..." % + (zone, algname(alg))) + if not check_events(eventsList[zone][alg], False): + foundprob = True + else: + print ("No errors found") + + if not zonesfound: + print("ERROR: No key events found for %s in '%s'" % + (args.zone, args.path)) + exit(1) + +############################################################################ +# fill_eventsList: +############################################################################ +def fill_eventsList(eventsList): + """populate the list of events""" + for zone, algorithms in keyDict.items(): + for alg, keys in algorithms.items(): + for keyid, keydata in keys.items(): + if("Publish" in keydata.metadata): + eventsList[zone][alg].append(Event("Publish", keydata)) + if("Activate" in keydata.metadata): + eventsList[zone][alg].append(Event("Activate", keydata)) + if("Inactive" in keydata.metadata): + eventsList[zone][alg].append(Event("Inactive", keydata)) + if("Delete" in keydata.metadata): + eventsList[zone][alg].append(Event("Delete", keydata)) + + eventsList[zone][alg] = sorted(eventsList[zone][alg], + key=lambda event: event.when) + + foundprob = False + if not keyDict: + print("ERROR: No key events found in '%s'" % args.path) + exit(1) + +############################################################################ +# set_path: +############################################################################ +def set_path(command, default=None): + """find the location of a specified command. if a default is supplied + and it works, we use it; otherwise we search PATH for a match. If + not found, error and exit""" + fpath = default + if not fpath or not os.path.isfile(fpath) or not os.access(fpath, os.X_OK): + path = os.environ["PATH"] + if not path: + path = os.path.defpath + for directory in path.split(os.pathsep): + fpath = directory + os.sep + command + if os.path.isfile(fpath) or os.access(fpath, os.X_OK): + break + fpath = None + + return fpath + +############################################################################ +# parse_args: +############################################################################ +def parse_args(): + """Read command line arguments, set global 'args' structure""" + global args + + compilezone = set_path('named-compilezone', + '@prefix@/sbin/named-compilezone') + + parser = argparse.ArgumentParser(description=prog + ': checks future ' + + 'DNSKEY coverage for a zone') + + parser.add_argument('zone', type=str, help='zone to check') + parser.add_argument('-K', dest='path', default='.', type=str, + help='a directory containing keys to process', + metavar='dir') + parser.add_argument('-f', dest='filename', type=str, + help='zone master file', metavar='file') + parser.add_argument('-m', dest='maxttl', type=str, + help='the longest TTL in the zone(s)', + metavar='int') + parser.add_argument('-d', dest='keyttl', type=str, + help='the DNSKEY TTL', metavar='int') + parser.add_argument('-r', dest='resign', default='1944000', + type=int, help='the RRSIG refresh interval ' + 'in seconds [default: 22.5 days]', + metavar='int') + parser.add_argument('-c', dest='compilezone', + default=compilezone, type=str, + help='path to \'named-compilezone\'', + metavar='path') + parser.add_argument('-D', '--debug', dest='debug_mode', + action='store_true', default=False, + help='Turn on debugging output') + parser.add_argument('-v', '--version', action='version', version='9.9.1') + + args = parser.parse_args() + + # convert from time arguments to seconds + try: + if args.maxttl: + m = parse_time(args.maxttl) + args.maxttl = m + except: + pass + + try: + if args.keyttl: + k = parse_time(args.keyttl) + args.keyttl = k + except: + pass + + try: + if args.resign: + r = parse_time(args.resign) + args.resign = r + except: + pass + + # if we've got the values we need from the command line, stop now + if args.maxttl and args.keyttl: + return + + # load keyttl and maxttl data from zonefile + if args.zone and args.filename: + try: + zone = Zone(args.zone) + zone.load(args.filename) + if not args.maxttl: + args.maxttl = zone.maxttl + if not args.keyttl: + args.keyttl = zone.maxttl + except Exception as e: + print("Unable to load zone data from %s: " % args.filename, e) + + if not args.maxttl: + vspace() + print ("WARNING: Maximum TTL value was not specified. Using 1 week\n" + "\t (604800 seconds); re-run with the -m option to get more\n" + "\t accurate results.") + args.maxttl = 604800 + +############################################################################ +# Main +############################################################################ +def main(): + global keyDict + + parse_args() + path=args.path + + print ("PHASE 1--Loading keys to check for internal timing problems") + keyDict = defaultdict(lambda : defaultdict(dict)) + files = glob.glob(os.path.join(path, '*.private')) + for infile in files: + key = Key(infile) + if args.zone and key.zone != args.zone: + continue + keyDict[key.zone][key.alg][key.keyid] = key + key.check_prepub() + if key.sep: + key.check_postpub() + else: + key.check_postpub(args.maxttl + args.resign) + + vspace() + print ("PHASE 2--Scanning future key events for coverage failures") + vreset() + + eventsList = defaultdict(lambda : defaultdict(list)) + fill_eventsList(eventsList) + check_zones(eventsList) + + if foundprob: + exit(1) + else: + exit(0) + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 33daafb0f2..9f9f25b179 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -44,6 +44,7 @@ REVOKE=$TOP/bin/dnssec/dnssec-revoke SETTIME=$TOP/bin/dnssec/dnssec-settime DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey CHECKDS=$TOP/bin/python/dnssec-checkds +COVERAGE=$TOP/bin/python/dnssec-coverage CHECKZONE=$TOP/bin/check/named-checkzone CHECKCONF=$TOP/bin/check/named-checkconf PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -s ${SLOT:-0} -p 1234" @@ -57,8 +58,8 @@ ARPANAME=$TOP/bin/tools/arpaname # load on the machine to make it unusable to other users. # v6synth SUBDIRS="acl additional allow_query addzone autosign builtin - cacheclean checkconf @CHECKDS@ checknames checkzone database - dlv dlvauto dlz dlzexternal dlzredir dname dns64 dnssec + cacheclean checkconf @CHECKDS@ checknames checkzone @COVERAGE@ + database dlv dlvauto dlz dlzexternal dlzredir dname dns64 dnssec dsdigest ecdsa formerr forward glue gost ixfr inline limits logfileconfig lwresd masterfile masterformat metadata notify nsupdate pending pkcs11 redirect resolver rndc rpz diff --git a/bin/tests/system/coverage/01-ksk-inactive/README b/bin/tests/system/coverage/01-ksk-inactive/README new file mode 100644 index 0000000000..81025936f9 --- /dev/null +++ b/bin/tests/system/coverage/01-ksk-inactive/README @@ -0,0 +1,10 @@ +This set includes one KSK rollover. The KSK is deactivated prior to +its replacement being activated. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +ERROR: After 2012-31-Jul (20:59:14): + Inactive: example.com/007/45435 (KSK) +No KSK's are active + +Checking ZSK events for zone example.com, algorithm 7: +OK diff --git a/bin/tests/system/coverage/01-ksk-inactive/expect b/bin/tests/system/coverage/01-ksk-inactive/expect new file mode 100644 index 0000000000..3d342b1a2b --- /dev/null +++ b/bin/tests/system/coverage/01-ksk-inactive/expect @@ -0,0 +1,6 @@ +args="-d 1h -m 2h" +warn=0 +error=1 +ok=1 +retcode=1 +match="No KSK's are active" diff --git a/bin/tests/system/coverage/02-zsk-inactive/README b/bin/tests/system/coverage/02-zsk-inactive/README new file mode 100644 index 0000000000..5d3fed1245 --- /dev/null +++ b/bin/tests/system/coverage/02-zsk-inactive/README @@ -0,0 +1,10 @@ +This set includes one ZSK rollover. The first ZSK is deactivated +prior to its replacement being activated. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +OK + +Checking ZSK events for zone example.com, algorithm 7: +ERROR: After 2012-05-Dec (20:39:32): + Inactive: example.com/005/08376 (ZSK) +No ZSK's are active diff --git a/bin/tests/system/coverage/02-zsk-inactive/expect b/bin/tests/system/coverage/02-zsk-inactive/expect new file mode 100644 index 0000000000..a905b585d4 --- /dev/null +++ b/bin/tests/system/coverage/02-zsk-inactive/expect @@ -0,0 +1,6 @@ +args="-d 1h -m 2h" +warn=0 +error=1 +ok=1 +retcode=1 +match="No ZSK's are active" diff --git a/bin/tests/system/coverage/03-ksk-unpublished/README b/bin/tests/system/coverage/03-ksk-unpublished/README new file mode 100644 index 0000000000..7d8a3015a5 --- /dev/null +++ b/bin/tests/system/coverage/03-ksk-unpublished/README @@ -0,0 +1,10 @@ +This set contains one KSK rollover. The KSK is unpublished before its +successor is published. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +ERROR: After 2012-06-Oct (21:07:57): + Delete: example.com/007/23040 (KSK) +No KSK's are published + +Checking ZSK events for zone example.com, algorithm 7: +OK diff --git a/bin/tests/system/coverage/03-ksk-unpublished/expect b/bin/tests/system/coverage/03-ksk-unpublished/expect new file mode 100644 index 0000000000..58c65bd161 --- /dev/null +++ b/bin/tests/system/coverage/03-ksk-unpublished/expect @@ -0,0 +1,7 @@ +args="-d 1h -m 2h" +warn=1 +error=1 +ok=1 +retcode=1 +match="WARNING: Key .* (KSK) is scheduled for deletion before +No KSK's are published" diff --git a/bin/tests/system/coverage/04-zsk-unpublished/README b/bin/tests/system/coverage/04-zsk-unpublished/README new file mode 100644 index 0000000000..5077abfd45 --- /dev/null +++ b/bin/tests/system/coverage/04-zsk-unpublished/README @@ -0,0 +1,10 @@ +This set contains one ZSK rollover. The ZSK is unpublished before its +successor is published. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +OK + +Checking ZSK events for zone example.com, algorithm 7: +ERROR: After 2012-06-Oct (21:13:45): + Delete: example.com/007/25967 (ZSK) +No ZSK's are published diff --git a/bin/tests/system/coverage/04-zsk-unpublished/expect b/bin/tests/system/coverage/04-zsk-unpublished/expect new file mode 100644 index 0000000000..4ffa0b663e --- /dev/null +++ b/bin/tests/system/coverage/04-zsk-unpublished/expect @@ -0,0 +1,7 @@ +args="-d 1h -m 2h" +warn=1 +error=1 +ok=1 +retcode=1 +match="WARNING: Key .* (ZSK) is scheduled for deletion before +No ZSK's are published" diff --git a/bin/tests/system/coverage/05-ksk-unpub-active/README b/bin/tests/system/coverage/05-ksk-unpub-active/README new file mode 100644 index 0000000000..119c1b2dac --- /dev/null +++ b/bin/tests/system/coverage/05-ksk-unpub-active/README @@ -0,0 +1,12 @@ +This set includes one KSK rollover. The first KSK is deleted +and its successor published prior to the first KSK being deactivated +and its successor activated. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +ERROR: After 2012-05-Dec (21:22:19): + Delete: example.com/007/06219 (KSK) + Publish: example.com/007/20559 (KSK) +No KSK's are both active and published + +Checking ZSK events for zone example.com, algorithm 7: +OK diff --git a/bin/tests/system/coverage/05-ksk-unpub-active/expect b/bin/tests/system/coverage/05-ksk-unpub-active/expect new file mode 100644 index 0000000000..b6cf2d2c27 --- /dev/null +++ b/bin/tests/system/coverage/05-ksk-unpub-active/expect @@ -0,0 +1,7 @@ +args="-d 1h -m 2h" +warn=1 +error=1 +ok=1 +retcode=1 +match="WARNING: Key .* (KSK) is scheduled for deletion before +No KSK's are both active and published" diff --git a/bin/tests/system/coverage/06-zsk-unpub-active/README b/bin/tests/system/coverage/06-zsk-unpub-active/README new file mode 100644 index 0000000000..84833f8431 --- /dev/null +++ b/bin/tests/system/coverage/06-zsk-unpub-active/README @@ -0,0 +1,12 @@ +This set includes one KSK rollover. The first KSK is deleted +and its successor published prior to the first KSK being deactivated +and its successor activated. Tool output should resemble: + +Checking KSK events for zone example.com, algorithm 7: +OK + +Checking ZSK events for zone example.com, algorithm 7: +ERROR: After 2012-05-Dec (20:44:18): + Delete: example.com/007/26369 (ZSK) + Publish: example.com/007/21029 (ZSK) +No ZSK's are both active and published diff --git a/bin/tests/system/coverage/06-zsk-unpub-active/expect b/bin/tests/system/coverage/06-zsk-unpub-active/expect new file mode 100644 index 0000000000..ed2004d268 --- /dev/null +++ b/bin/tests/system/coverage/06-zsk-unpub-active/expect @@ -0,0 +1,7 @@ +args="-d 1h -m 2h" +warn=1 +error=1 +ok=1 +retcode=1 +match="WARNING: Key .* (ZSK) is scheduled for deletion before +No ZSK's are both active and published" diff --git a/bin/tests/system/coverage/07-ksk-ttl/README b/bin/tests/system/coverage/07-ksk-ttl/README new file mode 100644 index 0000000000..265909921b --- /dev/null +++ b/bin/tests/system/coverage/07-ksk-ttl/README @@ -0,0 +1,4 @@ +This set includes a KSK rollover, with insufficient delay between +prepublication and rollover. + +Expected tool output TBD. diff --git a/bin/tests/system/coverage/07-ksk-ttl/expect b/bin/tests/system/coverage/07-ksk-ttl/expect new file mode 100644 index 0000000000..4cc3af21d9 --- /dev/null +++ b/bin/tests/system/coverage/07-ksk-ttl/expect @@ -0,0 +1,7 @@ +args="-d 1w -m 2w" +warn=1 +error=0 +ok=2 +retcode=0 +match="WARNING: Key .* (KSK) is activated too soon after +Activation should be at least 7 days after publication." diff --git a/bin/tests/system/coverage/08-zsk-ttl/README b/bin/tests/system/coverage/08-zsk-ttl/README new file mode 100644 index 0000000000..265909921b --- /dev/null +++ b/bin/tests/system/coverage/08-zsk-ttl/README @@ -0,0 +1,4 @@ +This set includes a KSK rollover, with insufficient delay between +prepublication and rollover. + +Expected tool output TBD. diff --git a/bin/tests/system/coverage/08-zsk-ttl/expect b/bin/tests/system/coverage/08-zsk-ttl/expect new file mode 100644 index 0000000000..0579769ab9 --- /dev/null +++ b/bin/tests/system/coverage/08-zsk-ttl/expect @@ -0,0 +1,7 @@ +args="-d 1w -m 2w" +warn=1 +error=0 +ok=2 +retcode=0 +match="WARNING: Key .* (ZSK) is activated too soon after +Activation should be at least 7 days after publication." diff --git a/bin/tests/system/coverage/clean.sh b/bin/tests/system/coverage/clean.sh new file mode 100644 index 0000000000..ea8ab97e22 --- /dev/null +++ b/bin/tests/system/coverage/clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh +rm -f named-compilezone +rm -f */K*.key +rm -f */K*.private +rm -rf coverage.* +rm -f random.data diff --git a/bin/tests/system/coverage/setup.sh b/bin/tests/system/coverage/setup.sh new file mode 100644 index 0000000000..90ab3b63b9 --- /dev/null +++ b/bin/tests/system/coverage/setup.sh @@ -0,0 +1,106 @@ +#!/bin/sh +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +KEYGEN="$KEYGEN -qr random.data" + +sh clean.sh + +ln -s $CHECKZONE named-compilezone +../../../tools/genrandom 400 random.data + +# Test 1: KSK goes inactive before successor is active +dir=01-ksk-inactive +rm -f $dir/K*.key +rm -f $dir/K*.private +ksk1=`$KEYGEN -K $dir -3fk example.com` +$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 +ksk2=`$KEYGEN -K $dir -S $ksk1` +$SETTIME -K $dir -I +7mo $ksk1 > /dev/null 2>&1 +zsk1=`$KEYGEN -K $dir -3 example.com` + +# Test 2: ZSK goes inactive before successor is active +dir=02-zsk-inactive +rm -f $dir/K*.key +rm -f $dir/K*.private +zsk1=`$KEYGEN -K $dir -3 example.com` +$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 +zsk2=`$KEYGEN -K $dir -S $zsk1` +$SETTIME -K $dir -I +7mo $zsk1 > /dev/null 2>&1 +ksk1=`$KEYGEN -K $dir -3fk example.com` + +# Test 3: KSK is unpublished before its successor is published +dir=03-ksk-unpublished +rm -f $dir/K*.key +rm -f $dir/K*.private +ksk1=`$KEYGEN -K $dir -3fk example.com` +$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 +ksk2=`$KEYGEN -K $dir -S $ksk1` +$SETTIME -K $dir -D +6mo $ksk1 > /dev/null 2>&1 +zsk1=`$KEYGEN -K $dir -3 example.com` + +# Test 4: ZSK is unpublished before its successor is published +dir=04-zsk-unpublished +rm -f $dir/K*.key +rm -f $dir/K*.private +zsk1=`$KEYGEN -K $dir -3 example.com` +$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 +zsk2=`$KEYGEN -K $dir -S $zsk1` +$SETTIME -K $dir -D +6mo $zsk1 > /dev/null 2>&1 +ksk1=`$KEYGEN -K $dir -3fk example.com` + +# Test 5: KSK deleted and successor published before KSK is deactivated +# and successor activated. +dir=05-ksk-unpub-active +rm -f $dir/K*.key +rm -f $dir/K*.private +ksk1=`$KEYGEN -K $dir -3fk example.com` +$SETTIME -K $dir -I +9mo -D +8mo $ksk1 > /dev/null 2>&1 +ksk2=`$KEYGEN -K $dir -S $ksk1` +zsk1=`$KEYGEN -K $dir -3 example.com` + +# Test 6: ZSK deleted and successor published before ZSK is deactivated +# and successor activated. +dir=06-zsk-unpub-active +rm -f $dir/K*.key +rm -f $dir/K*.private +zsk1=`$KEYGEN -K $dir -3 example.com` +$SETTIME -K $dir -I +9mo -D +8mo $zsk1 > /dev/null 2>&1 +zsk2=`$KEYGEN -K $dir -S $zsk1` +ksk1=`$KEYGEN -K $dir -3fk example.com` + +# Test 7: KSK rolled with insufficient delay after prepublication. +dir=07-ksk-ttl +rm -f $dir/K*.key +rm -f $dir/K*.private +ksk1=`$KEYGEN -K $dir -3fk example.com` +$SETTIME -K $dir -I +9mo -D +1y $ksk1 > /dev/null 2>&1 +ksk2=`$KEYGEN -K $dir -S $ksk1` +# allow only 1 day between publication and activation +$SETTIME -K $dir -P +269d $ksk2 > /dev/null 2>&1 +zsk1=`$KEYGEN -K $dir -3 example.com` + +# Test 8: ZSK rolled with insufficient delay after prepublication. +dir=08-zsk-ttl +rm -f $dir/K*.key +rm -f $dir/K*.private +zsk1=`$KEYGEN -K $dir -3 example.com` +$SETTIME -K $dir -I +9mo -D +1y $zsk1 > /dev/null 2>&1 +zsk2=`$KEYGEN -K $dir -S $zsk1` +# allow only 1 day between publication and activation +$SETTIME -K $dir -P +269d $zsk2 > /dev/null 2>&1 +ksk1=`$KEYGEN -K $dir -3fk example.com` diff --git a/bin/tests/system/coverage/tests.sh b/bin/tests/system/coverage/tests.sh new file mode 100644 index 0000000000..d89ff7e66a --- /dev/null +++ b/bin/tests/system/coverage/tests.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +COVERAGE="$COVERAGE -c ./named-compilezone" + +status=0 +n=1 + +matchall () { + file=$1 + echo "$2" | while read matchline; do + grep "$matchline" $file > /dev/null 2>&1 || { + echo "FAIL" + return + } + done +} + +echo "I:checking for DNSSEC key coverage issues" +ret=0 +for dir in [0-9][0-9]-*; do + ret=0 + echo "I:$dir" + args= warn= error= ok= retcode= match= + . $dir/expect + $COVERAGE $args -K $dir example.com > coverage.$n 2>&1 + + # check that return code matches expectations + [ $? -eq $retcode ] || ret=1 + + # check for correct number of errors + found=`grep ERROR coverage.$n | wc -l` + [ $found -eq $error ] || ret=1 + + # check for correct number of warnings + found=`grep WARNING coverage.$n | wc -l` + [ $found -eq $warn ] || ret=1 + + # check for correct number of OKs + found=`grep "No errors found" coverage.$n | wc -l` + [ $found -eq $ok ] || ret=1 + + found=`matchall coverage.$n "$match"` + [ "$found" = "FAIL" ] && ret=1 + + n=`expr $n + 1` + if [ $ret != 0 ]; then echo "I:failed"; fi + status=`expr $status + $ret` +done + +echo "I:exit status: $status" +exit $status diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index dc0ab1645d..558810a5e1 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1236,6 +1236,18 @@ israw1 signer/signer.out.7 || ret=1 if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:checking dnssec-signzone output format ($n)" +ret=0 +( +cd signer +$SIGNER -O full -f - -Sxt -o example example.db > signer.out.3 2>&1 +$SIGNER -O text -f - -Sxt -o example example.db > signer.out.4 2>&1 +) || ret=1 +awk '/IN *SOA/ {if (NF != 11) exit(1)}' signer/signer.out.3 || ret=1 +awk '/IN *SOA/ {if (NF != 7) exit(1)}' signer/signer.out.4 || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + echo "I:checking validated data are not cached longer than originalttl ($n)" ret=0 $DIG $DIGOPTS +ttl +noauth a.ttlpatch.example. @10.53.0.3 a > dig.out.ns3.test$n || ret=1 @@ -2011,5 +2023,14 @@ n=`expr $n + 1` if [ $ret != 0 ]; then echo "I:failed"; fi status=`expr $status + $ret` +echo "I:check dnssec-dsfromkey from stdin($n)" +ret=0 +$DIG $DIGOPTS dnskey algroll. @10.53.0.2 | \ + $DSFROMKEY -f - algroll. > dig.out.ns2.test$n || ret=1 +diff -b dig.out.ns2.test$n ns1/dsset-algroll. > /dev/null 2>&1 || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + echo "I:exit status: $status" exit $status diff --git a/configure b/configure index c2a11dd922..fd1ff12b28 100755 --- a/configure +++ b/configure @@ -1339,6 +1339,7 @@ ISC_PLATFORM_NORETURN_PRE ISC_PLATFORM_HAVELONGLONG ISC_SOCKADDR_LEN_T PYTHON_TOOLS +COVERAGE CHECKDS PYTHON PERL @@ -12206,6 +12207,7 @@ $as_echo "found, using $PYTHON" >&6; } unspec) PYTHON="" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found, python disabled" >&5 $as_echo "not found, python disabled" >&6; } ;; @@ -12221,13 +12223,16 @@ esac PYTHON_TOOLS='' CHECKDS='' +COVERAGE='' if test "X$PYTHON" != "X"; then PYTHON_TOOLS=python CHECKDS=checkds + COVERAGE=coverage fi + # # Special processing of paths depending on whether --prefix, # --sysconfdir or --localstatedir arguments were given. What's @@ -20838,7 +20843,7 @@ ac_config_commands="$ac_config_commands chmod" # elsewhere if there's a good reason for doing so. # -ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/dsdigest/prereq.sh bin/tests/system/ecdsa/prereq.sh bin/tests/system/dlzredir/prereq.sh bin/tests/system/filter-aaaa/Makefile bin/tests/system/geoip/Makefile bin/tests/system/gost/prereq.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/check-secure-delegation.pl contrib/zone-edit.sh doc/Makefile doc/arm/Makefile doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-docbook-latex.xsl doc/xsl/isc-manpage.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/export/Makefile lib/export/dns/Makefile lib/export/dns/include/Makefile lib/export/dns/include/dns/Makefile lib/export/dns/include/dst/Makefile lib/export/irs/Makefile lib/export/irs/include/Makefile lib/export/irs/include/irs/Makefile lib/export/isc/$thread_dir/Makefile lib/export/isc/$thread_dir/include/Makefile lib/export/isc/$thread_dir/include/isc/Makefile lib/export/isc/Makefile lib/export/isc/include/Makefile lib/export/isc/include/isc/Makefile lib/export/isc/nls/Makefile lib/export/isc/unix/Makefile lib/export/isc/unix/include/Makefile lib/export/isc/unix/include/isc/Makefile lib/export/isccfg/Makefile lib/export/isccfg/include/Makefile lib/export/isccfg/include/isccfg/Makefile lib/export/samples/Makefile lib/export/samples/Makefile-postinstall lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile unit/Makefile unit/unittest.sh" +ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/dsdigest/prereq.sh bin/tests/system/ecdsa/prereq.sh bin/tests/system/dlzredir/prereq.sh bin/tests/system/filter-aaaa/Makefile bin/tests/system/geoip/Makefile bin/tests/system/gost/prereq.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/check-secure-delegation.pl contrib/zone-edit.sh doc/Makefile doc/arm/Makefile doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-docbook-latex.xsl doc/xsl/isc-manpage.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/export/Makefile lib/export/dns/Makefile lib/export/dns/include/Makefile lib/export/dns/include/dns/Makefile lib/export/dns/include/dst/Makefile lib/export/irs/Makefile lib/export/irs/include/Makefile lib/export/irs/include/irs/Makefile lib/export/isc/$thread_dir/Makefile lib/export/isc/$thread_dir/include/Makefile lib/export/isc/$thread_dir/include/isc/Makefile lib/export/isc/Makefile lib/export/isc/include/Makefile lib/export/isc/include/isc/Makefile lib/export/isc/nls/Makefile lib/export/isc/unix/Makefile lib/export/isc/unix/include/Makefile lib/export/isc/unix/include/isc/Makefile lib/export/isccfg/Makefile lib/export/isccfg/include/Makefile lib/export/isccfg/include/isccfg/Makefile lib/export/samples/Makefile lib/export/samples/Makefile-postinstall lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile unit/Makefile unit/unittest.sh" # @@ -21846,6 +21851,7 @@ do "bin/pkcs11/Makefile") CONFIG_FILES="$CONFIG_FILES bin/pkcs11/Makefile" ;; "bin/python/Makefile") CONFIG_FILES="$CONFIG_FILES bin/python/Makefile" ;; "bin/python/dnssec-checkds.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-checkds.py" ;; + "bin/python/dnssec-coverage.py") CONFIG_FILES="$CONFIG_FILES bin/python/dnssec-coverage.py" ;; "bin/rndc/Makefile") CONFIG_FILES="$CONFIG_FILES bin/rndc/Makefile" ;; "bin/tests/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/Makefile" ;; "bin/tests/atomic/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/atomic/Makefile" ;; diff --git a/configure.in b/configure.in index f29f8cf06a..f61395e8be 100644 --- a/configure.in +++ b/configure.in @@ -179,6 +179,7 @@ except: exit(1)' unspec) PYTHON="" AC_SUBST(CHECKDS) + AC_SUBST(COVERAGE) AC_MSG_RESULT([not found, python disabled]) ;; yes) @@ -192,11 +193,14 @@ esac PYTHON_TOOLS='' CHECKDS='' +COVERAGE='' if test "X$PYTHON" != "X"; then PYTHON_TOOLS=python CHECKDS=checkds + COVERAGE=coverage fi AC_SUBST(CHECKDS) +AC_SUBST(COVERAGE) AC_SUBST(PYTHON_TOOLS) # @@ -3778,6 +3782,7 @@ AC_CONFIG_FILES([ bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py + bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index d1396487e1..3cd31c056a 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -17504,6 +17504,8 @@ zone "example.com" { Manual pages + + -- GitLab