mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-10 11:35:24 +00:00
4395 [bug] Improve out-of-tree installation of python modules.
[RT #42586]
This commit is contained in:
291
bin/python/isc/coverage.py.in
Normal file
291
bin/python/isc/coverage.py.in
Normal file
@@ -0,0 +1,291 @@
|
||||
############################################################################
|
||||
# Copyright (C) 2013-2016 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.
|
||||
############################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import glob
|
||||
import re
|
||||
import time
|
||||
import calendar
|
||||
import pprint
|
||||
from collections import defaultdict
|
||||
|
||||
prog = 'dnssec-coverage'
|
||||
|
||||
from isc import dnskey, eventlist, keydict, keyevent, keyzone, utils
|
||||
|
||||
############################################################################
|
||||
# print a fatal error and exit
|
||||
############################################################################
|
||||
def fatal(*args, **kwargs):
|
||||
print(*args, **kwargs)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
############################################################################
|
||||
# output:
|
||||
############################################################################
|
||||
_firstline = True
|
||||
def output(*args, **kwargs):
|
||||
"""output text, adding a vertical space this is *not* the first
|
||||
first section being printed since a call to vreset()"""
|
||||
global _firstline
|
||||
if 'skip' in kwargs:
|
||||
skip = kwargs['skip']
|
||||
kwargs.pop('skip', None)
|
||||
else:
|
||||
skip = True
|
||||
if _firstline:
|
||||
_firstline = False
|
||||
elif skip:
|
||||
print('')
|
||||
if args:
|
||||
print(*args, **kwargs)
|
||||
|
||||
|
||||
def vreset():
|
||||
"""reset vertical spacing"""
|
||||
global _firstline
|
||||
_firstline = True
|
||||
|
||||
|
||||
############################################################################
|
||||
# parse_time
|
||||
############################################################################
|
||||
def parse_time(s):
|
||||
""" convert a formatted time (e.g., 1y, 6mo, 15mi, etc) into seconds
|
||||
:param s: String with some text representing a time interval
|
||||
:return: Integer with the number of seconds in the time interval
|
||||
"""
|
||||
s = s.strip()
|
||||
|
||||
# if s is an integer, we're done already
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
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 ValueError("Cannot parse %s" % s)
|
||||
n, unit = m.groups()
|
||||
n = int(n)
|
||||
unit = unit.lower()
|
||||
if unit.startswith('y'):
|
||||
return n * 31536000
|
||||
elif unit.startswith('mo'):
|
||||
return n * 2592000
|
||||
elif unit.startswith('w'):
|
||||
return n * 604800
|
||||
elif unit.startswith('d'):
|
||||
return n * 86400
|
||||
elif unit.startswith('h'):
|
||||
return n * 3600
|
||||
elif unit.startswith('mi'):
|
||||
return n * 60
|
||||
elif unit.startswith('s'):
|
||||
return n
|
||||
else:
|
||||
raise ValueError("Invalid suffix %s" % unit)
|
||||
|
||||
|
||||
############################################################################
|
||||
# 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.
|
||||
:param command: string with a command to look for in the path
|
||||
:param default: default location to use
|
||||
:return: detected location for the desired command
|
||||
"""
|
||||
|
||||
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 = os.path.join(directory, command)
|
||||
if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
|
||||
break
|
||||
fpath = None
|
||||
|
||||
return fpath
|
||||
|
||||
|
||||
############################################################################
|
||||
# parse_args:
|
||||
############################################################################
|
||||
def parse_args():
|
||||
"""Read command line arguments, set global 'args' structure"""
|
||||
compilezone = set_path('named-compilezone',
|
||||
os.path.join(utils.prefix('sbin'),
|
||||
'named-compilezone'))
|
||||
|
||||
parser = argparse.ArgumentParser(description=prog + ': checks future ' +
|
||||
'DNSKEY coverage for a zone')
|
||||
|
||||
parser.add_argument('zone', type=str, nargs='*', default=None,
|
||||
help='zone(s) to check' +
|
||||
'(default: all zones in the directory)')
|
||||
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='time')
|
||||
parser.add_argument('-d', dest='keyttl', type=str,
|
||||
help='the DNSKEY TTL', metavar='time')
|
||||
parser.add_argument('-r', dest='resign', default='1944000',
|
||||
type=str, help='the RRSIG refresh interval '
|
||||
'in seconds [default: 22.5 days]',
|
||||
metavar='time')
|
||||
parser.add_argument('-c', dest='compilezone',
|
||||
default=compilezone, type=str,
|
||||
help='path to \'named-compilezone\'',
|
||||
metavar='path')
|
||||
parser.add_argument('-l', dest='checklimit',
|
||||
type=str, default='0',
|
||||
help='Length of time to check for '
|
||||
'DNSSEC coverage [default: 0 (unlimited)]',
|
||||
metavar='time')
|
||||
parser.add_argument('-z', dest='no_ksk',
|
||||
action='store_true', default=False,
|
||||
help='Only check zone-signing keys (ZSKs)')
|
||||
parser.add_argument('-k', dest='no_zsk',
|
||||
action='store_true', default=False,
|
||||
help='Only check key-signing keys (KSKs)')
|
||||
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=utils.version)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.no_zsk and args.no_ksk:
|
||||
fatal("ERROR: -z and -k cannot be used together.")
|
||||
elif args.no_zsk or args.no_ksk:
|
||||
args.keytype = "KSK" if args.no_zsk else "ZSK"
|
||||
else:
|
||||
args.keytype = None
|
||||
|
||||
if args.filename and len(args.zone) > 1:
|
||||
fatal("ERROR: -f can only be used with one zone.")
|
||||
|
||||
# convert from time arguments to seconds
|
||||
try:
|
||||
if args.maxttl:
|
||||
m = parse_time(args.maxttl)
|
||||
args.maxttl = m
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if args.keyttl:
|
||||
k = parse_time(args.keyttl)
|
||||
args.keyttl = k
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if args.resign:
|
||||
r = parse_time(args.resign)
|
||||
args.resign = r
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if args.checklimit:
|
||||
lim = args.checklimit
|
||||
r = parse_time(args.checklimit)
|
||||
if r == 0:
|
||||
args.checklimit = None
|
||||
else:
|
||||
args.checklimit = time.time() + r
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# if we've got the values we need from the command line, stop now
|
||||
if args.maxttl and args.keyttl:
|
||||
return args
|
||||
|
||||
# load keyttl and maxttl data from zonefile
|
||||
if args.zone and args.filename:
|
||||
try:
|
||||
zone = keyzone(args.zone[0], args.filename, args.compilezone)
|
||||
args.maxttl = args.maxttl or zone.maxttl
|
||||
args.keyttl = args.maxttl or zone.keyttl
|
||||
except Exception as e:
|
||||
print("Unable to load zone data from %s: " % args.filename, e)
|
||||
|
||||
if not args.maxttl:
|
||||
output("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
|
||||
|
||||
return args
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
############################################################################
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
print("PHASE 1--Loading keys to check for internal timing problems")
|
||||
|
||||
try:
|
||||
kd = keydict(path=args.path, zone=args.zone, keyttl=args.keyttl)
|
||||
except Exception as e:
|
||||
fatal('ERROR: Unable to build key dictionary: ' + str(e))
|
||||
|
||||
for key in kd:
|
||||
key.check_prepub(output)
|
||||
if key.sep:
|
||||
key.check_postpub(output)
|
||||
else:
|
||||
key.check_postpub(output, args.maxttl + args.resign)
|
||||
|
||||
output("PHASE 2--Scanning future key events for coverage failures")
|
||||
vreset()
|
||||
|
||||
try:
|
||||
elist = eventlist(kd)
|
||||
except Exception as e:
|
||||
fatal('ERROR: Unable to build event list: ' + str(e))
|
||||
|
||||
errors = False
|
||||
if not args.zone:
|
||||
if not elist.coverage(None, args.keytype, args.checklimit, output):
|
||||
errors = True
|
||||
else:
|
||||
for zone in args.zone:
|
||||
try:
|
||||
if not elist.coverage(zone, args.keytype,
|
||||
args.checklimit, output):
|
||||
errors = True
|
||||
except:
|
||||
output('ERROR: Coverage check failed for zone ' + zone)
|
||||
|
||||
sys.exit(1 if errors else 0)
|
Reference in New Issue
Block a user