diff options
author | Casey Deccio <casey@deccio.net> | 2019-03-07 23:57:05 +0300 |
---|---|---|
committer | Casey Deccio <casey@deccio.net> | 2019-03-07 23:57:05 +0300 |
commit | 7dbc582e4ac9fbd8e5c5c1e32fb71ca00df2a32e (patch) | |
tree | d49ddc4c59325ebc4fe17bb0fb31a8e5b0fb6d97 | |
parent | 5cdbd10c3f1ad79d7074ce6a13c843820fb750bc (diff) |
Mark the use of private IP addresses as an error
-rw-r--r-- | dnsviz/analysis/errors.py | 25 | ||||
-rw-r--r-- | dnsviz/analysis/offline.py | 32 | ||||
-rw-r--r-- | dnsviz/commands/graph.py | 6 | ||||
-rw-r--r-- | dnsviz/commands/grok.py | 6 | ||||
-rw-r--r-- | dnsviz/commands/print.py | 6 | ||||
-rw-r--r-- | doc/man/dnsviz-graph.1 | 7 | ||||
-rw-r--r-- | doc/man/dnsviz-grok.1 | 7 | ||||
-rw-r--r-- | doc/man/dnsviz-print.1 | 7 |
8 files changed, 90 insertions, 6 deletions
diff --git a/dnsviz/analysis/errors.py b/dnsviz/analysis/errors.py index 3ba1a26..b4665ef 100644 --- a/dnsviz/analysis/errors.py +++ b/dnsviz/analysis/errors.py @@ -1844,6 +1844,31 @@ class NoAddressForNSName(NSNameError): code = 'NO_ADDRESS_FOR_NS_NAME' description_template = "The following NS name(s) did not resolve to address(es): %(names_text)s" +class PrivateAddressNS(NSNameError): + pass + +class NSNameResolvesToPrivateIP(PrivateAddressNS): + ''' + >>> e = NSNameResolvesToPrivateIP(names=('ns1.foo.baz.',)) + >>> e.description + 'The following NS name(s) resolved to IP address(es) in private IP address space: ns1.foo.baz.' + ''' + + _abstract = False + code = 'NS_NAME_PRIVATE_IP' + description_template = "The following NS name(s) resolved to IP address(es) in private IP address space: %(names_text)s" + +class GlueReferencesPrivateIP(PrivateAddressNS): + ''' + >>> e = GlueReferencesPrivateIP(names=('ns1.foo.baz.',)) + >>> e.description + 'Glue for the following NS name(s) referenced IP address(es) in private IP address space: ns1.foo.baz.' + ''' + + _abstract = False + code = 'GLUE_PRIVATE_IP' + description_template = "Glue for the following NS name(s) referenced IP address(es) in private IP address space: %(names_text)s" + class GlueMismatchError(DelegationError): ''' >>> e = GlueMismatchError(name='ns1.foo.baz.', glue_addresses=('192.0.2.1',), auth_addresses=('192.0.2.2',)) diff --git a/dnsviz/analysis/offline.py b/dnsviz/analysis/offline.py index 3ec0bcc..3b64149 100644 --- a/dnsviz/analysis/offline.py +++ b/dnsviz/analysis/offline.py @@ -40,6 +40,7 @@ import dns.flags, dns.rcode, dns.rdataclass, dns.rdatatype from dnsviz import crypto import dnsviz.format as fmt +from dnsviz.ipaddr import * import dnsviz.query as Q from dnsviz import response as Response from dnsviz.util import tuple_to_dict @@ -96,6 +97,7 @@ class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): def __init__(self, *args, **kwargs): self._strict_cookies = kwargs.pop('strict_cookies', False) + self._allow_private = kwargs.pop('allow_private', False) super(OfflineDomainNameAnalysis, self).__init__(*args, **kwargs) @@ -1686,6 +1688,11 @@ class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): names_missing_glue = [] names_missing_auth = [] + names_auth_private = set() + names_auth_zero = set() + names_glue_private = set() + names_glue_zero = set() + for name in all_names: # if name resolution resulted in an error (other than NXDOMAIN) if name not in auth_mapping: @@ -1698,6 +1705,13 @@ class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): if not auth_mapping[name]: names_missing_auth.append(name) + for addr in auth_addrs: + if LOOPBACK_IPV4_RE.match(addr) or addr == LOOPBACK_IPV6 or \ + RFC_1918_RE.match(addr) or LINK_LOCAL_RE.match(addr) or UNIQ_LOCAL_RE.match(addr): + names_auth_private.add(name) + if ZERO_SLASH8_RE.search(addr): + names_auth_zero.add(name) + if names_from_parent: name_in_parent = name in names_from_parent elif self.delegation_status == Status.DELEGATION_STATUS_INCOMPLETE: @@ -1710,6 +1724,13 @@ class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): if name.is_subdomain(self.name) and not glue_mapping[name]: names_missing_glue.append(name) + for addr in glue_mapping[name]: + if LOOPBACK_IPV4_RE.match(addr) or addr == LOOPBACK_IPV6 or \ + RFC_1918_RE.match(addr) or LINK_LOCAL_RE.match(addr) or UNIQ_LOCAL_RE.match(addr): + names_glue_private.add(name) + if ZERO_SLASH8_RE.search(addr): + names_glue_zero.add(name) + # if there are both glue and authoritative addresses supplied, check that it matches the authoritative response if glue_mapping[name] and auth_addrs: # there are authoritative address records either of type A @@ -1764,6 +1785,17 @@ class OfflineDomainNameAnalysis(OnlineDomainNameAnalysis): names_error_resolving.sort() self.zone_errors.append(Errors.ErrorResolvingNSName(names=[fmt.humanize_name(x) for x in names_error_resolving])) + if not self._allow_private: + if names_auth_private: + names_auth_private = list(names_auth_private) + names_auth_private.sort() + self.zone_errors.append(Errors.NSNameResolvesToPrivateIP(names=[fmt.humanize_name(x) for x in names_auth_private])) + + if names_glue_private: + names_glue_private = list(names_glue_private) + names_glue_private.sort() + self.delegation_errors[dns.rdatatype.DS].append(Errors.GlueReferencesPrivateIP(names=[fmt.humanize_name(x) for x in names_glue_private])) + if names_with_no_glue_ipv4: names_with_no_glue_ipv4.sort() for name in names_with_no_glue_ipv4: diff --git a/dnsviz/commands/graph.py b/dnsviz/commands/graph.py index e64c239..f6b53d4 100644 --- a/dnsviz/commands/graph.py +++ b/dnsviz/commands/graph.py @@ -78,6 +78,7 @@ Options: -r <filename> - Read diagnostic queries from a file. -t <filename> - Use trusted keys from the designated file. -C - Enforce DNS cookies strictly. + -P - Allow private IP addresses for authoritative DNS servers. -R <type>[,<type>...] - Process queries of only the specified type(s). -e - Do not remove redundant RRSIG edges from the graph. @@ -156,7 +157,7 @@ def main(argv): test_pygraphviz() try: - opts, args = getopt.getopt(argv[1:], 'f:r:R:et:COo:T:h') + 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) @@ -201,6 +202,7 @@ def main(argv): rdtypes = None strict_cookies = '-C' in opts + allow_private = '-P' in opts remove_edges = '-e' not in opts @@ -305,7 +307,7 @@ def main(argv): 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) + 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: diff --git a/dnsviz/commands/grok.py b/dnsviz/commands/grok.py index 752b1ef..96f7875 100644 --- a/dnsviz/commands/grok.py +++ b/dnsviz/commands/grok.py @@ -108,6 +108,7 @@ Options: -r <filename> - Read diagnostic queries from a file. -t <filename> - Use trusted keys from the designated file. -C - Enforce DNS cookies strictly. + -P - Allow private IP addresses for authoritative DNS servers. -o <filename> - Save the output to the specified file. -c - Format JSON output minimally, instead of "pretty". -l <loglevel> - Log at the specified level: error, warning, info, debug. @@ -170,7 +171,7 @@ def test_pygraphviz(): def main(argv): try: try: - opts, args = getopt.getopt(argv[1:], 'f:r:t:Co:cl:h') + opts, args = getopt.getopt(argv[1:], 'f:r:t:CPo:cl:h') except getopt.GetoptError as e: sys.stderr.write('%s\n' % str(e)) sys.exit(1) @@ -216,6 +217,7 @@ def main(argv): loglevel = logging.DEBUG strict_cookies = '-C' in opts + allow_private = '-P' in opts if '-r' not in opts or opts['-r'] == '-': opt_r = sys.stdin.fileno() @@ -320,7 +322,7 @@ def main(argv): 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) + name_obj = OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if not name_objs: diff --git a/dnsviz/commands/print.py b/dnsviz/commands/print.py index 0b8d3b9..aac7bc9 100644 --- a/dnsviz/commands/print.py +++ b/dnsviz/commands/print.py @@ -74,6 +74,7 @@ Options: -r <filename> - Read diagnostic queries from a file. -t <filename> - Use trusted keys from the designated file. -C - Enforce DNS cookies strictly. + -P - Allow private IP addresses for authoritative DNS servers. -R <type>[,<type>...] - Process queries of only the specified type(s). -O - Derive the filename(s) from domain name(s). @@ -312,7 +313,7 @@ def main(argv): test_pygraphviz() try: - opts, args = getopt.getopt(argv[1:], 'f:r:R:t:COo:h') + opts, args = getopt.getopt(argv[1:], 'f:r:R:t:CPOo:h') except getopt.GetoptError as e: sys.stderr.write('%s\n' % str(e)) sys.exit(1) @@ -357,6 +358,7 @@ def main(argv): rdtypes = None strict_cookies = '-C' in opts + allow_private = '-P' in opts if '-o' in opts and '-O' in opts: sys.stderr.write('The -o and -O options may not be used together.\n') @@ -449,7 +451,7 @@ def main(argv): 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) + 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: diff --git a/doc/man/dnsviz-graph.1 b/doc/man/dnsviz-graph.1 index c6b6f5a..480a9c9 100644 --- a/doc/man/dnsviz-graph.1 +++ b/doc/man/dnsviz-graph.1 @@ -83,6 +83,13 @@ Enforce DNS cookies strictly. Require a server to return a "BADCOOKIE" response when a query contains a COOKIE option with no server cookie or with an invalid server cookie. .TP +.B -P +Allow private IP addresses for authoritative DNS servers. By default, if the +IP address corresponding to an authoritative server is in IP address space +designated as "private", it is flagged as an error. However, there are some +cases where this is allowed. For example, if the diagnostic queries are issued +to servers in an experimental environment, this might be permissible. +.TP .B -R \fItype\fR[,\fItype...\fI] Process queries of only the specified type(s) (e.g., A, AAAA). The default is to process all types queried as part of the diagnostic input. diff --git a/doc/man/dnsviz-grok.1 b/doc/man/dnsviz-grok.1 index 3f0b352..acd8ae5 100644 --- a/doc/man/dnsviz-grok.1 +++ b/doc/man/dnsviz-grok.1 @@ -81,6 +81,13 @@ Enforce DNS cookies strictly. Require a server to return a "BADCOOKIE" response when a query contains a COOKIE option with no server cookie or with an invalid server cookie. .TP +.B -P +Allow private IP addresses for authoritative DNS servers. By default, if the +IP address corresponding to an authoritative server is in IP address space +designated as "private", it is flagged as an error. However, there are some +cases where this is allowed. For example, if the diagnostic queries are issued +to servers in an experimental environment, this might be permissible. +.TP \fB-o\fR \fIfilename\fR Write the output to the specified file instead of to standard output, which is the default. diff --git a/doc/man/dnsviz-print.1 b/doc/man/dnsviz-print.1 index cd5fec8..b29672c 100644 --- a/doc/man/dnsviz-print.1 +++ b/doc/man/dnsviz-print.1 @@ -83,6 +83,13 @@ Enforce DNS cookies strictly. Require a server to return a "BADCOOKIE" response when a query contains a COOKIE option with no server cookie or with an invalid server cookie. .TP +.B -P +Allow private IP addresses for authoritative DNS servers. By default, if the +IP address corresponding to an authoritative server is in IP address space +designated as "private", it is flagged as an error. However, there are some +cases where this is allowed. For example, if the diagnostic queries are issued +to servers in an experimental environment, this might be permissible. +.TP .B -R \fItype\fR[,\fItype...\fR] Process queries of only the specified type(s) (e.g., A, AAAA). The default is to process all types queried as part of the diagnostic input. |