netbox-ddns 1.4.0__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_ddns/__init__.py +3 -3
- netbox_ddns/admin.py +0 -156
- netbox_ddns/api/serializers.py +20 -2
- netbox_ddns/api/views.py +1 -0
- netbox_ddns/background_tasks.py +28 -5
- netbox_ddns/filtersets.py +23 -1
- netbox_ddns/forms.py +54 -3
- netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0014_alter_extradnsname_name.py +19 -0
- netbox_ddns/migrations/0015_server_protocol.py +18 -0
- netbox_ddns/models.py +61 -9
- netbox_ddns/navigation.py +62 -0
- netbox_ddns/tables.py +78 -32
- netbox_ddns/template_content.py +40 -3
- netbox_ddns/templates/netbox_ddns/extradnsname.html +23 -131
- netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +1 -1
- netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
- netbox_ddns/templates/netbox_ddns/reversezone.html +51 -0
- netbox_ddns/templates/netbox_ddns/server.html +74 -0
- netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +11 -0
- netbox_ddns/templates/netbox_ddns/update_zone.html +11 -0
- netbox_ddns/templates/netbox_ddns/zone.html +53 -0
- netbox_ddns/urls.py +32 -14
- netbox_ddns/utils.py +95 -0
- netbox_ddns/validators.py +6 -0
- netbox_ddns/views.py +268 -84
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/METADATA +7 -5
- netbox_ddns-1.7.0.dist-info/RECORD +50 -0
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/WHEEL +1 -1
- netbox_ddns-1.4.0.dist-info/RECORD +0 -38
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info/licenses}/LICENSE.txt +0 -0
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{% extends 'generic/object.html' %}
|
|
2
|
+
{% load render_table from django_tables2 %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col col-md-6">
|
|
7
|
+
<div class="card">
|
|
8
|
+
<h2 class="card-header">Configuration</h2>
|
|
9
|
+
<table class="table table-hover attr-table">
|
|
10
|
+
<tr>
|
|
11
|
+
<th scope="row">Server</th>
|
|
12
|
+
<td>
|
|
13
|
+
{{ object.server }}
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
<tr>
|
|
17
|
+
<th scope="row">Server port</th>
|
|
18
|
+
<td>
|
|
19
|
+
{{ object.server_port }}
|
|
20
|
+
</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th scope="row">Protocol</th>
|
|
24
|
+
<td>
|
|
25
|
+
{{ object.protocol }}
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</table>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="card">
|
|
31
|
+
<h2 class="card-header">Authentication</h2>
|
|
32
|
+
<table class="table table-hover attr-table">
|
|
33
|
+
<tr>
|
|
34
|
+
<th scope="row">Name</th>
|
|
35
|
+
<td>
|
|
36
|
+
{{ object.tsig_key_name }}
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<th scope="row">Algorithm</th>
|
|
41
|
+
<td>
|
|
42
|
+
{{ object.tsig_algorithm }}
|
|
43
|
+
</td>
|
|
44
|
+
</tr>
|
|
45
|
+
<tr>
|
|
46
|
+
<th scope="row">Key</th>
|
|
47
|
+
<td>
|
|
48
|
+
{{ object.tsig_key }}
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
</table>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="card">
|
|
54
|
+
<div class="justify-content-between card-header">
|
|
55
|
+
<h1 class="card-title">Forward Zones</h1>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="card-body table-responsive">
|
|
58
|
+
{% render_table zone_table %}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="card">
|
|
62
|
+
<div class="justify-content-between card-header">
|
|
63
|
+
<h1 class="card-title">Reverse Zones</h1>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="card-body table-responsive">
|
|
66
|
+
{% render_table reversezone_table %}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
{% include 'inc/panels/custom_fields.html' %}
|
|
70
|
+
{% include 'inc/panels/tags.html' %}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{% endblock content %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% if perms.ipam.change_ipaddress %}
|
|
2
|
+
<form method="post" class="inline-block">
|
|
3
|
+
{% csrf_token %}
|
|
4
|
+
|
|
5
|
+
<button type="submit" name="_edit"
|
|
6
|
+
formaction="{% url 'plugins:netbox_ddns:reversezone_recreate_record' pk=object.pk %}"
|
|
7
|
+
class="btn btn-secondary">
|
|
8
|
+
<span class="mdi mdi-refresh" aria-hidden="true"></span> Recreate all Records
|
|
9
|
+
</button>
|
|
10
|
+
</form>
|
|
11
|
+
{% endif %}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% if perms.ipam.change_ipaddress %}
|
|
2
|
+
<form method="post" class="inline-block">
|
|
3
|
+
{% csrf_token %}
|
|
4
|
+
|
|
5
|
+
<button type="submit" name="_edit"
|
|
6
|
+
formaction="{% url 'plugins:netbox_ddns:zone_recreate_record' pk=object.pk %}"
|
|
7
|
+
class="btn btn-secondary">
|
|
8
|
+
<span class="mdi mdi-refresh" aria-hidden="true"></span> Recreate all Records
|
|
9
|
+
</button>
|
|
10
|
+
</form>
|
|
11
|
+
{% endif %}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{% extends 'generic/object.html' %}
|
|
2
|
+
{% load render_table from django_tables2 %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col col-md-6">
|
|
7
|
+
<div class="card">
|
|
8
|
+
<h2 class="card-header">Zone</h2>
|
|
9
|
+
<table class="table table-hover attr-table">
|
|
10
|
+
<tr>
|
|
11
|
+
<th scope="row">Name</th>
|
|
12
|
+
<td>
|
|
13
|
+
{{ object.name }}
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
<tr>
|
|
17
|
+
<th scope="row">TTL</th>
|
|
18
|
+
<td>
|
|
19
|
+
{{ object.ttl }}
|
|
20
|
+
</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th scope="row">Server</th>
|
|
24
|
+
<td>
|
|
25
|
+
<a href="{{ object.server.get_absolute_url }}">
|
|
26
|
+
{{ object.server }}
|
|
27
|
+
</a>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</table>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="card">
|
|
33
|
+
<div class="justify-content-between card-header">
|
|
34
|
+
<h1 class="card-title">IP Addresses</h1>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="card-body table-responsive">
|
|
37
|
+
{% render_table ip_address_table %}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="card">
|
|
41
|
+
<div class="justify-content-between card-header">
|
|
42
|
+
<h1 class="card-title">Extra DNS Names</h1>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="card-body table-responsive">
|
|
45
|
+
{% render_table extra_dns_name_table %}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
{% include 'inc/panels/custom_fields.html' %}
|
|
49
|
+
{% include 'inc/panels/tags.html' %}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{% endblock content %}
|
netbox_ddns/urls.py
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
from django.urls import path
|
|
1
|
+
from django.urls import path, include
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from utilities.urls import get_model_urls
|
|
4
|
+
from .views import (
|
|
5
|
+
ExtraDNSNameCreateView,
|
|
6
|
+
IPAddressDNSNameRecreateView,
|
|
7
|
+
ManagedDNSNameListView,
|
|
8
|
+
UpdateForwardZone,
|
|
9
|
+
UpdateReverseZone,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
urlpatterns = [
|
|
13
|
+
path('managed-dns-names/', ManagedDNSNameListView.as_view(), name='managed_dns_name_list'),
|
|
14
|
+
|
|
15
|
+
path('extra-dns-names/', include(get_model_urls('netbox_ddns', 'extradnsname', detail=False))),
|
|
16
|
+
path('extra-dns-names/<int:pk>/', include(get_model_urls('netbox_ddns', 'extradnsname'))),
|
|
17
|
+
|
|
18
|
+
path('reverse-zones/', include(get_model_urls('netbox_ddns', 'reversezone', detail=False))),
|
|
19
|
+
path('reverse-zones/<int:pk>/', include(get_model_urls('netbox_ddns', 'reversezone'))),
|
|
20
|
+
|
|
21
|
+
path('zones/', include(get_model_urls('netbox_ddns', 'zone', detail=False))),
|
|
22
|
+
path('zones/<int:pk>/', include(get_model_urls('netbox_ddns', 'zone'))),
|
|
23
|
+
|
|
24
|
+
path('servers/', include(get_model_urls('netbox_ddns', 'server', detail=False))),
|
|
25
|
+
path('servers/<int:pk>/', include(get_model_urls('netbox_ddns', 'server'))),
|
|
26
|
+
|
|
27
|
+
path(route='zones/<int:pk>/recreate_records/',
|
|
28
|
+
view=UpdateForwardZone.as_view(),
|
|
29
|
+
name='zone_recreate_record'),
|
|
30
|
+
path(route='reverse-zones/<int:pk>/recreate_records/',
|
|
31
|
+
view=UpdateReverseZone.as_view(),
|
|
32
|
+
name='reversezone_recreate_record'),
|
|
6
33
|
path(route='ip-addresses/<int:ipaddress_pk>/recreate/',
|
|
7
34
|
view=IPAddressDNSNameRecreateView.as_view(),
|
|
8
35
|
name='ipaddress_dnsname_recreate'),
|
|
9
|
-
path(route='ip-addresses/<int:ipaddress_pk>/extra/create/',
|
|
36
|
+
path(route='ip-addresses/<int:ipaddress_pk>/extra-dns-name/create/',
|
|
10
37
|
view=ExtraDNSNameCreateView.as_view(),
|
|
11
|
-
name='
|
|
12
|
-
|
|
13
|
-
view=ExtraDNSNameEditView.as_view(),
|
|
14
|
-
name='extradnsname_edit'),
|
|
15
|
-
path(route='ip-addresses/<int:ipaddress_pk>/extra/<int:pk>/delete/',
|
|
16
|
-
view=ExtraDNSNameDeleteView.as_view(),
|
|
17
|
-
name='extradnsname_delete'),
|
|
18
|
-
|
|
19
|
-
path(route='ip-addresses/<int:ipaddress_pk>/extra/<int:pk>/',
|
|
20
|
-
view=ExtraDNSNameView.as_view(),
|
|
21
|
-
name='extradnsname'),
|
|
38
|
+
name='extradnsname_ip_address_create'),
|
|
39
|
+
|
|
22
40
|
]
|
netbox_ddns/utils.py
CHANGED
|
@@ -1,6 +1,101 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
3
|
+
|
|
1
4
|
import dns.rdatatype
|
|
2
5
|
import dns.resolver
|
|
3
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ipam.models import IPAddress
|
|
9
|
+
from netbox_ddns.models import Zone
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ManagedDNSNameRow:
|
|
14
|
+
"""Primary DNS name from IPAddress, with optional zone."""
|
|
15
|
+
dns_name: str
|
|
16
|
+
ip_address: 'IPAddress'
|
|
17
|
+
zone: Optional['Zone']
|
|
18
|
+
obj: 'IPAddress'
|
|
19
|
+
view_url: str = ''
|
|
20
|
+
edit_url: str = ''
|
|
21
|
+
delete_url: Optional[str] = None
|
|
22
|
+
forward_status_html: str = ''
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _find_zone_for_dns_name(dns_name: str, zones_by_name: dict) -> Optional['Zone']:
|
|
26
|
+
"""In-memory zone lookup."""
|
|
27
|
+
parts = dns_name.lower().rstrip('.').split('.')
|
|
28
|
+
for i in range(len(parts)):
|
|
29
|
+
candidate = '.'.join(parts[i:])
|
|
30
|
+
for suffix in (candidate + '.', candidate):
|
|
31
|
+
if suffix in zones_by_name:
|
|
32
|
+
return zones_by_name[suffix]
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_managed_dns_names(user) -> List[ManagedDNSNameRow]:
|
|
37
|
+
"""
|
|
38
|
+
Return all primary DNS names (from IPAddress.dns_name), with or without a zone.
|
|
39
|
+
Extra DNS names are shown in the separate Extra DNS Names view.
|
|
40
|
+
"""
|
|
41
|
+
from django.urls import reverse
|
|
42
|
+
|
|
43
|
+
from dns import rcode
|
|
44
|
+
|
|
45
|
+
from ipam.models import IPAddress
|
|
46
|
+
from netbox_ddns.models import ACTION_CHOICES, DNSStatus, Zone, get_rcode_display
|
|
47
|
+
|
|
48
|
+
rows: List[ManagedDNSNameRow] = []
|
|
49
|
+
no_zone_status = '<span class="text-muted">No zone configured</span>'
|
|
50
|
+
|
|
51
|
+
zones = list(Zone.objects.all().restrict(user, 'view'))
|
|
52
|
+
zones_by_name = {z.name: z for z in zones}
|
|
53
|
+
for z in zones:
|
|
54
|
+
n = z.name.rstrip('.')
|
|
55
|
+
if n != z.name:
|
|
56
|
+
zones_by_name[n] = z
|
|
57
|
+
|
|
58
|
+
ip_addresses = (
|
|
59
|
+
IPAddress.objects.filter(dns_name__isnull=False)
|
|
60
|
+
.exclude(dns_name='')
|
|
61
|
+
.restrict(user, 'view')
|
|
62
|
+
.prefetch_related('dnsstatus')
|
|
63
|
+
.order_by('dns_name')
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
for ip_address in ip_addresses:
|
|
67
|
+
zone = _find_zone_for_dns_name(ip_address.dns_name, zones_by_name)
|
|
68
|
+
if zone is None:
|
|
69
|
+
forward_status = no_zone_status
|
|
70
|
+
else:
|
|
71
|
+
try:
|
|
72
|
+
status = ip_address.dnsstatus
|
|
73
|
+
if status.forward_action is not None:
|
|
74
|
+
output = next(
|
|
75
|
+
label for value, label in ACTION_CHOICES if value == status.forward_action
|
|
76
|
+
)
|
|
77
|
+
output += ': '
|
|
78
|
+
output += get_rcode_display(status.forward_rcode) or ''
|
|
79
|
+
colour = 'green' if status.forward_rcode == rcode.NOERROR else 'red'
|
|
80
|
+
forward_status = f'<span style="color:{colour}">{output}</span>'
|
|
81
|
+
else:
|
|
82
|
+
forward_status = '<span class="text-muted">Not created</span>'
|
|
83
|
+
except DNSStatus.DoesNotExist:
|
|
84
|
+
forward_status = '<span class="text-muted">Not created</span>'
|
|
85
|
+
|
|
86
|
+
rows.append(ManagedDNSNameRow(
|
|
87
|
+
dns_name=ip_address.dns_name,
|
|
88
|
+
ip_address=ip_address,
|
|
89
|
+
zone=zone,
|
|
90
|
+
obj=ip_address,
|
|
91
|
+
view_url=ip_address.get_absolute_url(),
|
|
92
|
+
edit_url=reverse('ipam:ipaddress_edit', args=[ip_address.pk]),
|
|
93
|
+
delete_url=None,
|
|
94
|
+
forward_status_html=forward_status,
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
return rows
|
|
98
|
+
|
|
4
99
|
|
|
5
100
|
def normalize_fqdn(dns_name: str) -> str:
|
|
6
101
|
if not dns_name:
|
netbox_ddns/validators.py
CHANGED
|
@@ -21,3 +21,9 @@ def validate_base64(value):
|
|
|
21
21
|
base64.b64decode(value, validate=True)
|
|
22
22
|
except binascii.Error:
|
|
23
23
|
raise ValidationError('Invalid base64 string')
|
|
24
|
+
|
|
25
|
+
class DNSNameValidator(RegexValidator):
|
|
26
|
+
regex = r"^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$"
|
|
27
|
+
message = _(
|
|
28
|
+
"Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names"
|
|
29
|
+
)
|