Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #166 : DNS Support #398

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5ea721a
Fixes #166: Full DNS support
Jul 20, 2016
2d39984
Add help texts in DNS forms
Jul 20, 2016
4046d69
In DNS : change search/filters & add bind exports
Jul 21, 2016
6230ae1
Add bind exports in dns api
Jul 21, 2016
efe6188
Add fields to dns models
Jul 21, 2016
117bf1a
Remove category field in record & add auto-updating serial in zone
Jul 22, 2016
02e142e
Fix zone serial updating on records bulk delete
Jul 22, 2016
90ea564
Add reverse DNS
Jul 22, 2016
0a39945
Fix bug in ipv6 reverse dns
Jul 22, 2016
d6fd35d
Fix bug in ipv6 reverse dns (bis)
Jul 22, 2016
7df6353
Fix bug in ipv6 reverse dns (ter)
Jul 22, 2016
2478b35
Modify reverse bind export
Jul 26, 2016
89a0049
Reverse last commit for ipv6 reverse dns
Jul 26, 2016
5282eee
Don't handle ip without host names in reverse DNS
Jul 26, 2016
03a65f1
Fix bug on "select" link in bind export in prefix page
Jul 26, 2016
d4ca076
Remove a debug residue in prefix.html
Jul 26, 2016
e9d6c92
Fix problem of reverse export with prefixes included in bigger ones
Jul 26, 2016
fd98c0c
Add full reverse dns view
Jul 26, 2016
f225efa
Improve records table on zone page
Jul 27, 2016
c11cade
Make records searchable by ip address
Jul 27, 2016
705690e
Add missing reverse DNS fields to prefix forms and to CSV exporting
Jul 27, 2016
ee78bf5
Rename field in IPAddr : "hostname" to "ptr"
Jul 27, 2016
ff5d770
Add auto A/AAAA creating when ptr modified & fix bug w/ prefix serial
Jul 27, 2016
1443bfd
Auto-create missing IPs on record creating/editing/importing
Jul 28, 2016
f462d0c
Fix problem with serials
Jul 28, 2016
955910b
Fix bug on reverse DNS fields on prefix page
Jul 28, 2016
4080c16
Move bind exports to specific views with download links
Jul 28, 2016
bcdb192
Put circuits panel into dcim column on home page
Jul 28, 2016
107d898
Adjust home page spacings (due to putting circuits into dcim column)
Jul 28, 2016
ec36cba
Add export views links to dns panel on home page
Jul 28, 2016
033a54a
Update APIs (ipam & dns)
Jul 28, 2016
b176fd7
Update DNS doc
Jul 28, 2016
254d022
Update DNS doc (bis)
Jul 28, 2016
cb33881
Fix display bug (overflow) on BIND exports pages
Jul 28, 2016
0cf2abb
Update fixtures
Jul 28, 2016
9c094f1
DNS support
Jul 28, 2016
7dafbae
Merge remote-tracking branch 'rdujardin/develop' into develop
Aug 3, 2016
50dc519
Merge migrations (were conflicting)
Aug 3, 2016
f873848
Fix to pass PEP8 check and tests
Aug 3, 2016
d14b0ec
Autocreate A/AAAA records if PTR is given a FQDN
Aug 8, 2016
0fc5a25
Fix two bugs with A/AAAA auto-creating
Aug 8, 2016
75437eb
Merge remote-tracking branch 'digitalocean/develop' into develop
Aug 8, 2016
339fcdc
Fix bug with prefixes CSV exporting
Aug 12, 2016
c86a74e
Add 'extra_conf' field to DNS zones and reverse DNS zones (i.e. prefi…
Aug 18, 2016
bc02fa4
Merge remote-tracking branch 'do/develop' into develop
Aug 19, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/data-model/dns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
The DNS component of NetBox deals with the management of DNS zones.

# Zones

A zone corresponds to a zone file in a DNS server, it stores the SOA (Start Of Authority) record and other records that are stored as Record objects.

Zone objects handle only forward DNS, reverse DNS is handled by Prefixes (in IPAM section), which also store a SOA record.

Netbox provides two views in the DNS menu to get the exports in BIND format, which is compatible with every DNS server, directly or by import. Those
exports are also accessible as JSON through the REST API. One of these views is the export of all the forward zones in the database,
the second is the export of all the reverse zones.

The reverse zones are correctly merged and/or divided to meet the requirements of a DNS server (for instance, IPv4 reverse zones must be /16 or /24), and
not to duplicate records (for instance if you have in database the prefixes 192.168.0.0/16 and 192.168.1.0/24, only the biggest will be exported) ; however,
only IP addresses which are in an active prefix will be taken into account. Obviously, reverse DNS is supported for both IPv4 and IPv6.

The SOA Serial field is not editable : it's automatically created and managed by Netbox. Each time a zone (forward or reverse) is exported,
if there are changes since the last export or if it's the first export, the serial will be incremented. It's in the following format :
YYYYMMDDNN with Y the year, M the month, D the day and N a two-digit counter.

As zones and their BIND exports are readable through the REST API, it is possible to write some external script to automatically update
your DNS server configuration from Netbox's database.

---

# Record

Each Record object represents a DNS record, i.e. a link between a hostname and a resource, which can be either an IP address or a text value,
for instance another name if the record is of CNAME type.

Records must be linked to an existing zone, and hold either an IP address link or a text value. The "Address" field points to an IP address
in database, but if you want to put an IP in your record but not in your database (if you don't own the IP for instance), it's possible
by putting the IP as text value instead.

You can create, edit or import records with IPs not existing yet in the database. They will be automatically created (but not the prefixes !).
However, the zones must be created first, they won't be so automatically.

Reverse DNS is not supported by Record objects, but by the "PTR" field in IP addresses. If this field is modified and not empty, a corresponding
A/AAAA record is automatically created if the corresponding zone is found in the database. Be careful, if there was A/AAAA records
for the old PTR value, they are not deleted.

1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
NetBox is an open source web application designed to help manage and document computer networks. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers. It encompasses the following aspects of network management:

* **IP address management (IPAM)** - IP networks and addresses, VRFs, and VLANs
* **DNS management** - DNS zones and records
* **Equipment racks** - Organized by group and site
* **Devices** - Types of devices and where they are installed
* **Connections** - Network, console, and power connections among devices
Expand Down
1 change: 1 addition & 0 deletions netbox/dns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'dns.apps.DNSConfig'
18 changes: 18 additions & 0 deletions netbox/dns/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib import admin

from .models import (
Zone, Record,
)


@admin.register(Zone)
class ZoneAdmin(admin.ModelAdmin):
list_display = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial']
prepopulated_fields = {
'soa_name': ['name'],
}


@admin.register(Record)
class RecordAdmin(admin.ModelAdmin):
list_display = ['name', 'zone', 'record_type', 'priority', 'address', 'value']
Empty file added netbox/dns/api/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions netbox/dns/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from rest_framework import serializers

from ipam.api.serializers import IPAddressNestedSerializer
from dns.models import Zone, Record

#
# Zones
#


class ZoneSerializer(serializers.ModelSerializer):

class Meta:
model = Zone
fields = ['id', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum', 'extra_conf', 'description']


class ZoneNestedSerializer(ZoneSerializer):

class Meta(ZoneSerializer.Meta):
fields = ['id', 'name']


#
# Records
#


class RecordSerializer(serializers.ModelSerializer):

zone = ZoneNestedSerializer()
address = IPAddressNestedSerializer()

class Meta:
model = Record
fields = ['id', 'name', 'record_type', 'priority', 'zone', 'address', 'value', 'description']


class RecordNestedSerializer(RecordSerializer):

class Meta(RecordSerializer.Meta):
fields = ['id', 'name', 'record_type', 'zone']
19 changes: 19 additions & 0 deletions netbox/dns/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.conf.urls import url

from .views import *

urlpatterns = [

# Zones
url(r'^zones/$', ZoneListView.as_view(), name='zone_list'),
url(r'^zones/(?P<pk>\d+)/$', ZoneDetailView.as_view(), name='zone_detail'),

# Records
url(r'^records/$', RecordListView.as_view(), name='record_list'),
url(r'^records/(?P<pk>\d+)/$', RecordDetailView.as_view(), name='record_detail'),

# BIND Exports
url(r'^bind/forward/$', bind_forward, name='bind_forward'),
url(r'^bind/reverse/$', bind_reverse, name='bind_reverse'),

]
76 changes: 76 additions & 0 deletions netbox/dns/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from rest_framework import generics
from django.http import HttpResponse

from rest_framework.decorators import api_view
from rest_framework.response import Response

from ipam.models import IPAddress
from dns.models import Zone, Record, export_bind_forward, export_bind_reverse
from dns import filters

from . import serializers

#
# Zones
#


class ZoneListView(generics.ListAPIView):
"""
List all zones
"""
queryset = Zone.objects.all()
serializer_class = serializers.ZoneSerializer
filter_class = filters.ZoneFilter


class ZoneDetailView(generics.RetrieveAPIView):
"""
Retrieve a single zone
"""
queryset = Zone.objects.all()
serializer_class = serializers.ZoneSerializer


#
# Records
#


class RecordListView(generics.ListAPIView):
"""
List all records
"""
queryset = Record.objects.all()
serializer_class = serializers.RecordSerializer


class RecordDetailView(generics.RetrieveAPIView):
"""
Retrieve a single record
"""
queryset = Record.objects.all()
serializer_class = serializers.RecordSerializer


#
# BIND Exports
#


@api_view(['GET'])
def bind_forward(request):
"""
Full export of forward zones in BIND format
"""
zones_list = export_bind_forward()
return Response(zones_list)


@api_view(['GET'])
def bind_reverse(request):
"""
Full export of reverse zones in BIND format
"""
zones_list = export_bind_reverse()
return Response(zones_list)
6 changes: 6 additions & 0 deletions netbox/dns/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class DNSConfig(AppConfig):
name = 'dns'
verbose_name = 'DNS'
53 changes: 53 additions & 0 deletions netbox/dns/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import django_filters
from django.db.models import Q

from ipam.models import IPAddress
from .models import (
Zone,
Record,
)
from .forms import record_type_choices


class ZoneFilter(django_filters.FilterSet):
name = django_filters.CharFilter(
name='name',
lookup_type='icontains',
label='Name',
)

class Meta:
model = Zone
fields = ['name']


class RecordFilter(django_filters.FilterSet):
zone__name = django_filters.ModelMultipleChoiceFilter(
name='zone__name',
to_field_name='name',
lookup_type='icontains',
queryset=Zone.objects.all(),
label='Zone (Name)',
)
record_type = django_filters.MultipleChoiceFilter(
name='record_type',
label='Type',
choices=record_type_choices
)
name = django_filters.CharFilter(
name='name',
lookup_type='icontains',
label='Name',
)
name_or_value_or_ip = django_filters.MethodFilter(
name='name_or_value_or_ip',
)

class Meta:
model = Record
field = ['name', 'record_type', 'value']

def filter_name_or_value_or_ip(self, queryset, value):
if not value:
return queryset
return queryset.filter(Q(name__icontains=value) | Q(value__icontains=value) | Q(address__address__icontains=value))
37 changes: 37 additions & 0 deletions netbox/dns/fixtures/dns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"model": "dns.zone",
"pk": 1,
"fields": {
"name": "foo.net",
"ttl": 10800,
"soa_name": "@",
"soa_contact": "ns@foo.net. noc@foo.net.",
"soa_serial": "2016070401",
"soa_refresh": 3600,
"soa_retry": 3600,
"soa_expire": 604800,
"soa_minimum": 1800
}
},
{
"model": "dns.record",
"pk": 1,
"fields": {
"name": "@",
"record_type": "NS",
"zone": 1,
"value": "ns.foo.net."
}
},
{
"model": "dns.record",
"pk": 2,
"fields": {
"name": "www",
"record_type": "A",
"zone": 1,
"address": 1
}
}
]
39 changes: 39 additions & 0 deletions netbox/dns/formfields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from netaddr import IPNetwork, AddrFormatError
import netaddr

from django import forms
from django.core.exceptions import ValidationError

from ipam.models import IPAddress, Prefix


#
# Form fields
#

class AddressFormField(forms.Field):
default_error_messages = {
'invalid': "Enter a valid IPv4 or IPv6 address (with CIDR mask).",
}

def to_python(self, value):
if not value:
return None

# Ensure that a subnet mask has been specified. This prevents IPs from defaulting to a /32 or /128.
if len(value.split('/')) != 2:
raise ValidationError('CIDR mask (e.g. /24) is required.')

try:
net = IPNetwork(value)
except AddrFormatError:
raise ValidationError("Please specify a valid IPv4 or IPv6 address.")

ip = IPAddress.objects.filter(address=value)
if not ip:
net = IPNetwork(value)
obj = IPAddress(address=net)
obj.save()
return obj
else:
return ip[0]
Loading