Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
282 lines (209 sloc) 8.51 KB
# -*- coding: utf-8 -*-
'''
Dynamic DNS Runner
==================
.. versionadded:: Beryllium
Runner to interact with DNS server and create/delete/update DNS records
:codeauthor: Nitin Madhok <nmadhok@clemson.edu>
'''
from __future__ import absolute_import, print_function, unicode_literals
# Import python libs
import os
import logging
# Import third party libs
HAS_LIBS = False
try:
import dns.query
import dns.update
import dns.tsigkeyring
HAS_LIBS = True
except ImportError:
HAS_LIBS = False
# Import salt libs
import salt.utils.files
import salt.utils.json
log = logging.getLogger(__name__)
def __virtual__():
'''
Check if required libs (python-dns) is installed and load runner
only if they are present
'''
if not HAS_LIBS:
return False
return True
def _get_keyring(keyfile):
keyring = None
if keyfile and os.path.isfile(os.path.expanduser(keyfile)):
with salt.utils.files.fopen(keyfile) as _f:
keyring = dns.tsigkeyring.from_text(salt.utils.json.load(_f))
return keyring
def create(zone, name, ttl, rdtype, data, keyname, keyfile, nameserver,
timeout, port=53, keyalgorithm='hmac-md5'):
'''
Create a DNS record. The nameserver must be an IP address and the master running
this runner must have create privileges on that server.
CLI Example:
.. code-block:: bash
salt-run ddns.create domain.com my-test-vm 3600 A 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5
'''
if zone in name:
name = name.replace(zone, '').rstrip('.')
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, rdtype)
answer = dns.query.udp(request, nameserver, timeout, port)
rdata_value = dns.rdatatype.from_text(rdtype)
rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data)
for rrset in answer.answer:
if rdata in rrset.items:
return {fqdn: 'Record of type \'{0}\' already exists with ttl of {1}'.format(rdtype, rrset.ttl)}
keyring = _get_keyring(keyfile)
dns_update = dns.update.Update(zone, keyring=keyring, keyname=keyname,
keyalgorithm=keyalgorithm)
dns_update.add(name, ttl, rdata)
answer = dns.query.udp(dns_update, nameserver, timeout, port)
if answer.rcode() > 0:
return {fqdn: 'Failed to create record of type \'{0}\''.format(rdtype)}
return {fqdn: 'Created record of type \'{0}\': {1} -> {2}'.format(rdtype, fqdn, data)}
def update(zone, name, ttl, rdtype, data, keyname, keyfile, nameserver,
timeout, replace=False, port=53, keyalgorithm='hmac-md5'):
'''
Replace, or update a DNS record. The nameserver must be an IP address and the master running
this runner must have update privileges on that server.
.. note::
If ``replace`` is set to True, all records for this name and type will first be deleted and
then recreated. Default is ``replace=False``.
CLI Example:
.. code-block:: bash
salt-run ddns.update domain.com my-test-vm 3600 A 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5
'''
if zone in name:
name = name.replace(zone, '').rstrip('.')
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, rdtype)
answer = dns.query.udp(request, nameserver, timeout, port)
if not answer.answer:
return {fqdn: 'No matching DNS record(s) found'}
rdata_value = dns.rdatatype.from_text(rdtype)
rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data)
for rrset in answer.answer:
if rdata in rrset.items:
rr = rrset.items
if ttl == rrset.ttl:
if replace and (len(answer.answer) > 1
or len(rrset.items) > 1):
break
return {fqdn: 'Record of type \'{0}\' already present with ttl of {1}'.format(rdtype, ttl)}
break
keyring = _get_keyring(keyfile)
dns_update = dns.update.Update(zone, keyring=keyring, keyname=keyname,
keyalgorithm=keyalgorithm)
dns_update.replace(name, ttl, rdata)
answer = dns.query.udp(dns_update, nameserver, timeout, port)
if answer.rcode() > 0:
return {fqdn: 'Failed to update record of type \'{0}\''.format(rdtype)}
return {fqdn: 'Updated record of type \'{0}\''.format(rdtype)}
def delete(zone, name, keyname, keyfile, nameserver, timeout, rdtype=None,
data=None, port=53, keyalgorithm='hmac-md5'):
'''
Delete a DNS record.
CLI Example:
.. code-block:: bash
salt-run ddns.delete domain.com my-test-vm my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 A
'''
if zone in name:
name = name.replace(zone, '').rstrip('.')
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, (rdtype or 'ANY'))
answer = dns.query.udp(request, nameserver, timeout, port)
if not answer.answer:
return {fqdn: 'No matching DNS record(s) found'}
keyring = _get_keyring(keyfile)
dns_update = dns.update.Update(zone, keyring=keyring, keyname=keyname,
keyalgorithm=keyalgorithm)
if rdtype:
rdata_value = dns.rdatatype.from_text(rdtype)
if data:
rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data)
dns_update.delete(name, rdata)
else:
dns_update.delete(name, rdata_value)
else:
dns_update.delete(name)
answer = dns.query.udp(dns_update, nameserver, timeout, port)
if answer.rcode() > 0:
return {fqdn: 'Failed to delete DNS record(s)'}
return {fqdn: 'Deleted DNS record(s)'}
def add_host(zone, name, ttl, ip, keyname, keyfile, nameserver, timeout,
port=53, keyalgorithm='hmac-md5'):
'''
Create both A and PTR (reverse) records for a host.
CLI Example:
.. code-block:: bash
salt-run ddns.add_host domain.com my-test-vm 3600 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5
'''
res = []
if zone in name:
name = name.replace(zone, '').rstrip('.')
fqdn = '{0}.{1}'.format(name, zone)
ret = create(zone, name, ttl, 'A', ip, keyname, keyfile, nameserver,
timeout, port, keyalgorithm)
res.append(ret[fqdn])
parts = ip.split('.')[::-1]
i = len(parts)
popped = []
# Iterate over possible reverse zones
while i > 1:
p = parts.pop(0)
i -= 1
popped.append(p)
zone = '{0}.{1}'.format('.'.join(parts), 'in-addr.arpa.')
name = '.'.join(popped)
rev_fqdn = '{0}.{1}'.format(name, zone)
ret = create(zone, name, ttl, 'PTR', "{0}.".format(fqdn), keyname,
keyfile, nameserver, timeout, port, keyalgorithm)
if "Created" in ret[rev_fqdn]:
res.append(ret[rev_fqdn])
return {fqdn: res}
res.append(ret[rev_fqdn])
return {fqdn: res}
def delete_host(zone, name, keyname, keyfile, nameserver, timeout, port=53,
keyalgorithm='hmac-md5'):
'''
Delete both forward (A) and reverse (PTR) records for a host only if the
forward (A) record exists.
CLI Example:
.. code-block:: bash
salt-run ddns.delete_host domain.com my-test-vm my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5
'''
res = []
if zone in name:
name = name.replace(zone, '').rstrip('.')
fqdn = '{0}.{1}'.format(name, zone)
request = dns.message.make_query(fqdn, 'A')
answer = dns.query.udp(request, nameserver, timeout, port)
try:
ips = [i.address for i in answer.answer[0].items]
except IndexError:
ips = []
ret = delete(zone, name, keyname, keyfile, nameserver, timeout, port=port,
keyalgorithm=keyalgorithm)
res.append("{0} of type \'A\'".format(ret[fqdn]))
for ip in ips:
parts = ip.split('.')[::-1]
i = len(parts)
popped = []
# Iterate over possible reverse zones
while i > 1:
p = parts.pop(0)
i -= 1
popped.append(p)
zone = '{0}.{1}'.format('.'.join(parts), 'in-addr.arpa.')
name = '.'.join(popped)
rev_fqdn = '{0}.{1}'.format(name, zone)
ret = delete(zone, name, keyname, keyfile, nameserver, timeout,
'PTR', "{0}.".format(fqdn), port, keyalgorithm)
if "Deleted" in ret[rev_fqdn]:
res.append("{0} of type \'PTR\'".format(ret[rev_fqdn]))
return {fqdn: res}
res.append(ret[rev_fqdn])
return {fqdn: res}
You can’t perform that action at this time.