def test_trusted_keys_file(self): tk1 = 'example.com. IN DNSKEY 256 3 7 AwEAAZ2YEuBl4X58v1CezDfZjT1viYn5kY3MF3lSDjvHjMZ6gJlYt4Qq oIdpChifmeJldEX9/wPc04Tg7MlEfV3m0x2j80dMyObM0FZTxzMgbTFk Zs0AWrDXELieGkFZv1FB9YoxSX2XqvpFxwvPyyszUtCy/c5hrb6vfKRB Jh+qIO+NsNrl6O8NiYjWWNjdiFw+c2BxzpArQoaA+rcoyDYwH4xGpvTw YLnE9HmkwTSQuwASkgWgX3KgTmsDEw4I0P5Tk+wvmNnaqDhmFMHJK5Oh 92wUX+ppxxSgUx4UIJmftzi7sCg0qekIYUf99Dkn7OlC8X0rjj+xO4cD hbTjGkxmsD0=' tk2 = 'example.com. IN DNSKEY 256 3 7 AwEAAaerI6CXvvG6U3UxkB0PXj+ORyGFtABYJ6JG3NL6w1KKlZl+73AS aPEEa7SXeuWmAWE1N3rsbnrMBvepBXkCbP609eoo2mJ8bsozT/NNwSSc FP1Ddw4wxpZAC/+/K736rF1HbI3ROS/rBTr7RW6rWzcyPbYFuUMVzrAM ZSJNJsTDcmyGc5Is3cFzNcrd3/Gmcjt8TKMmGq51HXWzFvxro7EH6aOl K6G4O4+mzaUKp91mg7DAVhX8yXnadXUZQ4yDfLzSleYQ2TroQqeSgI3X m/gUoACm3ELUOr84TmIKZ67X/zBTx8tHC5iBWY2tbIKqiJY7I4/aW4S4 NraCSRbDpbM=' tk1_rdata = ' '.join(tk1.split()[3:]) tk2_rdata = ' '.join(tk2.split()[3:]) tk_explicit = [(dns.name.from_text('example.com'), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY, tk1_rdata)), (dns.name.from_text('example.com'), dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DNSKEY, tk2_rdata))] now = datetime.datetime.now(utc) tk_default = get_default_trusted_keys(now) args = ['example.com'] arghelper = GraphArgHelper(self.logger) arghelper.build_parser('graph') arghelper.parse_args(args) arghelper.aggregate_trusted_key_info() self.assertEqual(arghelper.trusted_keys, None) arghelper.update_trusted_key_info(now) self.assertEqual(arghelper.trusted_keys, tk_default) with tempfile.NamedTemporaryFile('wb', prefix='dnsviz', delete=False) as tk1_file: tk1_file.write(tk1.encode('utf-8')) with tempfile.NamedTemporaryFile('wb', prefix='dnsviz', delete=False) as tk2_file: tk2_file.write(tk2.encode('utf-8')) try: args = ['-t', tk1_file.name, '-t', tk2_file.name, 'example.com'] arghelper = GraphArgHelper(self.logger) arghelper.build_parser('graph') arghelper.parse_args(args) arghelper.aggregate_trusted_key_info() arghelper.update_trusted_key_info(now) self.assertEqual(arghelper.trusted_keys, tk_explicit) args = ['-t', '/dev/null', 'example.com'] arghelper = GraphArgHelper(self.logger) arghelper.build_parser('graph') arghelper.parse_args(args) arghelper.aggregate_trusted_key_info() arghelper.update_trusted_key_info(now) self.assertEqual(arghelper.trusted_keys, []) finally: for tmpfile in (tk1_file, tk2_file): os.remove(tmpfile.name)
def update_trusted_key_info(self, latest_analysis_date): if self.args.trusted_keys_file is None: self.trusted_keys = get_default_trusted_keys(latest_analysis_date)
def main(argv): try: test_pygraphviz() try: opts, args = getopt.getopt(argv[1:], 'f:r:R:et:a:d:CPOo:T:h') except getopt.GetoptError as e: sys.stderr.write('%s\n' % str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: with io.open(arg, 'r', encoding='utf-8') as fh: tk_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error( 'There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) if '-f' in opts and args: sys.stderr.write( 'If -f is used, then domain names may not supplied as command line arguments.\n' ) sys.exit(1) if '-R' in opts: try: rdtypes = opts['-R'].split(',') except ValueError: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) try: rdtypes = [dns.rdatatype.from_text(x) for x in rdtypes] except dns.rdatatype.UnknownRdatatype: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) else: rdtypes = None if '-a' in opts: try: supported_algs = opts['-a'].split(',') except ValueError: sys.stderr.write('The list of algorithms was invalid: "%s"\n' % opts['-a']) sys.exit(1) try: supported_algs = set([int(x) for x in supported_algs]) except ValueError: sys.stderr.write('The list of algorithms was invalid: "%s"\n' % opts['-a']) sys.exit(1) else: supported_algs = None if '-d' in opts: try: supported_digest_algs = opts['-d'].split(',') except ValueError: sys.stderr.write( 'The list of digest algorithms was invalid: "%s"\n' % opts['-d']) sys.exit(1) try: supported_digest_algs = set( [int(x) for x in supported_digest_algs]) except ValueError: sys.stderr.write( 'The list of digest algorithms was invalid: "%s"\n' % opts['-d']) sys.exit(1) else: supported_digest_algs = None strict_cookies = '-C' in opts allow_private = '-P' in opts remove_edges = '-e' not in opts if '-T' in opts: fmt = opts['-T'] elif '-o' in opts: fmt = opts['-o'].split('.')[-1] else: fmt = 'dot' if fmt not in ('dot', 'png', 'jpg', 'svg', 'html'): sys.stderr.write('Image format unrecognized: "%s"\n' % fmt) sys.exit(1) if '-o' in opts and '-O' in opts: sys.stderr.write( 'The -o and -O options may not be used together.\n') sys.exit(1) if '-r' not in opts or opts['-r'] == '-': opt_r = sys.stdin.fileno() else: opt_r = opts['-r'] try: with io.open(opt_r, 'r', encoding='utf-8') as fh: analysis_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts.get('-r', '-'))) sys.exit(3) if not analysis_str: if opt_r != sys.stdin.fileno(): logger.error('No input.') sys.exit(3) try: analysis_structured = json.loads(analysis_str) except ValueError: logger.error('There was an error parsing the JSON input: "%s"' % opts.get('-r', '-')) sys.exit(3) # check version if '_meta._dnsviz.' not in analysis_structured or 'version' not in analysis_structured[ '_meta._dnsviz.']: logger.error('No version information in JSON input.') sys.exit(3) try: major_vers, minor_vers = [ int(x) for x in str(analysis_structured['_meta._dnsviz.'] ['version']).split('.', 1) ] except ValueError: logger.error('Version of JSON input is invalid: %s' % analysis_structured['_meta._dnsviz.']['version']) sys.exit(3) # ensure major version is a match and minor version is no greater # than the current minor version curr_major_vers, curr_minor_vers = [ int(x) for x in str(DNS_RAW_VERSION).split('.', 1) ] if major_vers != curr_major_vers or minor_vers > curr_minor_vers: logger.error( 'Version %d.%d of JSON input is incompatible with this software.' % (major_vers, minor_vers)) sys.exit(3) names = OrderedDict() if '-f' in opts: if opts['-f'] == '-': opts['-f'] = sys.stdin.fileno() try: f = io.open(opts['-f'], 'r', encoding='utf-8') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-f'])) sys.exit(3) for line in f: name = line.strip() try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: if name not in names: names[name] = None f.close() else: if args: # python3/python2 dual compatibility if isinstance(args[0], bytes): args = [ codecs.decode(x, sys.getfilesystemencoding()) for x in args ] else: try: args = analysis_structured['_meta._dnsviz.']['names'] except KeyError: logger.error('No names found in JSON input!') sys.exit(3) for name in args: try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: if name not in names: names[name] = None latest_analysis_date = None name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = OfflineDomainNameAnalysis.deserialize( name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if not name_objs: sys.exit(4) if '-t' not in opts: trusted_keys = get_default_trusted_keys(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status( trusted_keys, supported_algs=supported_algs, supported_digest_algs=supported_digest_algs) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error( 'No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if '-O' in opts: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s( name_obj.name.canonicalize().to_text()).rstrip('.') finish_graph(G, [name_obj], rdtypes, trusted_keys, supported_algs, fmt, '%s.%s' % (name, fmt), remove_edges) G = DNSAuthGraph() if '-O' not in opts: if '-o' not in opts or opts['-o'] == '-': finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, fmt, None, remove_edges) else: finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, fmt, opts['-o'], remove_edges) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
def main(argv): try: test_pygraphviz() try: opts, args = getopt.getopt(argv[1:], 'f:r:R:et:CPOo:T:h') except getopt.GetoptError as e: sys.stderr.write('%s\n' % str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: with io.open(arg, 'r', encoding='utf-8') as fh: tk_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error('There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) if '-f' in opts and args: sys.stderr.write('If -f is used, then domain names may not supplied as command line arguments.\n') sys.exit(1) if '-R' in opts: try: rdtypes = opts['-R'].split(',') except ValueError: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) try: rdtypes = [dns.rdatatype.from_text(x) for x in rdtypes] except dns.rdatatype.UnknownRdatatype: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) else: rdtypes = None strict_cookies = '-C' in opts allow_private = '-P' in opts remove_edges = '-e' not in opts if '-T' in opts: fmt = opts['-T'] elif '-o' in opts: fmt = opts['-o'].split('.')[-1] else: fmt = 'dot' if fmt not in ('dot','png','jpg','svg','html'): sys.stderr.write('Image format unrecognized: "%s"\n' % fmt) sys.exit(1) if '-o' in opts and '-O' in opts: sys.stderr.write('The -o and -O options may not be used together.\n') sys.exit(1) if '-r' not in opts or opts['-r'] == '-': opt_r = sys.stdin.fileno() else: opt_r = opts['-r'] try: with io.open(opt_r, 'r', encoding='utf-8') as fh: analysis_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts.get('-r', '-'))) sys.exit(3) if not analysis_str: if opt_r != sys.stdin.fileno(): logger.error('No input.') sys.exit(3) try: analysis_structured = json.loads(analysis_str) except ValueError: logger.error('There was an error parsing the JSON input: "%s"' % opts.get('-r', '-')) sys.exit(3) # check version if '_meta._dnsviz.' not in analysis_structured or 'version' not in analysis_structured['_meta._dnsviz.']: logger.error('No version information in JSON input.') sys.exit(3) try: major_vers, minor_vers = [int(x) for x in str(analysis_structured['_meta._dnsviz.']['version']).split('.', 1)] except ValueError: logger.error('Version of JSON input is invalid: %s' % analysis_structured['_meta._dnsviz.']['version']) sys.exit(3) # ensure major version is a match and minor version is no greater # than the current minor version curr_major_vers, curr_minor_vers = [int(x) for x in str(DNS_RAW_VERSION).split('.', 1)] if major_vers != curr_major_vers or minor_vers > curr_minor_vers: logger.error('Version %d.%d of JSON input is incompatible with this software.' % (major_vers, minor_vers)) sys.exit(3) names = OrderedDict() if '-f' in opts: if opts['-f'] == '-': opts['-f'] = sys.stdin.fileno() try: f = io.open(opts['-f'], 'r', encoding='utf-8') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-f'])) sys.exit(3) for line in f: name = line.strip() try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: if name not in names: names[name] = None f.close() else: if args: # python3/python2 dual compatibility if isinstance(args[0], bytes): args = [codecs.decode(x, sys.getfilesystemencoding()) for x in args] else: try: args = analysis_structured['_meta._dnsviz.']['names'] except KeyError: logger.error('No names found in JSON input!') sys.exit(3) for name in args: try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: if name not in names: names[name] = None latest_analysis_date = None name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if not name_objs: sys.exit(4) if '-t' not in opts: trusted_keys = get_default_trusted_keys(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error('No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if '-O' in opts: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s(name_obj.name.canonicalize().to_text()).rstrip('.') finish_graph(G, [name_obj], rdtypes, trusted_keys, fmt, '%s.%s' % (name, fmt), remove_edges) G = DNSAuthGraph() if '-O' not in opts: if '-o' not in opts or opts['-o'] == '-': finish_graph(G, name_objs, rdtypes, trusted_keys, fmt, None, remove_edges) else: finish_graph(G, name_objs, rdtypes, trusted_keys, fmt, opts['-o'], remove_edges) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
def print_result(analysis_structured): latest_analysis_date = None name_objs = [] cache = {} names = OrderedDict() strict_cookies = False allow_private = False supported_digest_algs = None supported_algs = None rdtypes = None args = analysis_structured['_meta._dnsviz.']['names'] for name in args: try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: if name not in names: names[name] = None for name in names: name_str = lb2s(name.canonicalize().to_text()) print('* %s' % name_str) if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = TTLAgnosticOfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if latest_analysis_date is None: logger.error('The analysis of "%s" doesn\'t include at least one analysis.' % lb2s(name.to_text())) print(names) return trusted_keys = get_default_trusted_keys(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys, supported_algs=supported_algs, supported_digest_algs=supported_digest_algs) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error('No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, None)
def probe(self): _log.debug('Starting probing for "{}"'.format(self.domain)) a = dnsviz.commands.probe.BulkAnalyst(self.rdclass, self.try_ipv4, self.try_ipv6, self.client_ipv4, self.client_ipv6, self.query_class_mixin, self.ceiling, self.edns_diagnostics, self.stop_at_explicit, self.cache_level, self.rdtypes, self.explicit_only, self.dlv_domain) names = [self.domain_native] name_objs = a.analyze(names) name_objs = [x for x in name_objs if x is not None] if len(name_objs) > 1: raise ValueError('More than one name specified?') name_obj = name_objs[0] serialized_obj = OrderedDict() name_obj.serialize(serialized_obj, False) trusted_keys = get_default_trusted_keys(name_obj.analysis_end) serialized_obj['_meta._dnsviz.'] = {'version': DNS_RAW_VERSION, 'names': [lb2s(n.to_text()) for n in names]} cache = {} analysis_obj = TTLAgnosticOfflineDomainNameAnalysis.deserialize(names[0], serialized_obj, cache) analysis_obj.populate_status(trusted_keys) # These errors are linked to the DS record delegation_status = analysis_obj.delegation_status[43] delegation_warnings = [w.description for w in analysis_obj.delegation_warnings[43]] delegation_errors = [e.description for e in analysis_obj.delegation_errors[43]] zone_status = analysis_obj.zone_status zone_warnings = [w.description for w in analysis_obj.zone_warnings] zone_errors = [e.description for e in analysis_obj.zone_errors] dnssec_status = { 'dnssec': {'status': zone_status, 'warnings': zone_warnings, 'errors': zone_errors}, 'delegation': {'status': delegation_status, 'warnings': delegation_warnings, 'errors': delegation_errors}, } yield nagiosplugin.Metric('dnssec_status', dnssec_status, context='dnssec') rrsig_errors = set() rrsig_warnings = set() rrsig_expiration = None now = int(time.time()) for _, rrsigs in analysis_obj.rrsig_status.iteritems(): for rrsig, rrsets in rrsigs.iteritems(): for keymeta, single_rrsig_status in rrsets.iteritems(): if keymeta.name != self.domain_native: continue for w in single_rrsig_status.warnings: rrsig_warnings.add('{}: {}'.format(keymeta, w.description)) for e in single_rrsig_status.errors: rrsig_errors.add('{}: {}'.format(keymeta, e.description)) expire_seconds = rrsig.expiration - now if rrsig_expiration is None or expire_seconds < rrsig_expiration: rrsig_expiration = expire_seconds yield nagiosplugin.Metric('rrsig_status', {'errors': rrsig_errors, 'warnings': rrsig_warnings}, context='rrsig') yield nagiosplugin.Metric('rrsig_expiration', rrsig_expiration, context='rrsig_expiration')