update to django-2, python3, and based on an alpine container
This commit is contained in:
parent
dac54a5ad7
commit
f9176387ac
11
Dockerfile
11
Dockerfile
|
@ -1,16 +1,13 @@
|
||||||
FROM python:2.7
|
FROM python:3-alpine
|
||||||
|
|
||||||
MAINTAINER Jeffrey Forman <code@jeffreyforman.net>
|
MAINTAINER Jeffrey Forman <code@jeffreyforman.net>
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
COPY . /code/
|
COPY . /code/
|
||||||
|
|
||||||
RUN pip install -r requirements.txt
|
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
|
EXPOSE 8000
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,6 @@ bootstrapping.
|
||||||
```
|
```
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
python manage.py createsuperuser
|
python manage.py createsuperuser
|
||||||
...
|
|
||||||
python manage.py dumpdata -o binder/fixtures/initial_data.json
|
python manage.py dumpdata -o binder/fixtures/initial_data.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import binascii
|
import binascii
|
||||||
import dns.tsigkeyring
|
import dns.tsigkeyring
|
||||||
from models import BindServer, Key
|
from binder.models import BindServer, Key
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.forms import ModelForm, ValidationError
|
from django.forms import ModelForm, ValidationError
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,7 @@ from django.core import validators
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
|
|
||||||
# App Imports
|
# App Imports
|
||||||
from models import Key
|
from binder.models import Key
|
||||||
|
|
||||||
|
|
||||||
class CustomUnicodeListField(forms.CharField):
|
class CustomUnicodeListField(forms.CharField):
|
||||||
|
|
|
@ -170,7 +170,7 @@ def ip_info(host_name):
|
||||||
if s_family == 10 and s_type == 1:
|
if s_family == 10 and s_type == 1:
|
||||||
ipv6_count += 1
|
ipv6_count += 1
|
||||||
info.append(["IPv6 (%d)" % ipv6_count, s_sockaddr[0]])
|
info.append(["IPv6 (%d)" % ipv6_count, s_sockaddr[0]])
|
||||||
except socket.gaierror, err:
|
except (socket.gaierror, err):
|
||||||
info.append(["Error", "Unable to resolve %s: %s" % (host_name, err)])
|
info.append(["Error", "Unable to resolve %s: %s" % (host_name, err)])
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
|
@ -2,22 +2,26 @@ from django.conf import settings
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
|
||||||
class LoginRequiredMiddleware(object):
|
class LoginRequiredMiddleware:
|
||||||
"""Middleware to redirect to the login page if the user isn't authenticated
|
"""Middleware to redirect to the login page if the user isn't authenticated
|
||||||
|
|
||||||
After successful authentication the user is redirected back to the page he
|
After successful authentication the user is redirected back to the page he
|
||||||
initially wanted to access.
|
initially wanted to access.
|
||||||
"""
|
"""
|
||||||
def process_request(self, request):
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
# allow access to the login url
|
# allow access to the login url
|
||||||
|
response = self.get_response(request)
|
||||||
if request.path == settings.LOGIN_URL:
|
if request.path == settings.LOGIN_URL:
|
||||||
return
|
return response
|
||||||
# redirect to the login url if the user isn't authenticated
|
# redirect to the login url if the user isn't authenticated
|
||||||
if not request.user.is_authenticated():
|
if not request.user.is_authenticated:
|
||||||
if request.path not in (settings.LOGIN_URL,
|
if request.path not in (settings.LOGIN_URL,
|
||||||
settings.LOGIN_REDIRECT_URL):
|
settings.LOGIN_REDIRECT_URL):
|
||||||
return HttpResponseRedirect('%s?%s=%s' % (settings.LOGIN_URL,
|
return HttpResponseRedirect('%s?%s=%s' % (settings.LOGIN_URL,
|
||||||
REDIRECT_FIELD_NAME,
|
REDIRECT_FIELD_NAME,
|
||||||
request.path))
|
request.path))
|
||||||
else:
|
|
||||||
return HttpResponseRedirect(settings.LOGIN_URL)
|
return HttpResponseRedirect(settings.LOGIN_URL)
|
||||||
|
return response
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# Generated by Django 2.1.5 on 2019-02-12 01:12
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -13,32 +15,30 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BindServer',
|
name='BindServer',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('hostname', models.CharField(help_text=b'Host name or IP address of the BIND server.', unique=True, max_length=255)),
|
('hostname', models.CharField(help_text='Host name or IP address of the BIND server.', max_length=255, unique=True)),
|
||||||
('statistics_port', models.IntegerField(help_text=b'Port where the BIND server is serving statistics on.')),
|
('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={
|
options={
|
||||||
'ordering': ['hostname'],
|
'ordering': ['hostname'],
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Key',
|
name='Key',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(help_text=b'A human readable name for the key to store, used for further references to the key.', unique=True, max_length=255)),
|
('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=b'The private part of the TSIG key.', max_length=255)),
|
('data', models.CharField(help_text='The private part of the TSIG key.', max_length=255)),
|
||||||
('algorithm', models.CharField(help_text=b'The algorithm which has been used for the key.', max_length=255, choices=[(b'HMAC-MD5.SIG-ALG.REG.INT', b'MD5'), (b'hmac-sha1', b'SHA1'), (b'hmac-sha256', b'SHA256'), (b'hmac-sha384', b'SHA384'), (b'hmac-sha512', b'SHA512')])),
|
('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={
|
options={
|
||||||
'ordering': ['name'],
|
'ordering': ['name'],
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='bindserver',
|
model_name='bindserver',
|
||||||
name='default_transfer_key',
|
name='default_transfer_key',
|
||||||
field=models.ForeignKey(blank=True, to='binder.Key', help_text=b'The default key to use for all actions with this DNS server as long as no other key is specified explicitly.', null=True),
|
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'),
|
||||||
preserve_default=True,
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('binder', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='bindserver',
|
|
||||||
name='dns_port',
|
|
||||||
field=models.IntegerField(default=53, verbose_name='DNS port', help_text=b'The port where the BIND server is listening for DNSrequests. binder especially uses that port for the dynamic zone updates. In most cases you should always leave it at the default port 53.'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -48,7 +48,7 @@ class Key(models.Model):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
f = Fernet(settings.FERNET_KEY)
|
f = Fernet(settings.FERNET_KEY)
|
||||||
crypted_key = f.encrypt(bytes(self.data))
|
crypted_key = f.encrypt(bytes(self.data, encoding="utf8"))
|
||||||
self.data = crypted_key
|
self.data = crypted_key
|
||||||
super(Key, self).save(*args, **kwargs)
|
super(Key, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class Key(models.Model):
|
||||||
try:
|
try:
|
||||||
key_data = self.decrypt_keydata()
|
key_data = self.decrypt_keydata()
|
||||||
keyring = dns.tsigkeyring.from_text({self.name: key_data})
|
keyring = dns.tsigkeyring.from_text({self.name: key_data})
|
||||||
except binascii.Error, err:
|
except (binascii.Error, err):
|
||||||
raise exceptions.KeyringException("Incorrect key data. Verify key: %s. Reason: %s" % (self.name, err))
|
raise exceptions.KeyringException("Incorrect key data. Verify key: %s. Reason: %s" % (self.name, err))
|
||||||
|
|
||||||
return keyring
|
return keyring
|
||||||
|
@ -99,6 +99,7 @@ class BindServer(models.Model):
|
||||||
default_transfer_key = models.ForeignKey(Key,
|
default_transfer_key = models.ForeignKey(Key,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
help_text="The default key to use for all actions "
|
help_text="The default key to use for all actions "
|
||||||
"with this DNS server as long as no other key is "
|
"with this DNS server as long as no other key is "
|
||||||
"specified explicitly.")
|
"specified explicitly.")
|
||||||
|
@ -153,7 +154,7 @@ class BindServer(models.Model):
|
||||||
except dns.tsig.PeerBadKey:
|
except dns.tsig.PeerBadKey:
|
||||||
# The incorrect TSIG key was selected for transfers.
|
# The incorrect TSIG key was selected for transfers.
|
||||||
raise exceptions.TransferException("Unable to list zone records because of a TSIG key mismatch.")
|
raise exceptions.TransferException("Unable to list zone records because of a TSIG key mismatch.")
|
||||||
except socket.error, err:
|
except socket.error as err:
|
||||||
# Thrown when the DNS server does not respond for a zone transfer (XFR).
|
# Thrown when the DNS server does not respond for a zone transfer (XFR).
|
||||||
raise exceptions.TransferException("DNS server did not respond for transfer. Reason: %s" % err)
|
raise exceptions.TransferException("DNS server did not respond for transfer. Reason: %s" % err)
|
||||||
except dns.exception.FormError:
|
except dns.exception.FormError:
|
||||||
|
|
|
@ -97,13 +97,14 @@ TEMPLATES = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'binder.middlewares.LoginRequiredMiddleware',
|
'binder.middlewares.LoginRequiredMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ class Model_Key_Tests(TestCase):
|
||||||
key_1.save()
|
key_1.save()
|
||||||
decrypt_key = Fernet(settings.FERNET_KEY)
|
decrypt_key = Fernet(settings.FERNET_KEY)
|
||||||
decrypted_tsig_key = decrypt_key.decrypt(bytes(key_1.data))
|
decrypted_tsig_key = decrypt_key.decrypt(bytes(key_1.data))
|
||||||
self.assertEqual(original_tsig_key, decrypted_tsig_key)
|
self.assertEqual(bytes(original_tsig_key, encoding="utf8"),
|
||||||
|
decrypted_tsig_key)
|
||||||
|
|
||||||
@override_settings(FERNET_KEY='yfE1kyYLNlpR-2ybdB-Mvs_k1ZoDMFFVtE_PpWYxVgs=')
|
@override_settings(FERNET_KEY='yfE1kyYLNlpR-2ybdB-Mvs_k1ZoDMFFVtE_PpWYxVgs=')
|
||||||
def test_FernetKeyDecryptionFailure(self):
|
def test_FernetKeyDecryptionFailure(self):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from binder import models
|
from binder import models
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class PostTests(TestCase):
|
||||||
self.assertContains(response,
|
self.assertContains(response,
|
||||||
'<input type="hidden" id="zone_name" name="zone_name" value="testzone1.test.net" />', html=True)
|
'<input type="hidden" id="zone_name" name="zone_name" value="testzone1.test.net" />', html=True)
|
||||||
self.assertContains(response,
|
self.assertContains(response,
|
||||||
'<input type="hidden" id="rr_list" name="rr_list" value="[u'testrecord1.testzone1.test.net', u'testrecord2.testzone1.test.net']"/>',
|
'<input type="hidden" id="rr_list" name="rr_list" value="['testrecord1.testzone1.test.net', 'testrecord2.testzone1.test.net']"/>',
|
||||||
html=True)
|
html=True)
|
||||||
self.assertContains(response,
|
self.assertContains(response,
|
||||||
'<input type="hidden" id="dns_server" name="dns_server" value="testserver.test.net" />',
|
'<input type="hidden" id="dns_server" name="dns_server" value="testserver.test.net" />',
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
import django.contrib.auth.views
|
from django.contrib.auth import login
|
||||||
|
from django.contrib.auth.views import logout_then_login
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
import binder.views
|
import binder.views
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', admin.site.urls),
|
||||||
|
|
||||||
url(r'^accounts/login/$', django.contrib.auth.views.login, name='login'),
|
url(r'^accounts/login/$', auth_views.LoginView.as_view(), name='login'),
|
||||||
url(r'^accounts/logout/$', django.contrib.auth.views.logout_then_login, name='logout'),
|
url(r'^accounts/logout/$', logout_then_login, name='logout'),
|
||||||
|
|
||||||
url(r'^$', binder.views.home_index, name="index"),
|
url(r'^$', binder.views.home_index, name="index"),
|
||||||
url(r'^server_list/$', binder.views.view_server_list, name="server_list"),
|
url(r'^server_list/$', binder.views.view_server_list, name="server_list"),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
docker build -t jforman/binder:latest .
|
docker build -t jforman/binder:latest .
|
||||||
docker run -it --rm -v `pwd`:/code/ -w /code/ jforman/binder:latest /bin/bash
|
docker run -it --rm -v `pwd`:/code/ -w /code/ jforman/binder:latest /bin/ash
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
Django
|
||||||
cryptography
|
cryptography
|
||||||
Django>=1.10
|
|
||||||
dnspython>=1.11
|
dnspython>=1.11
|
||||||
pybindxml>=0.7
|
pybindxml>=0.7
|
||||||
lxml
|
lxml
|
||||||
mysqlclient
|
|
||||||
|
|
Loading…
Reference in New Issue