diff --git a/netbox_ddns/background_tasks.py b/netbox_ddns/background_tasks.py index 690ceea..5f5c5e5 100644 --- a/netbox_ddns/background_tasks.py +++ b/netbox_ddns/background_tasks.py @@ -5,57 +5,16 @@ import dns.query import dns.rdatatype import dns.resolver from django.db import IntegrityError -from django.db.models.functions import Length from django_rq import job from dns import rcode from netaddr import ip from netbox_ddns.models import ACTION_CREATE, ACTION_DELETE, DNSStatus, ReverseZone, Zone -from netbox_ddns.utils import normalize_fqdn +from netbox_ddns.utils import get_soa logger = logging.getLogger('netbox_ddns') -def get_zone(dns_name: str) -> Optional[Zone]: - # Generate all possible zones - zones = [] - parts = dns_name.lower().split('.') - for i in range(len(parts)): - zones.append('.'.join(parts[-i - 1:])) - - # Find the zone, if any - return Zone.objects.filter(name__in=zones).order_by(Length('name').desc()).first() - - -def get_soa(dns_name: str) -> str: - parts = dns_name.rstrip('.').split('.') - for i in range(len(parts)): - zone_name = normalize_fqdn('.'.join(parts[i:])) - - try: - dns.resolver.query(zone_name, dns.rdatatype.SOA) - return zone_name - except dns.resolver.NoAnswer: - # The name exists, but has no SOA. Continue one level further up - continue - except dns.resolver.NXDOMAIN as e: - # Look for a SOA record in the authority section - for query, response in e.responses().items(): - for rrset in response.authority: - if rrset.rdtype == dns.rdatatype.SOA: - return rrset.name.to_text() - - -def get_reverse_zone(address: ip.IPAddress) -> Optional[ReverseZone]: - # Find the zone, if any - zones = list(ReverseZone.objects.filter(prefix__net_contains=address)) - if not zones: - return None - - zones.sort(key=lambda zone: zone.prefix.prefixlen) - return zones[-1] - - def status_update(output: List[str], operation: str, response) -> None: code = response.rcode() @@ -70,7 +29,7 @@ def status_update(output: List[str], operation: str, response) -> None: def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]): - zone = get_zone(dns_name) + zone = Zone.objects.find_for_dns_name(dns_name) if zone: logger.debug(f"Found zone {zone.name} for {dns_name}") if status: @@ -101,7 +60,7 @@ def create_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]): - zone = get_zone(dns_name) + zone = Zone.objects.find_for_dns_name(dns_name) if zone: logger.debug(f"Found zone {zone.name} for {dns_name}") if status: @@ -131,7 +90,7 @@ def delete_forward(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]): - zone = get_reverse_zone(address) + zone = ReverseZone.objects.find_for_address(address) if zone and dns_name: record_name = zone.record_name(address) logger.debug(f"Found zone {zone.name} for {record_name}") @@ -162,7 +121,7 @@ def create_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSSta def delete_reverse(dns_name: str, address: ip.IPAddress, status: Optional[DNSStatus], output: List[str]): - zone = get_reverse_zone(address) + zone = ReverseZone.objects.find_for_address(address) if zone and dns_name: record_name = zone.record_name(address) logger.debug(f"Found zone {zone.name} for {record_name}") diff --git a/netbox_ddns/models.py b/netbox_ddns/models.py index 7cc2e12..7a66168 100644 --- a/netbox_ddns/models.py +++ b/netbox_ddns/models.py @@ -6,6 +6,7 @@ import dns.tsigkeyring import dns.update from django.core.exceptions import ValidationError from django.db import models +from django.db.models.functions import Length from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from dns import rcode @@ -115,6 +116,18 @@ class Server(models.Model): ) +class ZoneQuerySet(models.QuerySet): + def find_for_dns_name(self, dns_name: str) -> Optional['Zone']: + # Generate all possible zones + zones = [] + parts = dns_name.lower().split('.') + for i in range(len(parts)): + zones.append('.'.join(parts[-i - 1:])) + + # Find the zone, if any + return self.filter(name__in=zones).order_by(Length('name').desc()).first() + + class Zone(models.Model): name = models.CharField( verbose_name=_('zone name'), @@ -131,6 +144,8 @@ class Zone(models.Model): on_delete=models.PROTECT, ) + objects = ZoneQuerySet.as_manager() + class Meta: ordering = ('name',) verbose_name = _('forward zone') @@ -147,6 +162,17 @@ class Zone(models.Model): return self.server.create_update(self.name) +class ReverseZoneQuerySet(models.QuerySet): + def find_for_address(self, address: ip.IPAddress) -> Optional['ReverseZone']: + # Find the zone, if any + zones = list(ReverseZone.objects.filter(prefix__net_contains=address)) + if not zones: + return None + + zones.sort(key=lambda zone: zone.prefix.prefixlen) + return zones[-1] + + class ReverseZone(models.Model): prefix = IPNetworkField( verbose_name=_('prefix'), @@ -167,6 +193,8 @@ class ReverseZone(models.Model): on_delete=models.PROTECT, ) + objects = ReverseZoneQuerySet.as_manager() + class Meta: ordering = ('prefix',) verbose_name = _('reverse zone') diff --git a/netbox_ddns/template_content.py b/netbox_ddns/template_content.py index ca439f7..3c00f8a 100644 --- a/netbox_ddns/template_content.py +++ b/netbox_ddns/template_content.py @@ -1,4 +1,5 @@ from django.contrib.auth.context_processors import PermWrapper +from django.template.context_processors import csrf from extras.plugins import PluginTemplateExtension from . import tables @@ -8,6 +9,16 @@ from . import tables class DNSInfo(PluginTemplateExtension): model = 'ipam.ipaddress' + def buttons(self): + """ + A button to force DNS re-provisioning + """ + context = { + 'perms': PermWrapper(self.context['request'].user), + } + context.update(csrf(self.context['request'])) + return self.render('netbox_ddns/ipaddress/dns_refresh_button.html', context) + def left_page(self): """ An info-box with the status of the DNS modifications and records diff --git a/netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html b/netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html new file mode 100644 index 0000000..5c5f5b2 --- /dev/null +++ b/netbox_ddns/templates/netbox_ddns/ipaddress/dns_refresh_button.html @@ -0,0 +1,11 @@ +{% if perms.ipam.change_ipaddress %} +
+{% endif %} diff --git a/netbox_ddns/urls.py b/netbox_ddns/urls.py index 8d90a62..b667169 100644 --- a/netbox_ddns/urls.py +++ b/netbox_ddns/urls.py @@ -1,8 +1,11 @@ from django.urls import path -from .views import ExtraDNSNameCreateView, ExtraDNSNameDeleteView, ExtraDNSNameEditView +from .views import ExtraDNSNameCreateView, ExtraDNSNameDeleteView, ExtraDNSNameEditView, IPAddressDNSNameRecreateView urlpatterns = [ + path(route='ip-addresses/