From 75dfe6c88aee21b26e86b0aa03dacb07b627c80e Mon Sep 17 00:00:00 2001 From: Andreas Olsson Date: Sat, 28 Sep 2019 19:34:29 +0200 Subject: [PATCH] nsupdate: Use authoritative server for zone lookup (#62329) Using a regular recursive resolver to lookup the zone name might not work when the zone in question belong to a private/internal domain. The authoritative server being used on the other hand will definitely know about the zone(s) it's serving. This approach is also consistent with the nsupdate module already querying the specified authoritative server for TTL values. The reason for the implementation having to loop until finding a direct match is to account for different SOA responses triggered by CNAMEs and DNAMEs. The previously used `dns.resolver.zone_for_name()` function does the same. Resolves #62052 --- .../62329-nsupdate-lookup-internal-zones.yaml | 2 ++ lib/ansible/modules/net_tools/nsupdate.py | 34 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/62329-nsupdate-lookup-internal-zones.yaml diff --git a/changelogs/fragments/62329-nsupdate-lookup-internal-zones.yaml b/changelogs/fragments/62329-nsupdate-lookup-internal-zones.yaml new file mode 100644 index 00000000000..d11aea777b9 --- /dev/null +++ b/changelogs/fragments/62329-nsupdate-lookup-internal-zones.yaml @@ -0,0 +1,2 @@ +bugfixes: + - nsupdate - Fix zone name lookup of internal/private zones (https://github.com/ansible/ansible/issues/62052) diff --git a/lib/ansible/modules/net_tools/nsupdate.py b/lib/ansible/modules/net_tools/nsupdate.py index 7471e559208..93350195225 100644 --- a/lib/ansible/modules/net_tools/nsupdate.py +++ b/lib/ansible/modules/net_tools/nsupdate.py @@ -203,14 +203,7 @@ class RecordManager(object): if module.params['zone'] is None: if module.params['record'][-1] != '.': self.module.fail_json(msg='record must be absolute when omitting zone parameter') - - try: - self.zone = dns.resolver.zone_for_name(self.module.params['record']).to_text() - except (dns.exception.Timeout, dns.resolver.NoNameservers, dns.resolver.NoRootSOA) as e: - self.module.fail_json(msg='Zone resolver error (%s): %s' % (e.__class__.__name__, to_native(e))) - - if self.zone is None: - self.module.fail_json(msg='Unable to find zone, dnspython returned None') + self.zone = self.lookup_zone() else: self.zone = module.params['zone'] @@ -251,6 +244,31 @@ class RecordManager(object): return entry return '"{text}"'.format(text=entry) + def lookup_zone(self): + name = dns.name.from_text(self.module.params['record']) + while True: + query = dns.message.make_query(name, dns.rdatatype.SOA) + try: + if self.module.params['protocol'] == 'tcp': + lookup = dns.query.tcp(query, self.module.params['server'], timeout=10, port=self.module.params['port']) + else: + lookup = dns.query.udp(query, self.module.params['server'], timeout=10, port=self.module.params['port']) + except (socket_error, dns.exception.Timeout) as e: + self.module.fail_json(msg='DNS server error: (%s): %s' % (e.__class__.__name__, to_native(e))) + if lookup.rcode() in [dns.rcode.SERVFAIL, dns.rcode.REFUSED]: + self.module.fail_json(msg='Zone lookup failure: \'%s\' will not respond to queries regarding \'%s\'.' % ( + self.module.params['server'], self.module.params['record'])) + try: + zone = lookup.authority[0].name + if zone == name: + return zone.to_text() + except IndexError: + pass + try: + name = name.parent() + except dns.name.NoParent: + self.module.fail_json(msg='Zone lookup of \'%s\' failed for unknown reason.' % (self.module.params['record'])) + def __do_update(self, update): response = None try: