Whilst glancing through somebody’s slide deck recently, I noticed a reference to the DNSTXT lookup-plugin for Ansible which I wrote in 2012, and I caught myself smiling. Then, just a day ago, I saw a request on the Ansible mailing-list where somebody asked how to do DNS lookups via Ansible. Serge responded along the lines of “use dig(1) with the pipe lookup plugin”, which is probably good enough for most cases.
Even so, in a moment of weakness, I thought: na, let’s do better. That, however, turned
out to be a bit of a long story, because I found an issue with the way lookup plugins
return lists to Jinja2 templates. Be that
as it may, Brian Coca of Ansible has merged a fix which allows me to present you with
the first version of a new Ansible lookup plugin which I’m naming dig
. :-)
Assume the following playbook to demonstrate dig
in a template:
---
- hosts:
- localhost
connection: local
gather_facts: False
tasks:
- local_action: template src=dns.j2 dest=/tmp/dns.out
- action: debug msg="GOOG == {{ item.1 }}"
with_indexed_items: "{{ lookup('dig', 'google.com./A', wantlist=True) }}"
and this template:
# 01) -- My external IP address is: {{ lookup('dig', '@ns1.google.com o-o.myaddr.l.google.com/TXT') }}
# 02) --- iis.se/DNSKEY
{% for dnskey in lookup('dig', 'iis.se./DNSKEY', wantlist=True) -%}
{{ dnskey }}
{% endfor %}
# 03) --- isc.org/TXT
{% for rr in lookup('dig', 'isc.org./TXT', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 04) --- iis.se/DS
{% for rr in lookup('dig', 'iis.se./DS', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 05) --- google.com/A
{% for rr in lookup('dig', 'google.com./A', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 06) --- google.com --- specials
First Goog record: {{ lookup('dig', 'google.com', wantlist=True)[0] }}
Random Goog record: {{ lookup('dig', 'google.com', wantlist=True) | random }}
# 07) --- www.kame.net/AAAA
{% for rr in lookup('dig', 'www.kame.net/AAAA', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 08a) --- nl.cc.jpmens.net/TXT
{% for rr in lookup('dig', 'nl.cc.jpmens.net./TXT', wantlist=True) -%}
{{ rr | capitalize }}
{% endfor %}
# 08b) --- nl.cc.jpmens.net qtype=txt
{% for rr in lookup('dig', 'nl.cc.jpmens.net. qtype=txt', wantlist=True) -%}
{{ rr | capitalize }}
{% endfor %}
# 09) --- denic.de/NSEC3PARAM
{% for rr in lookup('dig', 'denic.de./NSEC3PARAM', wantlist=True) -%}
{{ rr | upper}}
{% endfor %}
# 10) --- kdkdkd.iis.se/A [NXDOMAIN expected]
{% for rr in lookup('dig', 'kdkdkd.iis.se.', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 11) --- mary.jpmens.org/TXT
{% for rr in lookup('dig', 'mary.jpmens.org/TXT', wantlist=True) -%}
[{{ rr | upper }}]
{% endfor %}
# 12) -- 8.8.8.8/PTR
{% for rr in lookup('dig', '8.8.8.8/PTR', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 13) -- 4.4.8.8.in-addr.arpa./PTR
{% for rr in lookup('dig', '4.4.8.8.in-addr.arpa./PTR', wantlist=True) -%}
{{ rr }}
{% endfor %}
# 14) -- @192.168.1.114 p01.aa/TXT [query specific server]
{% for rr in lookup('dig', '@192.168.1.114 p01.aa/TXT', wantlist=True) %}
{{ rr }}
{% endfor %}
{% for rr in lookup('dig', 'p01.aa/TXT @192.168.1.114 ', wantlist=True) %}
{{ rr }}
{% endfor %}
# 15) -- @192.168.1.114 p01.aa/MX [NODATA expected]
{% for rr in lookup('dig', '@192.168.1.114 p01.aa/MX', wantlist=True) %}
--->{{ rr }}<---
{% endfor %}
# 16) -- heise.de/MX (just hostname of first returned MX)
{#
lookup returns list.
take first element [0]
split that into list (priority, hostname)
grab hostname [1]
#}
{{ lookup('dig', 'heise.de/MX', wantlist=True)[0].split()[1] }}
# 17) -- PTR of ansible_default_ipv4
{{ lookup('dig', ansible_default_ipv4.address + '/PTR') }}
# 18) -- ***** dict: iis.se. qtype=A flat=0
{% set data = lookup('dig', 'iis.se. qtype=a flat=0', wantlist=True)[0] -%}
Domain...: {{ data.owner }}
Type.....: {{ data.type }}
Address..: {{ data.address }}
TTL......: {{ data.ttl }}
# 19) -- **** dict: iis.se qtype=MX flat=0
{% set data = lookup('dig', 'iis.se. qtype=mx flat=0', wantlist=True) -%}
{{ data | pprint }}
# 20) -- **** dict: ubu.jpmens.org qtype=SSHFP flat=0
{% set data = lookup('dig', 'ubu.jpmens.org. qtype=sshfp flat=0', wantlist=True) -%}
{{ data | pprint }}
# 21) -- **** dict: 8.8.8.8/PTR flat=0
{{ lookup('dig', '8.8.8.8/ptr flat=0', wantlist=True) | pprint }}
# 22) -- **** dict: statdns.net LOC
{{ lookup('dig', 'statdns.net./LOC flat=1', wantlist=True) | pprint }}
{{ lookup('dig', 'statdns.net/loc flat=0', wantlist=True) | pprint }}
When I run the playbook, the following output is produced:
# 01) -- My external IP address is: 174.0.23.4
# 02) --- iis.se/DNSKEY
257 3 5 AwEAAcq5u+qe5VibnyvSnGU20panweAk 2QxflGVuVQhzQABQV4SIdAQs+LNVHF61 lcxe504jhPmjeQ656X6t+dHpRz1DdPO/ ukcIITjIRoJHqS+XXyL6gUluZoDU+K6v pxkGJx5m5n4boRTKCTUAR/9rw2+IQRRT tb6nBwsC3pmf9IlJQjQMb1cQTb0UO7fY gXDZIYVul2LwGpKRrMJ6Ul1nepkSxTMw Q4H9iKE9FhqPeIpzU9dnXGtJ+ZCx9tWS Z9VsSLWBJtUwoE6ZfIoF1ioqqxfGl9JV 1/6GkDxo3pMN2edhkp8aqoo/R+mrJYi0 vE8jbXvhZ12151DywuSxbGjAlxk=
256 3 5 BQEAAAAB9AdrxXeW/9/peShyQ3c2Pjuh mmzOtVIuBPiNS9hcAFv42yRWU3Fyk04U JrU5XLlNcznV+8nc/3WcgAe2qlRR0Wma C5CyiaLX8zdd19JSmrDXMb33gNUCc3wk vt6YPxf+y+ionxKoGJaPXw/bNmnkIHNa gGEo5jFxrdS8NRIQx6s=
# 03) --- isc.org/TXT
v=spf1 a mx ip4:204.152.184.0/21 ip4:149.20.0.0/16 ip6:2001:04F8::0/32 ip6:2001:500:60::65/128 ~all
$Id: isc.org,v 1.1971 2015-02-25 18:38:13 jtl Exp $
# 04) --- iis.se/DS
18937 5 1 10dd1efdc7841abfdf630c8bb37153724d70830a
18937 5 2 b5c422428dea4137fbf15e1049a48d27fa5eade64d2ec9f3b58a994a6abde543
# 05) --- google.com/A
74.125.136.100
74.125.136.139
74.125.136.102
74.125.136.138
74.125.136.101
74.125.136.113
# 06) --- google.com --- specials
First Goog record: 74.125.136.100
Random Goog record: 74.125.136.138
# 07) --- www.kame.net/AAAA
2001:200:dff:fff1:216:3eff:feb1:44d7
# 08a) --- nl.cc.jpmens.net/TXT
Netherlands
# 08b) --- nl.cc.jpmens.net qtype=txt
Netherlands
# 09) --- denic.de/NSEC3PARAM
1 0 15 DE1C
# 10) --- kdkdkd.iis.se/A [NXDOMAIN expected]
NXDOMAIN
# 11) --- mary.jpmens.org/TXT
[HIS FLEECE WAS WHITE AS SNOW,]
[AND EVERYWHERE THAT MARY WENT,]
[THE LAMB WAS SURE TO GO.]
[HE FOLLOWED HER TO SCHOOL ONE DAY," "WHICH WAS AGAINST THE RULE,]
[MARY HAD A LITTLE LAMB,]
[IT MADE THE CHILDREN LAUGH AND PLAY" "TO SEE A LAMB AT SCHOOL.]
# 12) -- 8.8.8.8/PTR
google-public-dns-a.google.com.
# 13) -- 4.4.8.8.in-addr.arpa./PTR
google-public-dns-b.google.com.
# 14) -- @192.168.1.114 p01.aa/TXT [query specific server]
Hola patatin
Hola patatin
# 15) -- @192.168.1.114 p01.aa/MX [NODATA expected]
---><---
# 16) -- heise.de/MX (just hostname of first returned MX)
relay.heise.de.
# 17) -- PTR of ansible_default_ipv4
tiggr.ww.mens.de.
# 18) -- ***** dict: iis.se. qtype=A flat=0
Domain...: iis.se.
Type.....: A
Address..: 91.226.36.46
TTL......: 17
# 19) -- **** dict: iis.se qtype=MX flat=0
[{'exchange': 'mx2.iis.se.',
'owner': 'iis.se.',
'preference': 5,
'ttl': 17,
'type': 'MX'},
{'exchange': 'mx1.iis.se.',
'owner': 'iis.se.',
'preference': 5,
'ttl': 17,
'type': 'MX'}]
# 20) -- **** dict: ubu.jpmens.org qtype=SSHFP flat=0
[{'algorithm': 1,
'fingerprint': '7e7a55cea3b8e15528665a6781ca7c35190cf0eb',
'fp_type': 1,
'owner': 'ubu.jpmens.org.',
'ttl': 77,
'type': 'SSHFP'},
{'algorithm': 2,
'fingerprint': 'cc17f14da60cf38e809fe58b10d0f22680d59d08',
'fp_type': 1,
'owner': 'ubu.jpmens.org.',
'ttl': 77,
'type': 'SSHFP'}]
# 21) -- **** dict: 8.8.8.8/PTR flat=0
[{'owner': '8.8.8.8.in-addr.arpa.',
'target': 'google-public-dns-a.google.com.',
'ttl': 23925,
'type': 'PTR'}]
# 22) -- **** dict: statdns.net LOC
['52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000.00m 10.00m']
[{'altitude': -200.0,
'horizontal_precision': 1000000.0,
'latitude': (52, 22, 23, 0),
'longitude': (4, 53, 32, 0),
'owner': 'statdns.net.',
'size': 0.0,
'ttl': 227,
'type': 'LOC',
'vertical_precision': 1000.0}]
Some highlights:
- Specify a name to query for an
A
record by default. (I’ll write in my will that one of my grandchildren should submit a pull-request to change that toAAAA
as soon as it’s mainstream. :-) - Specify
domain.name/QTYPE
to query for that type. - Alternatively, specify
domain.name qtype=QTYPE
to query for that type. - Use
192.168.1.2/PTR
(or192.168.1.2 qtype=PTR
) as a shorthand to2.1.168.192.in-addr.arpa/PTR
. (Thinkdig -x
.) - Add
@nameserver
to use that particular resolver.nameserver
may also be a comma-separated list of addresses or hostnames, whereby hostnames are resolved by the systems’ default resolver. TXT
records are stripped of leading and trailing quotes. This produces strange results at times.- Specify
flat=0
to obtain results as a list of dicts (currentlyA
,AAAA
,DS
,SOA
,DNSKEY
,LOC
,MX
and a few others.)
I’m not sure whether I should submit this as a PR to the Ansible project. I have submitted this as a pull-request to Ansible core, and it was merged on 2015-02-27.
Update: 2015-03-18: I’ve created another PR which supports specifying the class so as to be able to do this:
version.bind: {{ lookup('dig', '@i.ns.se version.bind qtype=TXT class=CH flat=1', wantlist=True)[0] }}
which would return
version.bind: contact info@netnod.se