dnssec-coverage.py.in revision bef771f237c7f0a77ddf8738a842b9025306b50d
#!@PYTHON@
############################################################################
# Copyright (C) 2013, 2014 Internet Systems Consortium, Inc. ("ISC")
#
# 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 glob
import time
prog='dnssec-coverage'
# These routines permit platform-independent location of BIND 9 tools
if os.name == 'nt':
if os.name != 'nt':
bind_subkey = "Software\\ISC\\BIND"
try:
if keyFound:
try:
if keyFound:
return os.path.join(namedBase, bindir)
########################################################################
# 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."""
return self.key.showkey()
return self.key.showkeytype()
########################################################################
# Class Key
########################################################################
"""An individual DNSSEC key. Identified by path, zone, algorithm, keyid.
Contains a dictionary of metadata events."""
self.keyid = int(keyid)
continue
continue
septoken = 3
vspace()
print("WARNING: Unable to determine TTL for DNSKEY %s." %
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
else:
continue
continue
"%Y%m%d%H%M%S")
"%Y%m%d%H%M%S")
"%Y%m%d%H%M%S")
"%Y%m%d%H%M%S")
"%Y%m%d%H%M%S")
return "%s/%03d/%05d" % (self.zone, self.alg, self.keyid);
# ensure that the gap between Publish and Activate is big enough
return False
if a > now:
vspace()
print("WARNING: Key %s (%s) is scheduled for activation but \n"
"\t not for publication." %
return False
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." %
print("\t Activation should be at least %s after publication."
return True
if a < p:
vspace()
print("WARNING: Key %s (%s) is active before it is published" %
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."
print("\t Activation should be at least %s after publication." %
return False
return True
# ensure that the gap between Inactive and Delete is big enough
return False
if d > now:
vspace()
print("WARNING: Key %s (%s) is scheduled for deletion but\n"
"\t not for inactivation." %
return False
return True
if (d < i):
vspace()
print("WARNING: Key %s (%s) is scheduled for deletion before\n"
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."
print("\t Deletion should be at least %s after inactivation." %
return False
return True
########################################################################
# class Zone
########################################################################
"""Stores data about a specific zone"""
if not args.compilezone:
exit(1)
return
fp.close()
############################################################################
# debug_print:
############################################################################
"""pretty print a variable iff debug mode is enabled"""
return
else:
return
############################################################################
# vspace:
############################################################################
"""adds vertical space between two sections of output text if and only
if this is *not* the first section being printed"""
if _firstline:
else:
print()
############################################################################
# vreset:
############################################################################
"""reset vertical spacing"""
############################################################################
# getunit
############################################################################
"""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"""
if bigunit:
############################################################################
# addtime
############################################################################
"""add a formatted unit of time to an accumulating string"""
if t:
return output
############################################################################
# duration:
############################################################################
"""given a length of time in seconds, print a formatted human duration
in larger units of time
"""
# define units:
minute = 60
# calculate time in units:
output = ''
return output
############################################################################
# parse_time
############################################################################
"""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
# try to parse as a number with a suffix indicating unit of time
m = r.match(s)
if not m:
n = int(n)
return n * 31536000
return n * 2592000
return n * 604800
return n * 86400
return n * 3600
return n * 60
return n
else:
############################################################################
# algname:
############################################################################
"""return the mnemonic for a DNSSEC algorithm"""
'ECDSAP384SHA384')
############################################################################
# list_events:
############################################################################
"""print a list of the events in an eventgroup"""
if not eventgroup:
return
print (" %s: %s (%s)" %
############################################################################
# process_events:
############################################################################
"""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"""
if event.what == "Activate":
elif event.what == "Publish":
elif event.what == "Inactive":
vspace()
print ("\tWARNING: %s (%s) scheduled to become inactive "
"before it is active" %
else:
elif event.what == "Delete":
else:
vspace()
print ("WARNING: key %s (%s) is scheduled for deletion before "
"it is published, at %s" %
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
############################################################################
# check_events:
############################################################################
"""create lists of events happening at the same time, check for
inconsistancies"""
active = set()
published = set()
# collect up all events that have the same time
# if checking ZSKs, skip KSKs, and vice versa
continue
# we found an appropriate (ZSK or KSK event)
# add event to current eventgroup
if (not eventgroup or eventgroup[0].when == event.when):
# 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):
if eventgroup:
if (args.checklimit and
print("Ignoring events after %s" %
return True
# and then check for inconsistencies:
return False
return False
print (("ERROR: No %s's are both active and published " +
return False
if not eventsfound:
print ("ERROR: No %s events found in '%s'" %
return False
return True
############################################################################
# check_zones:
# ############################################################################
"""scan events per zone, algorithm, and key type, in order of occurrance,
noting inconsistent states when found"""
continue
vspace()
print("Checking scheduled KSK events for zone %s, algorithm %s..." %
else:
print ("No errors found")
vspace()
print("Checking scheduled ZSK events for zone %s, algorithm %s..." %
else:
print ("No errors found")
if not zonesfound:
print("ERROR: No key events found for %s in '%s'" %
exit(1)
############################################################################
# fill_eventsList:
############################################################################
"""populate the list of events"""
exit(1)
############################################################################
# set_path:
############################################################################
"""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"""
break
return fpath
############################################################################
# parse_args:
############################################################################
"""Read command line arguments, set global 'args' structure"""
'DNSKEY coverage for a zone')
type=int, help='the RRSIG refresh interval '
'in seconds [default: 22.5 days]',
default=compilezone, type=str,
help='Length of time to check for '
'DNSSEC coverage [default: 0 (unlimited)]',
print("ERROR: -z and -k cannot be used together.");
exit(1)
# convert from time arguments to seconds
try:
if args.maxttl:
m = parse_time(args.maxttl)
args.maxttl = m
try:
if args.keyttl:
k = parse_time(args.keyttl)
args.keyttl = k
try:
if args.resign:
r = parse_time(args.resign)
args.resign = r
try:
if args.checklimit:
if r == 0:
else:
# if we've got the values we need from the command line, stop now
return
# load keyttl and maxttl data from zonefile
try:
if not args.maxttl:
if not args.keyttl:
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
############################################################################
print ("PHASE 1--Loading keys to check for internal timing problems")
continue
if key.sep:
else:
vspace()
print ("PHASE 2--Scanning future key events for coverage failures")
vreset()
if foundprob:
exit(1)
else:
exit(0)
if __name__ == "__main__":
main()