commit 34ffc817721cc42ce23d52c62ea52813c3c72a44 Author: Damian Myerscough Date: Sat Oct 5 18:10:21 2013 -0700 BIND DNS RESTful API diff --git a/bind-api.py b/bind-api.py new file mode 100755 index 0000000..6dc8a2c --- /dev/null +++ b/bind-api.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +import ConfigParser +import dns.tsigkeyring +import dns.resolver +import dns.update +import dns.query +import dns.zone + +from dns.rdatatype import * + +from flask import Flask, jsonify +from werkzeug.routing import BaseConverter + +app = Flask(__name__) + + +class RegexConverter(BaseConverter): + def __init__(self, url_map, *items): + super(RegexConverter, self).__init__(url_map) + self.regex = items[0] + + +app.url_map.converters['regex'] = RegexConverter + + +def parse_config(config): + """ + Parse the user config and retreieve the nameserver, username and + password required to perform dynamic DNS updates + """ + options = {} + parser = ConfigParser.ConfigParser() + parser.read(config) + + options['nameserver'] = parser.get('Nameserver', 'nameserver') + options['username'] = parser.get('Auth', 'username') + options['password'] = parser.get('Auth', 'password') + + options['zones'] = [i + '.' for i in parser.get('Zones', 'valid').split(",")] + + return options + + +@app.route('/dns/zone/', methods=['GET']) +def get_zone(zone_name): + """ + Query a DNS zone file and get every record and return it in JSON + format + """ + config = parse_config('config.ini') + + record_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA'] + valid_zones = config['zones'] + + records = {} + + if not zone_name.endswith('.'): + zone_name = zone_name + '.' + + if zone_name not in valid_zones: + return jsonify({'error': 'zone file not permitted'}) + + try: + zone = dns.zone.from_xfr(dns.query.xfr(config['nameserver'], zone_name)) + except dns.exception.FormError: + return jsonify({'fail': zone_name}) + + for (name, ttl, rdata) in zone.iterate_rdatas(): + if rdata.rdtype != SOA: + if records.get(str(name), 0): + records[str(name)] = records[str(name)] + [{'Answer': str(rdata), 'RecordType': rdata.rdtype, 'TTL': ttl}] + else: + records[str(name)] = [{'Answer': str(rdata), 'RecordType': rdata.rdtype, 'TTL': ttl}] + + return jsonify({zone_name: records}) + + +@app.route('/dns/record/', methods=['GET']) +def get_record(domain): + """ + Allow users to request the records for a particualr record + """ + config = parse_config('config.ini') + + record_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA'] + valid_zones = config['zones'] + + record = {} + + valid = [True for i in valid_zones if domain.endswith(i)] + + """ + Only allow the valid zones to be queried, this should stop + TLD outside of your nameserver from being queried + """ + if valid: + for record_type in record_types: + try: + answers = dns.resolver.query(domain, record_type) + except dns.resolver.NoAnswer: + continue + + record.update({record_type: [str(i) for i in answers.rrset]}) + + return jsonify({domain: record}) + else: + return jsonify({'error': 'zone not permitted'}) + + +@app.route('/dns/record/////', methods=['PUT', 'POST']) +def dns_mgmt(action, domain, ttl, record_type, response): + """ + Allow users to update existing records + """ + config = parse_config('config.ini') + + record_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA'] + valid_zones = config['zones'] + + zone = '' + zone = '.'.join(dns.name.from_text(domain).labels[1:]) + + if record_type not in record_types: + return jsonify({'error': 'not a valid record type'}) + + if zone not in valid_zones: + return jsonify({'error': 'not a valid zone'}) + + """ + If the user is only updating make sure the record exists before + attempting to perform a dynamic update. This will + """ + if action == 'update': + try: + answer = dns.resolver.query('.'.join([domain, zone]), record_type) + except dns.resolver.NXDOMAIN: + return jsonify({'error': 'does not exist'}) + + tsig = dns.tsigkeyring.from_text({config['username']: config['password']}) + + update = dns.update.Update(zone, keyring=tsig) + update.replace(dns.name.from_text(domain).labels[0], ttl, str(record_type), str(response)) + + try: + response = dns.query.tcp(update, config['nameserver']) + except: + return jsonify({'error': 'unable to update domain'}) + + if response.rcode() == 0: + return jsonify({domain: 'successfully updated'}) + else: + return jsonify({domain: 'failed to update'}) + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..d67163e --- /dev/null +++ b/config.ini @@ -0,0 +1,9 @@ +[Nameserver] +nameserver = 172.16.33.135 + +[Auth] +username = TRANSFER +password = /h9xIk0tbF3A0lOzy6BYAQ== + +[Zones] +valid = ops.zone.org,eng.zone.net diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..46f17c1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +Flask==0.10.1 +Jinja2==2.7.1 +MarkupSafe==0.18 +Werkzeug==0.9.4 +argparse==1.2.1 +distribute==0.6.24 +dnspython==1.11.1 +itsdangerous==0.23 +wsgiref==0.1.2