use zones/nameserver IPs/update keys from DB, logging (thanks to asmaps)
remove unneeded stuff from settings (we still need some in conftest.py for the tests, though) init DB for tests via conftest.py more update algorithm choices give origin zone (if we already know it) to dnstools functions new views: DomainOverview, DeleteDomain unify deletion templates using delete_object.html add django-extensions
This commit is contained in:
parent
5f272aa7c4
commit
49693121ea
22
conftest.py
Normal file
22
conftest.py
Normal file
@ -0,0 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.utils.translation import activate
|
||||
|
||||
|
||||
# Note: fixture must be "function" scope (default), see https://github.com/pelme/pytest_django/issues/33
|
||||
@pytest.fixture(autouse=True)
|
||||
def db_init(db):
|
||||
"""
|
||||
Init the database contents for testing, so we have a service domain, ...
|
||||
"""
|
||||
from nsupdate.main.models import Domain
|
||||
from django.db import IntegrityError
|
||||
Domain.objects.create(domain='nsupdate.info',
|
||||
nameserver_ip='85.10.192.104',
|
||||
nameserver_update_algorithm='HMAC_SHA512',
|
||||
nameserver_update_key='YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ==',
|
||||
available_for_everyone=True)
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
activate('en')
|
@ -3,6 +3,7 @@ Tests for dnstools module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
from dns.resolver import NXDOMAIN
|
||||
|
||||
|
@ -4,6 +4,9 @@ Misc. DNS related code: query, dynamic update, etc.
|
||||
Usually, higher level code wants to call the add/update/delete functions.
|
||||
"""
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import dns.inet
|
||||
import dns.name
|
||||
import dns.resolver
|
||||
@ -167,11 +170,10 @@ def get_ns_info(origin):
|
||||
:param origin: zone we are dealing with, must be with trailing dot
|
||||
:return: master nameserver, update key, update algo
|
||||
"""
|
||||
# later look this up from Domain model: domain+'.': nameserver_ip, nameserver_update_key
|
||||
ns_info = {
|
||||
settings.BASEDOMAIN + '.': (settings.SERVER, settings.UPDATE_KEY, settings.UPDATE_ALGO),
|
||||
}
|
||||
return ns_info[origin]
|
||||
from .models import Domain
|
||||
d = Domain.objects.get(domain=origin.rstrip('.'))
|
||||
algorithm = getattr(dns.tsig, d.nameserver_update_algorithm)
|
||||
return d.nameserver_ip, d.nameserver_update_key, algorithm
|
||||
|
||||
|
||||
def update_ns(fqdn, rdtype='A', ipaddr=None, origin=None, action='upd', ttl=60):
|
||||
@ -201,5 +203,7 @@ def update_ns(fqdn, rdtype='A', ipaddr=None, origin=None, action='upd', ttl=60):
|
||||
elif action == 'upd':
|
||||
assert ipaddr is not None
|
||||
upd.replace(name, ttl, rdtype, ipaddr)
|
||||
logger.debug("performing %s for name %s and origin %s with rdtype %s and ipaddr %s" % (
|
||||
action, name, origin, rdtype, ipaddr))
|
||||
response = dns.query.tcp(upd, nameserver)
|
||||
return response
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django import forms
|
||||
|
||||
from .models import Host
|
||||
from .models import Host, Domain
|
||||
|
||||
|
||||
class CreateHostForm(forms.ModelForm):
|
||||
@ -14,3 +14,9 @@ class EditHostForm(forms.ModelForm):
|
||||
class Meta(object):
|
||||
model = Host
|
||||
fields = ['comment']
|
||||
|
||||
|
||||
class CreateDomainForm(forms.ModelForm):
|
||||
class Meta(object):
|
||||
model = Domain
|
||||
exclude = ['created_by']
|
||||
|
@ -34,12 +34,25 @@ def domain_blacklist_validator(value):
|
||||
raise ValidationError(u'This domain is not allowed')
|
||||
|
||||
|
||||
UPDATE_ALGORITHMS = (
|
||||
('HMAC_SHA512', 'HMAC_SHA512'),
|
||||
('HMAC_SHA384', 'HMAC_SHA384'),
|
||||
('HMAC_SHA256', 'HMAC_SHA256'),
|
||||
('HMAC_SHA224', 'HMAC_SHA224'),
|
||||
('HMAC_SHA1', 'HMAC_SHA1'),
|
||||
('HMAC_MD5', 'HMAC_MD5'),
|
||||
)
|
||||
|
||||
|
||||
class Domain(models.Model):
|
||||
domain = models.CharField(max_length=256, unique=True)
|
||||
nameserver_ip = models.GenericIPAddressField(
|
||||
max_length=256,
|
||||
help_text="An IP where the nsupdates for this domain will be sent to")
|
||||
help_text="IP where the dynamic updates for this domain will be sent to")
|
||||
nameserver_update_key = models.CharField(max_length=256)
|
||||
nameserver_update_algorithm = models.CharField(
|
||||
max_length=256, default='HMAC_SHA512', choices=UPDATE_ALGORITHMS)
|
||||
available_for_everyone = models.BooleanField(default=False)
|
||||
|
||||
last_update = models.DateTimeField(auto_now=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
@ -88,14 +101,14 @@ class Host(models.Model):
|
||||
|
||||
def getIPv4(self):
|
||||
try:
|
||||
return dnstools.query_ns(self.get_fqdn(), 'A')
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
|
||||
return dnstools.query_ns(self.get_fqdn(), 'A', origin=self.domain.domain)
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.resolver.Timeout):
|
||||
return ''
|
||||
|
||||
def getIPv6(self):
|
||||
try:
|
||||
return dnstools.query_ns(self.get_fqdn(), 'AAAA')
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
|
||||
return dnstools.query_ns(self.get_fqdn(), 'AAAA', origin=self.domain.domain)
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.resolver.Timeout):
|
||||
return ''
|
||||
|
||||
def poke(self):
|
||||
@ -118,6 +131,6 @@ class Host(models.Model):
|
||||
|
||||
def post_delete_host(sender, **kwargs):
|
||||
obj = kwargs['instance']
|
||||
dnstools.delete(obj.get_fqdn())
|
||||
dnstools.delete(obj.get_fqdn(), origin=obj.domain.domain)
|
||||
|
||||
post_delete.connect(post_delete_host, sender=Host)
|
||||
|
@ -2,10 +2,9 @@
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<h3>Delete Host <small><a href="{% url 'overview' %}"><i class="icon-double-angle-left"></i> back to overview</a></small></h3>
|
||||
<h3>Delete {{ object }}</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object }}"?</p>
|
||||
@ -13,6 +12,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
61
nsupdate/main/templates/main/domain_overview.html
Normal file
61
nsupdate/main/templates/main/domain_overview.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends "base.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h3>Your Domains</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Nameserver IP</th>
|
||||
<th>Update key</th>
|
||||
<th>Algorithm</th>
|
||||
<th>Public?</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td><b>{{ domain.domain }}</b></td>
|
||||
<td>{{ domain.nameserver_ip }}</td>
|
||||
<td>{{ domain.nameserver_update_key }}</td>
|
||||
<td>{{ domain.get_nameserver_update_algorithm_display }}</td>
|
||||
<td>{{ domain.available_for_everyone|yesno }}</td>
|
||||
<td>
|
||||
<a href="{% url 'delete_domain' domain.pk %}"><i class="icon icon-remove"></i> delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6">No domains yet.</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<div class="well well-sm">
|
||||
<h3>Add a new Domain</h3>
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="well well-sm">
|
||||
<h3>Help</h3>
|
||||
<p>Here you can add new domains (zones) which you control (and this is only useful if you
|
||||
have some own zone which you can update automatically).</p>
|
||||
<p>You need to configure the primary master nameserver of the zone so it accepts dynamic updates
|
||||
if the correct update key is presented (which is just a shared secret between the nameserver
|
||||
and this service).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@ from django.views.generic import TemplateView
|
||||
|
||||
from .views import (
|
||||
HomeView, OverviewView, HostView, DeleteHostView, AboutView, HelpView, GenerateSecretView,
|
||||
RobotsTxtView, )
|
||||
RobotsTxtView, DomainOverwievView, DeleteDomainView)
|
||||
from ..api.views import (
|
||||
MyIpView, DetectIpView, NicUpdateView, AuthorizedNicUpdateView)
|
||||
|
||||
@ -20,6 +20,10 @@ urlpatterns = patterns(
|
||||
url(r'^generate_secret/(?P<pk>\d+)/$', GenerateSecretView.as_view(), name='generate_secret_view'),
|
||||
url(r'^host/(?P<pk>\d+)/delete/$',
|
||||
DeleteHostView.as_view(), name='delete_host'),
|
||||
url(r'^domain_overview/$',
|
||||
DomainOverwievView.as_view(), name='domain_overview'),
|
||||
url(r'^domain/(?P<pk>\d+)/delete/$',
|
||||
DeleteDomainView.as_view(), name='delete_domain'),
|
||||
url(r'^myip$', MyIpView),
|
||||
url(r'^detectip/$', DetectIpView),
|
||||
url(r'^detectip/(?P<secret>\w+)/$', DetectIpView),
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import dns.inet
|
||||
|
||||
from django.db.models import Q
|
||||
from django.views.generic import TemplateView, CreateView
|
||||
from django.views.generic.edit import UpdateView, DeleteView
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
@ -13,8 +14,8 @@ from django.core.exceptions import PermissionDenied
|
||||
|
||||
import dnstools
|
||||
|
||||
from .forms import CreateHostForm, EditHostForm
|
||||
from .models import Host
|
||||
from .forms import CreateHostForm, EditHostForm, CreateDomainForm
|
||||
from .models import Host, Domain
|
||||
|
||||
|
||||
class GenerateSecretView(UpdateView):
|
||||
@ -87,11 +88,17 @@ class OverviewView(CreateView):
|
||||
def get_success_url(self):
|
||||
return reverse('generate_secret_view', args=(self.object.pk,))
|
||||
|
||||
def get_form(self, form_class):
|
||||
form = super(OverviewView, self).get_form(form_class)
|
||||
form.fields['domain'].queryset = Domain.objects.filter(
|
||||
Q(created_by=self.request.user) | Q(available_for_everyone=True))
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
self.object.created_by = self.request.user
|
||||
self.object.save()
|
||||
dnstools.add(self.object.get_fqdn(), self.request.META['REMOTE_ADDR'])
|
||||
dnstools.add(self.object.get_fqdn(), self.request.META['REMOTE_ADDR'], origin=self.object.domain.domain)
|
||||
messages.add_message(self.request, messages.SUCCESS, 'Host added.')
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
@ -136,8 +143,7 @@ class HostView(UpdateView):
|
||||
|
||||
class DeleteHostView(DeleteView):
|
||||
model = Host
|
||||
template_name = "main/delete_host.html"
|
||||
form_class = EditHostForm
|
||||
template_name = "main/delete_object.html"
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
@ -159,6 +165,52 @@ class DeleteHostView(DeleteView):
|
||||
return context
|
||||
|
||||
|
||||
class DomainOverwievView(CreateView):
|
||||
model = Domain
|
||||
template_name = "main/domain_overview.html"
|
||||
form_class = CreateDomainForm
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(DomainOverwievView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('domain_overview')
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
self.object.created_by = self.request.user
|
||||
self.object.save()
|
||||
messages.add_message(self.request, messages.SUCCESS, 'Domain added.')
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(
|
||||
DomainOverwievView, self).get_context_data(*args, **kwargs)
|
||||
context['nav_domains'] = True
|
||||
context['domains'] = Domain.objects.filter(
|
||||
created_by=self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
class DeleteDomainView(DeleteView):
|
||||
model = Domain
|
||||
template_name = "main/delete_object.html"
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(DeleteDomainView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
obj = super(DeleteDomainView, self).get_object(*args, **kwargs)
|
||||
if obj.created_by != self.request.user:
|
||||
raise PermissionDenied() # or Http404
|
||||
return obj
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('domain_overview')
|
||||
|
||||
|
||||
def RobotsTxtView(request):
|
||||
"""
|
||||
Dynamically serve robots.txt content.
|
||||
|
@ -27,7 +27,6 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
SERVER = '85.10.192.104' # ns1.thinkmo.de (master / dynamic upd server for nsupdate.info)
|
||||
BASEDOMAIN = 'nsupdate.info'
|
||||
|
||||
NONEXISTING_HOST = 'nonexisting.' + BASEDOMAIN
|
||||
@ -39,9 +38,6 @@ WWW_IPV6_IP = '2001:41d0:8:e00e::1'
|
||||
|
||||
BAD_AGENTS = set() # useragent blacklist for /nic/update service
|
||||
|
||||
UPDATE_ALGO = dns.tsig.HMAC_SHA512
|
||||
UPDATE_KEY = 'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ=='
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = [WWW_HOST, WWW_IPV4_HOST, WWW_IPV6_HOST]
|
||||
@ -156,6 +152,7 @@ INSTALLED_APPS = (
|
||||
'nsupdate.main',
|
||||
'bootstrapform',
|
||||
'registration',
|
||||
'django_extensions',
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
@ -189,6 +186,11 @@ LOGGING = {
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'nsupdate.main.dnstools': {
|
||||
'handlers': ['stderr', ],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins', ],
|
||||
'level': 'ERROR',
|
||||
|
@ -52,6 +52,7 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'account_profile' %}">Profile</a></li>
|
||||
<li><a href="{% url 'password_change' %}">Change password</a></li>
|
||||
<li><a href="{% url 'domain_overview' %}">Own Domains</a></li>
|
||||
{% if request.user.is_staff %}
|
||||
<li><a href="/admin/">Admin</a></li>
|
||||
{% endif %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user