Compare commits
No commits in common. "master" and "nsd" have entirely different histories.
11
Dockerfile
11
Dockerfile
|
@ -3,16 +3,11 @@ FROM python:3-alpine
|
|||
MAINTAINER Jeffrey Forman <code@jeffreyforman.net>
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
RUN apk add --no-cache nsd build-base python3-dev libffi-dev openssl-dev libc-dev libxslt-dev mariadb-connector-c-dev \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
COPY requirements.txt /code/
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . /code/
|
||||
|
||||
RUN apk add --no-cache nsd build-base python3-dev libffi-dev openssl-dev libc-dev libxslt-dev \
|
||||
&& pip install --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
A Django web application for viewing and editing BIND DNS zone records.
|
||||
|
||||
It has support for NSD-hosted DNS zones, but as NSD does not support dynamic updates,
|
||||
that feature is not available.
|
||||
|
||||
## Download ##
|
||||
|
||||
```
|
||||
|
@ -24,8 +21,7 @@ pip install -r requirements.txt
|
|||
|
||||
## Running Binder ##
|
||||
|
||||
Over the course of developing Binder, it has come to the fore that using a container makes
|
||||
development and running Binder much easier.
|
||||
Over the course of developing Binder, it has come to the fore that using a container makes development and runnin Binder much easier.
|
||||
|
||||
### Local Sqlite database ###
|
||||
|
||||
|
@ -36,11 +32,6 @@ docker run jforman/binder:latest
|
|||
|
||||
Default admin user for Binder is 'admin', and password is 'admin' as well.
|
||||
|
||||
If the default admin user doesn't exist in your database, create one:
|
||||
```shell script
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
### MySQL database ###
|
||||
|
||||
If you wish to use a MySQL database, the following structure works:
|
||||
|
@ -111,18 +102,6 @@ If you wish to use a statically configured encryption/decryption key, one must p
|
|||
Aside from the Binder application itself, other infrastructure is required
|
||||
to make Binder useful.
|
||||
|
||||
### NSD DNS Server ###
|
||||
|
||||
If you wish to access an NSD DNS server, the credentials are expected to be found in `/creds` creds directory,
|
||||
where each subdirectory matches the configured hostname.
|
||||
|
||||
For example, for NSD host ns1.university.edu, the NSD remote control certificates would be found at the following paths:
|
||||
|
||||
```
|
||||
/creds/ns1.university.edu/nsd_control.key
|
||||
/creds/ns1.university.edu/nsd_control.pem
|
||||
```
|
||||
|
||||
### BIND DNS Server ###
|
||||
|
||||
When Binder accesses your BIND DNS server, it first queries the statistics port to gather zone information. This includes zone name, view, and serial number.
|
||||
|
|
|
@ -5,8 +5,27 @@ from django.contrib import admin
|
|||
from django.forms import ModelForm, ValidationError
|
||||
|
||||
|
||||
class BindServerAdminForm(ModelForm):
|
||||
def clean_statistics_port(self):
|
||||
port = self.cleaned_data["statistics_port"]
|
||||
if port < 1 or port > 65535:
|
||||
raise ValidationError("Invalid port number %(port)s. Please enter "
|
||||
"a valid one between 1 and 65535.",
|
||||
params={'port': port})
|
||||
return self.cleaned_data["statistics_port"]
|
||||
|
||||
def clean_dns_port(self):
|
||||
port = self.cleaned_data["dns_port"]
|
||||
if port < 1 or port > 65535:
|
||||
raise ValidationError("Invalid port number %(port)s. Please enter "
|
||||
"a valid one between 1 and 65535.",
|
||||
params={'port': port})
|
||||
return self.cleaned_data["dns_port"]
|
||||
|
||||
|
||||
class BindServerAdmin(admin.ModelAdmin):
|
||||
list_display = ['hostname', 'server_type', 'control_port', 'default_transfer_key']
|
||||
form = BindServerAdminForm
|
||||
list_display = ['hostname', 'statistics_port', 'default_transfer_key']
|
||||
|
||||
|
||||
class KeyAdminForm(ModelForm):
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
# NSD Backend Handler Class
|
||||
|
||||
import os
|
||||
from django.conf import settings
|
||||
from jinja2 import Template
|
||||
import re
|
||||
import subprocess
|
||||
from binder import helpers
|
||||
|
||||
NSD_CONF_TEMPLATE = """
|
||||
# nsd.conf for {{hostname}}
|
||||
remote-control:
|
||||
control-enable: yes
|
||||
control-key-file: {{creds_dir}}/{{hostname}}/nsd_control.key
|
||||
control-cert-file: {{creds_dir}}/{{hostname}}/nsd_control.pem
|
||||
server-cert-file: {{creds_dir}}/{{hostname}}/nsd_server.pem
|
||||
|
||||
"""
|
||||
|
||||
ZONE_RE = re.compile("""zone:\s+(?P<zone_name>\S+)\s+state:\s+master""")
|
||||
|
||||
class NSDServer(object):
|
||||
"""Class to manage NSD backend server data."""
|
||||
|
||||
def __init__(self, hostname, control_port):
|
||||
self.hostname = hostname
|
||||
self.control_port = control_port
|
||||
|
||||
def get_creds_dir(self):
|
||||
return os.path.join(settings.CREDS_DIR,
|
||||
self.hostname)
|
||||
|
||||
|
||||
def get_config_path(self):
|
||||
return os.path.join(
|
||||
os.path.join(self.get_creds_dir()),
|
||||
'nsd.conf')
|
||||
|
||||
def write_config(self):
|
||||
if not os.path.exists(self.get_creds_dir()):
|
||||
os.makedirs(self.get_creds_dir())
|
||||
|
||||
with open(self.get_config_path(), 'w') as f:
|
||||
template = Template(NSD_CONF_TEMPLATE)
|
||||
conf = template.render(
|
||||
creds_dir=settings.CREDS_DIR,
|
||||
hostname=self.hostname)
|
||||
f.write(conf)
|
||||
|
||||
def get_zone_list(self):
|
||||
try:
|
||||
zs_out = subprocess.check_output(
|
||||
["/usr/sbin/nsd-control",
|
||||
"-c", self.get_config_path(),
|
||||
"-s", helpers.ip_address(self.hostname),
|
||||
"zonestatus"],
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-8')
|
||||
except subprocess.CalledProcessError:
|
||||
raise
|
||||
zones = ZONE_RE.findall(zs_out)
|
||||
zone_data = {}
|
||||
zone_data['stats'] = {}
|
||||
zone_data['stats']['zone_stats'] = {}
|
||||
|
||||
for zone in zones:
|
||||
zone_data['stats']['zone_stats'][zone] = {}
|
||||
zone_data['stats']['zone_stats'][zone]["no_view"] = {}
|
||||
zone_data['stats']['zone_stats'][zone]["no_view"]["serial"] = "n/a"
|
||||
return zone_data
|
|
@ -175,20 +175,6 @@ def ip_info(host_name):
|
|||
|
||||
return info
|
||||
|
||||
def ip_address(host_name):
|
||||
"""Find an IP address for a host.
|
||||
"""
|
||||
try:
|
||||
for s_family, s_type, s_proto, s_cannoname, s_sockaddr in socket.getaddrinfo(host_name, None):
|
||||
if s_family == 2 and s_type == 1:
|
||||
return s_sockaddr[0]
|
||||
if s_family == 10 and s_type == 1:
|
||||
return s_sockaddr[0]
|
||||
except (socket.gaierror, err):
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def send_dns_update(dns_message, dns_server, port, key_name):
|
||||
"""Send DNS message to server and return response.
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# Generated by Django 2.1.7 on 2019-03-18 00:13
|
||||
# Generated by Django 2.1.7 on 2019-02-14 01:37
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
@ -18,9 +17,8 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('hostname', models.CharField(help_text='Host name or IP address of the BIND server.', max_length=255, unique=True)),
|
||||
('dns_port', models.IntegerField(default=53, help_text='The port where the DNS server is listening for DNS requests. binder especially uses that port for the dynamic zone updates. In most cases you should always leave it at the default port 53.', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65535)], verbose_name='DNS port')),
|
||||
('server_type', models.CharField(choices=[('BIND', 'Bind'), ('NSD', 'NSD')], default='BIND', help_text='DNS Server Type.', max_length=20)),
|
||||
('control_port', models.IntegerField(default=0, help_text='Port where the DNS server accepts remote statistic or control commands. 8053 for BIND, 8952 for NSD.', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65535)])),
|
||||
('dns_port', models.IntegerField(default=53, help_text='The port where the BIND server is listening for DNS requests. binder especially uses that port for the dynamic zone updates. In most cases you should always leave it at the default port 53.', verbose_name='DNS port')),
|
||||
('statistics_port', models.IntegerField(help_text='Port where the BIND server is serving statistics on.')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['hostname'],
|
||||
|
@ -31,7 +29,7 @@ class Migration(migrations.Migration):
|
|||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='A human readable name for the key to store, used for further references to the key.', max_length=255, unique=True)),
|
||||
('data', models.CharField(help_text='The TSIG key.', max_length=255)),
|
||||
('data', models.CharField(help_text='The private part of the TSIG key.', max_length=255)),
|
||||
('algorithm', models.CharField(choices=[('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'), ('hmac-sha1', 'SHA1'), ('hmac-sha256', 'SHA256'), ('hmac-sha384', 'SHA384'), ('hmac-sha512', 'SHA512')], help_text='The algorithm which has been used for the key.', max_length=255)),
|
||||
],
|
||||
options={
|
||||
|
@ -41,6 +39,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='bindserver',
|
||||
name='default_transfer_key',
|
||||
field=models.ForeignKey(help_text='The default key to use for all actions with this DNS server.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='binder.Key'),
|
||||
field=models.ForeignKey(blank=True, help_text='The default key to use for all actions with this DNS server as long as no other key is specified explicitly.', null=True, on_delete=django.db.models.deletion.CASCADE, to='binder.Key'),
|
||||
),
|
||||
]
|
||||
|
|
103
binder/models.py
103
binder/models.py
|
@ -1,7 +1,6 @@
|
|||
### Binder Models
|
||||
|
||||
# Standard Imports
|
||||
import base64
|
||||
import binascii
|
||||
import socket
|
||||
|
||||
|
@ -11,17 +10,12 @@ from pybindxml import reader as bindreader
|
|||
import dns.exception
|
||||
import dns.query
|
||||
import dns.tsig
|
||||
import dns.tsigkeyring
|
||||
import dns.zone
|
||||
|
||||
# App Imports
|
||||
from binder import exceptions
|
||||
from binder.backends import nsd
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from binder import helpers
|
||||
|
||||
|
||||
TSIG_ALGORITHMS = (('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'),
|
||||
('hmac-sha1', 'SHA1'),
|
||||
|
@ -29,11 +23,6 @@ TSIG_ALGORITHMS = (('HMAC-MD5.SIG-ALG.REG.INT', 'MD5'),
|
|||
('hmac-sha384', 'SHA384'),
|
||||
('hmac-sha512', 'SHA512'))
|
||||
|
||||
DNS_BACKENDS = (
|
||||
('BIND', 'Bind'),
|
||||
('NSD', 'NSD'),
|
||||
)
|
||||
|
||||
|
||||
class Key(models.Model):
|
||||
|
||||
|
@ -44,7 +33,7 @@ class Key(models.Model):
|
|||
help_text="A human readable name for the key to "
|
||||
"store, used for further references to the key.")
|
||||
data = models.CharField(max_length=255,
|
||||
help_text="The TSIG key.")
|
||||
help_text="The private part of the TSIG key.")
|
||||
algorithm = models.CharField(max_length=255,
|
||||
choices=TSIG_ALGORITHMS,
|
||||
help_text="The algorithm which has been used "
|
||||
|
@ -53,26 +42,23 @@ class Key(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
app_label = 'binder'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
f = Fernet(settings.FERNET_KEY)
|
||||
encrypted_text = f.encrypt(bytes(self.data, 'utf-8'))
|
||||
self.data = encrypted_text
|
||||
crypted_key = f.encrypt(bytes(self.data, encoding="utf8"))
|
||||
self.data = crypted_key
|
||||
super(Key, self).save(*args, **kwargs)
|
||||
|
||||
def create_keyring(self):
|
||||
if self.name is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
key_data = self.decrypt_keydata()
|
||||
keyring = dns.tsigkeyring.from_text({self.name: key_data})
|
||||
except binascii.Error as err:
|
||||
except (binascii.Error, err):
|
||||
raise exceptions.KeyringException("Incorrect key data. Verify key: %s. Reason: %s" % (self.name, err))
|
||||
|
||||
return keyring
|
||||
|
@ -82,21 +68,18 @@ class Key(models.Model):
|
|||
fernet_key=key
|
||||
else:
|
||||
fernet_key=settings.FERNET_KEY
|
||||
|
||||
try:
|
||||
f = Fernet(fernet_key)
|
||||
# self.data is returned as a string. so we need to re-convert it
|
||||
# to a byte string with just the key, then decrypt it.
|
||||
decrypted_key = f.decrypt(bytes(self.data[2:-1], 'utf-8'))
|
||||
except InvalidToken as err:
|
||||
decrypted_key = f.decrypt(bytes(self.data))
|
||||
except InvalidToken:
|
||||
raise exceptions.KeyringException()
|
||||
|
||||
return str(decrypted_key)[2:-1]
|
||||
return decrypted_key
|
||||
|
||||
|
||||
class BindServer(models.Model):
|
||||
|
||||
"""Store DNS servers and attributes for referencing their control ports.
|
||||
"""Store DNS servers and attributes for referencing their statistics ports.
|
||||
|
||||
Also reference FK for TSIG transfer keys, if required.
|
||||
"""
|
||||
|
@ -104,38 +87,21 @@ class BindServer(models.Model):
|
|||
hostname = models.CharField(max_length=255,
|
||||
unique=True,
|
||||
help_text="Host name or IP address of the BIND server.")
|
||||
|
||||
dns_port = models.IntegerField(default=53,
|
||||
verbose_name="DNS port",
|
||||
validators=[
|
||||
MinValueValidator(1),
|
||||
MaxValueValidator(65535),
|
||||
],
|
||||
help_text="The port where the DNS server is listening for DNS "
|
||||
help_text="The port where the BIND server is listening for DNS "
|
||||
"requests. binder especially uses that port for the dynamic "
|
||||
"zone updates. In most cases you should always leave it at the "
|
||||
"default port 53.")
|
||||
|
||||
server_type = models.CharField(
|
||||
help_text="DNS Server Type.",
|
||||
choices=DNS_BACKENDS,
|
||||
default='BIND',
|
||||
max_length=20)
|
||||
|
||||
control_port = models.IntegerField(
|
||||
default=0,
|
||||
validators=[
|
||||
MinValueValidator(1),
|
||||
MaxValueValidator(65535),
|
||||
],
|
||||
help_text="Port where the DNS server accepts remote statistic or control commands. "
|
||||
"8053 for BIND, 8952 for NSD.")
|
||||
|
||||
statistics_port = models.IntegerField(help_text="Port where the BIND server is serving "
|
||||
"statistics on.")
|
||||
default_transfer_key = models.ForeignKey(Key,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
help_text="The default key to use for all actions "
|
||||
"with this DNS server.")
|
||||
"with this DNS server as long as no other key is "
|
||||
"specified explicitly.")
|
||||
|
||||
def __unicode__(self):
|
||||
return self.hostname
|
||||
|
@ -145,14 +111,6 @@ class BindServer(models.Model):
|
|||
|
||||
class Meta:
|
||||
ordering = ["hostname"]
|
||||
app_label = 'binder'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.server_type == 'NSD':
|
||||
server = nsd.NSDServer(hostname=self.hostname, control_port=self.control_port)
|
||||
server.write_config()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
def list_zones(self):
|
||||
"""List the DNS zones and attributes.
|
||||
|
@ -165,18 +123,9 @@ class BindServer(models.Model):
|
|||
String zone_class,
|
||||
String zone_serial }
|
||||
"""
|
||||
if self.server_type == "BIND":
|
||||
# TODO: just return stats from get_stats call. This is probably not used
|
||||
# anywhere else.
|
||||
zone_data = bindreader.BindXmlReader(host=self.hostname, port=self.control_port)
|
||||
zone_data.get_stats()
|
||||
return zone_data
|
||||
elif self.server_type == "NSD":
|
||||
zone_data = nsd.NSDServer(hostname=self.hostname,
|
||||
control_port=self.control_port)
|
||||
zone_dict = zone_data.get_zone_list()
|
||||
return zone_dict
|
||||
|
||||
zone_data = bindreader.BindXmlReader(host=self.hostname, port=self.statistics_port)
|
||||
zone_data.get_stats()
|
||||
return zone_data
|
||||
|
||||
def list_zone_records(self, zone_name):
|
||||
"""List all records in a specific zone.
|
||||
|
@ -199,13 +148,11 @@ class BindServer(models.Model):
|
|||
algorithm = transfer_key.algorithm
|
||||
|
||||
try:
|
||||
xfr = dns.query.xfr(
|
||||
helpers.ip_address(self.hostname),
|
||||
zone_name,
|
||||
port=self.dns_port,
|
||||
keyring=keyring,
|
||||
keyalgorithm=algorithm)
|
||||
zone = dns.zone.from_xfr(xfr)
|
||||
zone = dns.zone.from_xfr(dns.query.xfr(self.hostname,
|
||||
zone_name,
|
||||
port=self.dns_port,
|
||||
keyring=keyring,
|
||||
keyalgorithm=algorithm))
|
||||
except dns.tsig.PeerBadKey:
|
||||
# The incorrect TSIG key was selected for transfers.
|
||||
raise exceptions.TransferException("Unable to list zone records because of a TSIG key mismatch.")
|
||||
|
@ -218,7 +165,7 @@ class BindServer(models.Model):
|
|||
raise exceptions.TransferException("Unable to perform AXFR to list zone records. Did you forget to specify a default transfer key?")
|
||||
|
||||
names = zone.nodes.keys()
|
||||
sorted(names)
|
||||
names.sort()
|
||||
record_array = []
|
||||
for current_name in names:
|
||||
current_record = zone[current_name].to_text(current_name)
|
||||
|
|
|
@ -180,9 +180,3 @@ try:
|
|||
from local_settings import *
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Base directory where credentials are to be stored.
|
||||
# For NSD, a subdirectory under CREDS_DIR should be created with th e
|
||||
# appropriate certificates for nsd-control to execute.
|
||||
CREDS_DIR = "/creds"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load static %}
|
||||
{% load static from staticfiles %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% block header %}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<th>View</th>
|
||||
<th>Serial Number</th>
|
||||
</tr>
|
||||
{% for current_zone, cz_data in zone_array.stats.zone_stats.items %}
|
||||
{% for current_view, cv_data in cz_data.items %}
|
||||
{% for current_zone, cz_data in zone_array.stats.zone_stats.iteritems %}
|
||||
{% for current_view, cv_data in cz_data.iteritems %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "zone_list" dns_server=dns_server.hostname zone_name=current_zone %}"> {{ current_zone }}</a>
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
<tr>
|
||||
<th>Hostname</th>
|
||||
<th>IP Address</th>
|
||||
<th>Server Type</th>
|
||||
</tr>
|
||||
{% for current_server in server_info %}
|
||||
<tr>
|
||||
|
@ -16,7 +15,6 @@
|
|||
{{type}}: {{data}}<br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td> {{ current_server.server_type}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
<td>{{ current_record.rr_data }}</td>
|
||||
<td>
|
||||
<div class="btn-toolbar" style="margin: 0;">
|
||||
{% if dynamic_dns_available %}
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
Record Actions <span class="caret"></span>
|
||||
|
@ -39,9 +38,6 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
Actions not available.
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load static %}
|
||||
{% load static from staticfiles %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
@ -9,11 +9,11 @@ class Form_Tests(TestCase):
|
|||
def setUp(self):
|
||||
self.ns1_key = Key.objects.create(name='test1', data='testdata1234', algorithm='MD5')
|
||||
self.ns1 = BindServer.objects.create(hostname='ns1.test.net',
|
||||
control_port=8053,
|
||||
statistics_port=8053,
|
||||
default_transfer_key=self.ns1_key)
|
||||
self.ns2_key = Key.objects.create(name='test2', data='testdata1234', algorithm='MD5')
|
||||
self.ns2 = BindServer.objects.create(hostname='ns2.test.net',
|
||||
control_port=8053,
|
||||
statistics_port=8053,
|
||||
default_transfer_key=self.ns2_key)
|
||||
|
||||
def test_Valid_FormAddRecordWithoutReverseRecord(self):
|
||||
|
|
|
@ -12,17 +12,24 @@ class Model_BindServer_Tests(TestCase):
|
|||
"""Test that adding a well-formed BindServer works."""
|
||||
self.assertEqual(models.BindServer.objects.count(), 0)
|
||||
bindserver_1 = models.BindServer(hostname="test1",
|
||||
control_port=1234)
|
||||
statistics_port=1234)
|
||||
bindserver_1.save()
|
||||
self.assertEqual(models.BindServer.objects.count(), 1)
|
||||
|
||||
def test_BindServerNonIntControlPort(self):
|
||||
"""Attempt to add a Bindserver with a non-integer control port."""
|
||||
def test_BindServerMissingStatisticsPort(self):
|
||||
"""Attempt to add a BindServer without a statistics port."""
|
||||
bindserver_1 = models.BindServer(hostname="badtest1")
|
||||
with self.assertRaises(IntegrityError):
|
||||
bindserver_1.save()
|
||||
|
||||
def test_BindServerNonIntStatisticsPort(self):
|
||||
"""Attempt to add a Bindserver with a non-integer statistics port."""
|
||||
bindserver_1 = models.BindServer(hostname="foo",
|
||||
control_port="bar1")
|
||||
statistics_port="bar1")
|
||||
with self.assertRaisesMessage(ValueError, "invalid literal for int() with base 10: 'bar1'"):
|
||||
bindserver_1.save()
|
||||
|
||||
|
||||
class Model_Key_Tests(TestCase):
|
||||
def test_KeyModel(self):
|
||||
"""Test that adding a well-formed Key works."""
|
||||
|
|
|
@ -42,7 +42,7 @@ class PostTests(TestCase):
|
|||
def setUp(self):
|
||||
self.client = Client()
|
||||
models.BindServer(hostname="testserver.test.net",
|
||||
control_port=1234).save()
|
||||
statistics_port=1234).save()
|
||||
|
||||
user = User.objects.create_user('testuser',
|
||||
'testuser@example.com',
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
# Binder Views
|
||||
|
||||
import subprocess
|
||||
# Binder VIews
|
||||
|
||||
# 3rd Party
|
||||
import dns.query
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
|
||||
# App Imports
|
||||
from binder import forms, helpers, models
|
||||
from binder import exceptions
|
||||
from binder.exceptions import KeyringException, RecordException, TransferException, ZoneException
|
||||
|
||||
def home_index(request):
|
||||
"""List the main index page for Binder."""
|
||||
|
@ -22,8 +19,7 @@ def view_server_list(request):
|
|||
server_info = []
|
||||
for current in server_list:
|
||||
server_info.append({"host_name": current,
|
||||
"ip_address": helpers.ip_info(current.hostname),
|
||||
"server_type": current.server_type})
|
||||
"ip_address": helpers.ip_info(current.hostname)})
|
||||
|
||||
return render(request, "bcommon/list_servers.html",
|
||||
{"server_info": server_info})
|
||||
|
@ -37,10 +33,8 @@ def view_server_zones(request, dns_server):
|
|||
|
||||
try:
|
||||
zone_array = this_server.list_zones()
|
||||
except exceptions.ZoneException as exc:
|
||||
except ZoneException as exc:
|
||||
messages.error(request, "Unable to list server zones. Error: %s" % exc)
|
||||
except subprocess.CalledProcessError as err:
|
||||
messages.error(request, "Error in retrieving zones: %s." % str(err.output))
|
||||
|
||||
return render(request, "bcommon/list_server_zones.html",
|
||||
{"dns_server": this_server,
|
||||
|
@ -55,31 +49,22 @@ def view_zone_records(request, dns_server, zone_name):
|
|||
|
||||
try:
|
||||
zone_array = this_server.list_zone_records(zone_name)
|
||||
except exceptions.TransferException as exc:
|
||||
messages.error(request, "TransferException: %s." % exc)
|
||||
except TransferException as exc:
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{"zone_name": zone_name,
|
||||
"dns_server": this_server})
|
||||
except exceptions.KeyringException:
|
||||
except KeyringException:
|
||||
messages.error(request, "Unable to get zone list. A problem was encountered "
|
||||
"decrypting your TSIG key. Ensure the key is correctly "
|
||||
"specified in the Binder Database.")
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{ "dns_server": this_server,
|
||||
"zone_name" :zone_name })
|
||||
except dns.query.TransferError as err:
|
||||
messages.error(request, "TransferError: %s." % err)
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{"zone_name": zone_name,
|
||||
"dns_server": this_server})
|
||||
|
||||
return render(request, "bcommon/list_zone.html",
|
||||
{"zone_array": zone_array,
|
||||
"dns_server": this_server,
|
||||
"zone_name": zone_name,
|
||||
# NOTE: A hack because NSD doesn't support dynamic updates
|
||||
# so merely display the zone.
|
||||
"dynamic_dns_available": this_server.server_type in ['BIND']})
|
||||
"zone_name": zone_name})
|
||||
|
||||
|
||||
def view_add_record(request, dns_server, zone_name):
|
||||
|
@ -101,8 +86,7 @@ def view_add_record(request, dns_server, zone_name):
|
|||
form_cleaned["ttl"],
|
||||
form_cleaned["key_name"],
|
||||
form_cleaned["create_reverse"])
|
||||
except (exceptions.KeyringException,
|
||||
exceptions.RecordException) as exc:
|
||||
except (KeyringException, RecordException) as exc:
|
||||
messages.error(request, "Adding %s.%s failed: %s" %
|
||||
(form_cleaned["record_name"], zone_name, exc))
|
||||
else:
|
||||
|
@ -146,8 +130,7 @@ def view_edit_record(request, dns_server, zone_name, record_name=None,
|
|||
form_cleaned["ttl"],
|
||||
form_cleaned["key_name"],
|
||||
form_cleaned["create_reverse"])
|
||||
except (exceptions.KeyringException,
|
||||
exceptions.RecordException) as exc:
|
||||
except (KeyringException, RecordException) as exc:
|
||||
messages.error(request, "Modifying %s.%s failed: %s" %
|
||||
(form_cleaned["record_name"], zone_name, exc))
|
||||
else:
|
||||
|
@ -190,8 +173,7 @@ def view_add_cname_record(request, dns_server, zone_name, record_name):
|
|||
str(form_cleaned["zone_name"])),
|
||||
form_cleaned["ttl"],
|
||||
form_cleaned["key_name"])
|
||||
except (exceptions.KeyringException,
|
||||
exceptions.RecordException) as exc:
|
||||
except (KeyringException, RecordException) as exc:
|
||||
messages.error(request, "Adding %s.%s failed: %s" %
|
||||
(form_cleaned["cname"], zone_name, exc))
|
||||
else:
|
||||
|
@ -233,7 +215,7 @@ def view_delete_record(request, dns_server, zone_name):
|
|||
response = helpers.delete_record(form_cleaned["dns_server"],
|
||||
rr_list,
|
||||
form_cleaned["key_name"])
|
||||
except exceptions.KeyringException as exc:
|
||||
except KeyringException as exc:
|
||||
for record in rr_list:
|
||||
messages.error(request, "Deleting %s.%s failed: %s" %
|
||||
(record, zone_name, exc))
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
cryptography
|
||||
Django
|
||||
django-mysql
|
||||
dnspython>=1.16.0
|
||||
Jinja2
|
||||
lxml
|
||||
cryptography
|
||||
dnspython>=1.11
|
||||
pybindxml>=0.7
|
||||
mysqlclient
|
||||
lxml
|
||||
|
|
Loading…
Reference in New Issue