DNS checking API

This commit is contained in:
Sebastian Lohff 2017-05-01 06:11:54 +02:00
parent 278a44988e
commit 10580c4b4b
6 changed files with 222 additions and 2 deletions

129
api/dnshelper.py Normal file
View File

@ -0,0 +1,129 @@
import dns.name
import dns.message
import dns.query
import dns.resolver
#from collections import defaultdict
# FIXME: DNS timeouts
def compareRecords(rrset, expected):
result = {
"nameMissing": [],
"rrMissing": [],
"rrExtra": [],
}
for domain, rrtype, content in expected:
for rrrec in rrset:
if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype:
for name in content:
if name not in map(lambda _x: _x.to_text(), rrrec.items):
# record missing
result["rrMissing"].append((domain, rrtype, name))
for item in rrrec.items:
if item.to_text() not in content:
# superfluous record
result["rrExtra"].append((domain, rrtype, item.to_text()))
break
else:
# domain + rr nicht in nameserver
result["nameMissing"].append((domain, rrtype))
success = not any(len(_x) > 0 for _x in result.values())
print("NARF", success, result)
return success, result
def dnsQuery(domain, rrType, nameserverIp):
dname = dns.name.from_text(domain)
req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType))
resp = dns.query.udp(req, nameserverIp, timeout=2.0)
if resp.rcode() != dns.rcode.NXDOMAIN:
rrset = resp.answer + resp.authority + resp.additional
return True, rrset
else:
return False, []
def checkDomain(domain, tldNameserver, nameservers):
print(domain, tldNameserver, nameservers)
result = []
# build record set
nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))]
glueRecords = []
for ns in nameservers:
if ns.name.endswith("." + domain):
if ns.glueIPv4 or ns.glueIPv6:
if ns.glueIPv4:
glueRecords.append((ns.name, "A", [ns.glueIPv4]))
if ns.glueIPv6:
glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
else:
result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain)))
print(nsRecords, glueRecords)
# 1. TLD nameserver
try:
found, rrset = dnsQuery(domain, "ANY", tldNameserver)
if found:
#print(rrset, nsRecords, glueRecords)
success, errors = compareRecords(rrset, nsRecords + glueRecords)
if success:
result.append(("succ", "All records present in TLD nameserver"))
else:
result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors))
else:
result.append(("err", "Domain %s not found in TLD nameserver" % (domain,)))
except (dns.exception.Timeout, OSError):
result.append(("err", "TLD nameserver is currently not reachable"))
# find other records...
# 2. your nameservers
for ns in nameservers:
addr = None
if ns.glueIPv4:
addr = ns.glueIPv4
elif ns.glueIPv6:
addr = ns.glueIPv6
else:
for rrType in ("A", "AAAA"):
try:
r = dns.resolver.Resolver()
r.timeout = 2.0
q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType))
addr = q.response.answer[0].items[0].address
except (dns.exception.DNSException, OSError):
pass
if addr:
err = False
errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []}
try:
for rec in (nsRecords + glueRecords):
found, rrset = dnsQuery(rec[0], rec[1], addr)
success, errors = compareRecords(rrset, nsRecords + glueRecords)
if not success:
err = True
for k in errors.keys():
errDict[k].extend(errors[k])
if not err:
result.append(("succ", "Nameserver %s is configured correctly" % ns.name))
else:
print(" ==> ", errDict, addr)
result.append(("err", "Nameserver %s recordset does not match the database" % (ns.name,), errDict))
except (dns.exception.DNSException, OSError):
result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr)))
else:
result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name))
return result

View File

@ -7,6 +7,7 @@ from django.db.models import Q
from whoisdb.models import ASBlock, ASNumber, InetNum
from domains.models import Domain, ReverseZone
from dnmgmt.settings import TLD_NAMESERVERS
from .dnshelper import checkDomain as helperCheckDomain
import ipaddress
@ -187,7 +188,8 @@ def checkDomain(request):
ret["success"] = True
ret["domain"] = domain.name
ret["result"] = checkDomain(domain.name, TLD_NAMESERVERS, domain.nameservers.all())
# FIXME: change this if we ever have more than one...
ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all())
except Domain.DoesNotExist:
ret["errorMsg"] = "Domain does not exist"
@ -210,7 +212,8 @@ def checkRzone(request):
raise ReverseZone.DoesNotExist()
ret["success"] = True
ret["result"] = checkDomain(rzone.name, TLD_NAMESERVERS, rzone.nameservers.all())
# FIXME: change this if we ever have more than one...
ret["result"] = helperCheckDomain(rzone.name, TLD_NAMESERVERS[0], rzone.nameservers.all())
except Domain.DoesNotExist:
ret["errorMsg"] = "ReverseZone does not exist"

View File

@ -6,6 +6,7 @@ urlpatterns = [
url(r'^$', domains_views.overview, name='overview'),
url(r'domain/create/$', domains_views.DomainCreate.as_view(), name='domain-create'),
url(r'domain/check/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainCheck.as_view(), name='domain-check'),
url(r'domain/show/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDetail.as_view(), name='domain-show'),
url(r'domain/edit/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainEdit.as_view(), name='domain-edit'),
url(r'domain/delete/(?P<domain>[a-z0-9.-]+)/$', domains_views.DomainDelete.as_view(), name='domain-delete'),

View File

@ -34,6 +34,14 @@ class DomainCreate(LoginRequiredMixin, CreateView):
return kwargs
class DomainCheck(LoginRequiredMixin, DetailView):
model = Domain
slug_field = "name"
slug_url_kwarg = "domain"
context_object_name = "domain"
template_name = "domains/domain_check.html"
class DomainDetail(LoginRequiredMixin, DetailView):
model = Domain
slug_field = "name"

BIN
static/img/loader.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,79 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Checking domain {{ domain.name }}</div>
<div class="panel-body">
<div id="dnscontent">
</div>
</div>
</div>
</div>
</div>
<script>
function addMessage(div, level, message) {
$(div).html($(div).html() + ' <div role="alert" class="alert alert-'+level+' alert-dismissible"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> '+message+'<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>');
}
function addRecord(d, key, title) {
var msg = "";
if(d[key].length > 0) {
msg += "\n<p>" + title + ":<ul>";
for(var i=0; i<d[key].length; i++)
msg += "<li>" + d[key][i].join(" ") + "</li>";
msg += "</ul></p>";
}
return msg;
}
$(document).ready(function() {
$("#dnscontent").text("Running API request...");
$.get({
url: "{% url "api:domain-check" %}",
data: {domain: "{{ domain.name }}"},
beforeSend: function() {
$("#dnscontent").html('<img src="{% static "img/loader.gif" %}">');
},
success: function(data) {
$("#dnscontent").html("<p>Checked domain: " + data.domain + "</p>");
if(data.success) {
for(message of data.result) {
var level = "success";
if(message[0] == "err")
level = "danger";
var msg = message[1];
if(message.length > 2) {
var d = message[2];
msg += addRecord(d, "nameMissing", "Missing names");
msg += addRecord(d, "rrExtra", "Extra records");
msg += addRecord(d, "rrMissing", "Missing records");
//console.log(d);
//if(d["rrMissing"].length > 0) {
// msg += "\n<p>Missing records:<ul>";
// for(var i=0; i<d["rrMissing"].length; i++)
// msg += "<li>" + d["rrMissing"][i].join(" ") + "</li>";
// msg += "</ul></p>";
//}
}
addMessage("#dnscontent", level, msg);
}
} else {
addMessage("#dnscontent", "danger", "Could not get DNS data from API!");
}
}
});
});
</script>
{% endblock %}