summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/lookup/dig.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/lookup/dig.py')
-rw-r--r--ansible_collections/community/general/plugins/lookup/dig.py451
1 files changed, 451 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/lookup/dig.py b/ansible_collections/community/general/plugins/lookup/dig.py
new file mode 100644
index 000000000..fa915220b
--- /dev/null
+++ b/ansible_collections/community/general/plugins/lookup/dig.py
@@ -0,0 +1,451 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Jan-Piet Mens <jpmens(at)gmail.com>
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+ name: dig
+ author: Jan-Piet Mens (@jpmens) <jpmens(at)gmail.com>
+ short_description: query DNS using the dnspython library
+ requirements:
+ - dnspython (python library, http://www.dnspython.org/)
+ description:
+ - The dig lookup runs queries against DNS servers to retrieve DNS records for a specific name (FQDN - fully qualified domain name).
+ It is possible to lookup any DNS record in this manner.
+ - There is a couple of different syntaxes that can be used to specify what record should be retrieved, and for which name.
+ It is also possible to explicitly specify the DNS server(s) to use for lookups.
+ - In its simplest form, the dig lookup plugin can be used to retrieve an IPv4 address (DNS A record) associated with FQDN
+ - In addition to (default) A record, it is also possible to specify a different record type that should be queried.
+ This can be done by either passing-in additional parameter of format qtype=TYPE to the dig lookup, or by appending /TYPE to the FQDN being queried.
+ - If multiple values are associated with the requested record, the results will be returned as a comma-separated list.
+ In such cases you may want to pass option I(wantlist=true) to the lookup call, or alternatively use C(query) instead of C(lookup),
+ which will result in the record values being returned as a list over which you can iterate later on.
+ - By default, the lookup will rely on system-wide configured DNS servers for performing the query.
+ It is also possible to explicitly specify DNS servers to query using the @DNS_SERVER_1,DNS_SERVER_2,...,DNS_SERVER_N notation.
+ This needs to be passed-in as an additional parameter to the lookup
+ options:
+ _terms:
+ description: Domain(s) to query.
+ type: list
+ elements: str
+ qtype:
+ description:
+ - Record type to query.
+ - C(DLV) has been removed in community.general 6.0.0.
+ - C(CAA) has been added in community.general 6.3.0.
+ type: str
+ default: 'A'
+ choices: [A, ALL, AAAA, CAA, CNAME, DNAME, DNSKEY, DS, HINFO, LOC, MX, NAPTR, NS, NSEC3PARAM, PTR, RP, RRSIG, SOA, SPF, SRV, SSHFP, TLSA, TXT]
+ flat:
+ description: If 0 each record is returned as a dictionary, otherwise a string.
+ type: int
+ default: 1
+ retry_servfail:
+ description: Retry a nameserver if it returns SERVFAIL.
+ default: false
+ type: bool
+ version_added: 3.6.0
+ fail_on_error:
+ description:
+ - Abort execution on lookup errors.
+ - The default for this option will likely change to C(true) in the future.
+ The current default, C(false), is used for backwards compatibility, and will result in empty strings
+ or the string C(NXDOMAIN) in the result in case of errors.
+ default: false
+ type: bool
+ version_added: 5.4.0
+ real_empty:
+ description:
+ - Return empty result without empty strings, and return empty list instead of C(NXDOMAIN).
+ - The default for this option will likely change to C(true) in the future.
+ - This option will be forced to C(true) if multiple domains to be queried are specified.
+ default: false
+ type: bool
+ version_added: 6.0.0
+ class:
+ description:
+ - "Class."
+ type: str
+ default: 'IN'
+ notes:
+ - ALL is not a record per-se, merely the listed fields are available for any record results you retrieve in the form of a dictionary.
+ - While the 'dig' lookup plugin supports anything which dnspython supports out of the box, only a subset can be converted into a dictionary.
+ - If you need to obtain the AAAA record (IPv6 address), you must specify the record type explicitly.
+ Syntax for specifying the record type is shown in the examples below.
+ - The trailing dot in most of the examples listed is purely optional, but is specified for completeness/correctness sake.
+'''
+
+EXAMPLES = """
+- name: Simple A record (IPV4 address) lookup for example.com
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.general.dig', 'example.com.')}}"
+
+- name: "The TXT record for example.org."
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.general.dig', 'example.org.', qtype='TXT') }}"
+
+- name: "The TXT record for example.org, alternative syntax."
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.general.dig', 'example.org./TXT') }}"
+
+- name: use in a loop
+ ansible.builtin.debug:
+ msg: "MX record for gmail.com {{ item }}"
+ with_items: "{{ lookup('community.general.dig', 'gmail.com./MX', wantlist=true) }}"
+
+- name: Lookup multiple names at once
+ ansible.builtin.debug:
+ msg: "A record found {{ item }}"
+ loop: "{{ query('community.general.dig', 'example.org.', 'example.com.', 'gmail.com.') }}"
+
+- name: Lookup multiple names at once (from list variable)
+ ansible.builtin.debug:
+ msg: "A record found {{ item }}"
+ loop: "{{ query('community.general.dig', *hosts) }}"
+ vars:
+ hosts:
+ - example.org.
+ - example.com.
+ - gmail.com.
+
+- ansible.builtin.debug:
+ msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '192.0.2.5/PTR') }}"
+- ansible.builtin.debug:
+ msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa./PTR') }}"
+- ansible.builtin.debug:
+ msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa.', qtype='PTR') }}"
+- ansible.builtin.debug:
+ msg: "Querying 198.51.100.23 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@198.51.100.23') }}"
+
+- ansible.builtin.debug:
+ msg: "XMPP service for gmail.com. is available at {{ item.target }} on port {{ item.port }}"
+ with_items: "{{ lookup('community.general.dig', '_xmpp-server._tcp.gmail.com./SRV', flat=0, wantlist=true) }}"
+
+- name: Retry nameservers that return SERVFAIL
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.general.dig', 'example.org./A', retry_servfail=true) }}"
+"""
+
+RETURN = """
+ _list:
+ description:
+ - List of composed strings or dictionaries with key and value
+ If a dictionary, fields shows the keys returned depending on query type
+ type: list
+ elements: raw
+ contains:
+ ALL:
+ description:
+ - owner, ttl, type
+ A:
+ description:
+ - address
+ AAAA:
+ description:
+ - address
+ CAA:
+ description:
+ - flags
+ - tag
+ - value
+ version_added: 6.3.0
+ CNAME:
+ description:
+ - target
+ DNAME:
+ description:
+ - target
+ DNSKEY:
+ description:
+ - flags, algorithm, protocol, key
+ DS:
+ description:
+ - algorithm, digest_type, key_tag, digest
+ HINFO:
+ description:
+ - cpu, os
+ LOC:
+ description:
+ - latitude, longitude, altitude, size, horizontal_precision, vertical_precision
+ MX:
+ description:
+ - preference, exchange
+ NAPTR:
+ description:
+ - order, preference, flags, service, regexp, replacement
+ NS:
+ description:
+ - target
+ NSEC3PARAM:
+ description:
+ - algorithm, flags, iterations, salt
+ PTR:
+ description:
+ - target
+ RP:
+ description:
+ - mbox, txt
+ SOA:
+ description:
+ - mname, rname, serial, refresh, retry, expire, minimum
+ SPF:
+ description:
+ - strings
+ SRV:
+ description:
+ - priority, weight, port, target
+ SSHFP:
+ description:
+ - algorithm, fp_type, fingerprint
+ TLSA:
+ description:
+ - usage, selector, mtype, cert
+ TXT:
+ description:
+ - strings
+"""
+
+from ansible.errors import AnsibleError
+from ansible.plugins.lookup import LookupBase
+from ansible.module_utils.common.text.converters import to_native
+from ansible.module_utils.parsing.convert_bool import boolean
+from ansible.utils.display import Display
+import socket
+
+try:
+ import dns.exception
+ import dns.name
+ import dns.resolver
+ import dns.reversename
+ import dns.rdataclass
+ from dns.rdatatype import (A, AAAA, CAA, CNAME, DNAME, DNSKEY, DS, HINFO, LOC,
+ MX, NAPTR, NS, NSEC3PARAM, PTR, RP, SOA, SPF, SRV, SSHFP, TLSA, TXT)
+ HAVE_DNS = True
+except ImportError:
+ HAVE_DNS = False
+
+
+display = Display()
+
+
+def make_rdata_dict(rdata):
+ ''' While the 'dig' lookup plugin supports anything which dnspython supports
+ out of the box, the following supported_types list describes which
+ DNS query types we can convert to a dict.
+
+ Note: adding support for RRSIG is hard work. :)
+ '''
+ supported_types = {
+ A: ['address'],
+ AAAA: ['address'],
+ CAA: ['flags', 'tag', 'value'],
+ CNAME: ['target'],
+ DNAME: ['target'],
+ DNSKEY: ['flags', 'algorithm', 'protocol', 'key'],
+ DS: ['algorithm', 'digest_type', 'key_tag', 'digest'],
+ HINFO: ['cpu', 'os'],
+ LOC: ['latitude', 'longitude', 'altitude', 'size', 'horizontal_precision', 'vertical_precision'],
+ MX: ['preference', 'exchange'],
+ NAPTR: ['order', 'preference', 'flags', 'service', 'regexp', 'replacement'],
+ NS: ['target'],
+ NSEC3PARAM: ['algorithm', 'flags', 'iterations', 'salt'],
+ PTR: ['target'],
+ RP: ['mbox', 'txt'],
+ # RRSIG: ['type_covered', 'algorithm', 'labels', 'original_ttl', 'expiration', 'inception', 'key_tag', 'signer', 'signature'],
+ SOA: ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire', 'minimum'],
+ SPF: ['strings'],
+ SRV: ['priority', 'weight', 'port', 'target'],
+ SSHFP: ['algorithm', 'fp_type', 'fingerprint'],
+ TLSA: ['usage', 'selector', 'mtype', 'cert'],
+ TXT: ['strings'],
+ }
+
+ rd = {}
+
+ if rdata.rdtype in supported_types:
+ fields = supported_types[rdata.rdtype]
+ for f in fields:
+ val = rdata.__getattribute__(f)
+
+ if isinstance(val, dns.name.Name):
+ val = dns.name.Name.to_text(val)
+
+ if rdata.rdtype == DS and f == 'digest':
+ val = dns.rdata._hexify(rdata.digest).replace(' ', '')
+ if rdata.rdtype == DNSKEY and f == 'algorithm':
+ val = int(val)
+ if rdata.rdtype == DNSKEY and f == 'key':
+ val = dns.rdata._base64ify(rdata.key).replace(' ', '')
+ if rdata.rdtype == NSEC3PARAM and f == 'salt':
+ val = dns.rdata._hexify(rdata.salt).replace(' ', '')
+ if rdata.rdtype == SSHFP and f == 'fingerprint':
+ val = dns.rdata._hexify(rdata.fingerprint).replace(' ', '')
+ if rdata.rdtype == TLSA and f == 'cert':
+ val = dns.rdata._hexify(rdata.cert).replace(' ', '')
+
+ rd[f] = val
+
+ return rd
+
+
+# ==============================================================
+# dig: Lookup DNS records
+#
+# --------------------------------------------------------------
+
+class LookupModule(LookupBase):
+
+ def run(self, terms, variables=None, **kwargs):
+
+ '''
+ terms contains a string with things to `dig' for. We support the
+ following formats:
+ example.com # A record
+ example.com qtype=A # same
+ example.com/TXT # specific qtype
+ example.com qtype=txt # same
+ 192.0.2.23/PTR # reverse PTR
+ ^^ shortcut for 23.2.0.192.in-addr.arpa/PTR
+ example.net/AAAA @nameserver # query specified server
+ ^^^ can be comma-sep list of names/addresses
+
+ ... flat=0 # returns a dict; default is 1 == string
+ '''
+ if HAVE_DNS is False:
+ raise AnsibleError("The dig lookup requires the python 'dnspython' library and it is not installed")
+
+ self.set_options(var_options=variables, direct=kwargs)
+
+ # Create Resolver object so that we can set NS if necessary
+ myres = dns.resolver.Resolver(configure=True)
+ edns_size = 4096
+ myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
+
+ domains = []
+ qtype = self.get_option('qtype')
+ flat = self.get_option('flat')
+ fail_on_error = self.get_option('fail_on_error')
+ real_empty = self.get_option('real_empty')
+ try:
+ rdclass = dns.rdataclass.from_text(self.get_option('class'))
+ except Exception as e:
+ raise AnsibleError("dns lookup illegal CLASS: %s" % to_native(e))
+ myres.retry_servfail = self.get_option('retry_servfail')
+
+ for t in terms:
+ if t.startswith('@'): # e.g. "@10.0.1.2,192.0.2.1" is ok.
+ nsset = t[1:].split(',')
+ for ns in nsset:
+ nameservers = []
+ # Check if we have a valid IP address. If so, use that, otherwise
+ # try to resolve name to address using system's resolver. If that
+ # fails we bail out.
+ try:
+ socket.inet_aton(ns)
+ nameservers.append(ns)
+ except Exception:
+ try:
+ nsaddr = dns.resolver.query(ns)[0].address
+ nameservers.append(nsaddr)
+ except Exception as e:
+ raise AnsibleError("dns lookup NS: %s" % to_native(e))
+ myres.nameservers = nameservers
+ continue
+ if '=' in t:
+ try:
+ opt, arg = t.split('=', 1)
+ except Exception:
+ pass
+
+ if opt == 'qtype':
+ qtype = arg.upper()
+ elif opt == 'flat':
+ flat = int(arg)
+ elif opt == 'class':
+ try:
+ rdclass = dns.rdataclass.from_text(arg)
+ except Exception as e:
+ raise AnsibleError("dns lookup illegal CLASS: %s" % to_native(e))
+ elif opt == 'retry_servfail':
+ myres.retry_servfail = boolean(arg)
+ elif opt == 'fail_on_error':
+ fail_on_error = boolean(arg)
+ elif opt == 'real_empty':
+ real_empty = boolean(arg)
+
+ continue
+
+ if '/' in t:
+ try:
+ domain, qtype = t.split('/')
+ domains.append(domain)
+ except Exception:
+ domains.append(t)
+ else:
+ domains.append(t)
+
+ # print "--- domain = {0} qtype={1} rdclass={2}".format(domain, qtype, rdclass)
+
+ if qtype.upper() == 'PTR':
+ reversed_domains = []
+ for domain in domains:
+ try:
+ n = dns.reversename.from_address(domain)
+ reversed_domains.append(n.to_text())
+ except dns.exception.SyntaxError:
+ pass
+ except Exception as e:
+ raise AnsibleError("dns.reversename unhandled exception %s" % to_native(e))
+ domains = reversed_domains
+
+ if len(domains) > 1:
+ real_empty = True
+
+ ret = []
+
+ for domain in domains:
+ try:
+ answers = myres.query(domain, qtype, rdclass=rdclass)
+ for rdata in answers:
+ s = rdata.to_text()
+ if qtype.upper() == 'TXT':
+ s = s[1:-1] # Strip outside quotes on TXT rdata
+
+ if flat:
+ ret.append(s)
+ else:
+ try:
+ rd = make_rdata_dict(rdata)
+ rd['owner'] = answers.canonical_name.to_text()
+ rd['type'] = dns.rdatatype.to_text(rdata.rdtype)
+ rd['ttl'] = answers.rrset.ttl
+ rd['class'] = dns.rdataclass.to_text(rdata.rdclass)
+
+ ret.append(rd)
+ except Exception as err:
+ if fail_on_error:
+ raise AnsibleError("Lookup failed: %s" % str(err))
+ ret.append(str(err))
+
+ except dns.resolver.NXDOMAIN as err:
+ if fail_on_error:
+ raise AnsibleError("Lookup failed: %s" % str(err))
+ if not real_empty:
+ ret.append('NXDOMAIN')
+ except dns.resolver.NoAnswer as err:
+ if fail_on_error:
+ raise AnsibleError("Lookup failed: %s" % str(err))
+ if not real_empty:
+ ret.append("")
+ except dns.resolver.Timeout as err:
+ if fail_on_error:
+ raise AnsibleError("Lookup failed: %s" % str(err))
+ if not real_empty:
+ ret.append("")
+ except dns.exception.DNSException as err:
+ raise AnsibleError("dns.resolver unhandled exception %s" % to_native(err))
+
+ return ret