Files
Sven Luethi ac34afc46c modified: netbox_dns_bridge/__init__.py
deleted:    netbox_dns_bridge/logger.py
modified:   netbox_dns_bridge/management/commands/dns-transfer-endpoint.py
new file:   netbox_dns_bridge/management/commands/dns_transfer_endpoint/__init__.py
renamed:    netbox_dns_bridge/catalog_zone_manager.py -> netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py
renamed:    netbox_dns_bridge/dns_server.py -> netbox_dns_bridge/management/commands/dns_transfer_endpoint/dns_server.py
renamed:    netbox_dns_bridge/request_handler.py -> netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py
modified:   netbox_dns_bridge/signals/netbox_dns_zones.py
renamed:    netbox_dns_bridge/notify_handler.py -> netbox_dns_bridge/signals/notify_handler.py
modified:   netbox_dns_bridge/utils.py
2026-08-11 18:30:07 +02:00

118 lines
3.9 KiB
Python

import threading
import sys
import dns.query
import dns.message
import dns.tsigkeyring
import dns.name
import dns.zone
import dns.rdatatype
import dns.rdataclass
import dns.rdtypes
import dns.exception
import dns.renderer
from django.core.management.base import BaseCommand
from django.conf import settings
from django.db import OperationalError
from netbox_dns.models import View
from .dns_transfer_endpoint.request_handler import UDPRequestHandler, TCPRequestHandler
from .dns_transfer_endpoint.dns_server import UDPDNSServer, TCPDNSServer
from netbox_dns_bridge.utils import get_logger
LOGGER = get_logger(__name__)
SETTINGS = settings.PLUGINS_CONFIG.get("netbox_dns_bridge", {})
class Command(BaseCommand):
help = "Run a minimal AXFR DNS server using data from NetBox DNS plugin"
def _load_tsig_key_settings(self):
self.keyring = {}
self.tsig_view_map = {}
for view_name, data in self.tsig_keys.items():
key_name = data.get("keyname")
secret = data.get("secret")
algorithm = data.get("algorithm", "hmac-sha256")
if not key_name:
LOGGER.error(
f"TSIG key for view {view_name} not found. Cannot start transfer endpoint."
)
sys.exit(1)
elif not secret:
LOGGER.error(
f"TSIG secret for key {key_name} not found. Cannot start transfer endpoint."
)
sys.exit(1)
try:
nb_view = View.objects.get(name=view_name)
except View.DoesNotExist:
LOGGER.error(
f"Skipping TSIG key {key_name}: View '{view_name}' not found in database."
)
continue
except OperationalError as e:
LOGGER.error(
f"There was an error retrieving view model from database: {e}"
)
sys.exit(1)
# Normalize key name to absolute DNS name
key_name_obj = dns.name.from_text(
key_name, origin=dns.name.root
).canonicalize()
key_name_str = key_name_obj.to_text() # Will always include trailing do
self.keyring[key_name_obj] = dns.tsig.Key(
name=key_name_obj, secret=secret, algorithm=algorithm
)
self.tsig_view_map[key_name_str] = nb_view
LOGGER.info(f"Loaded TSIG key {key_name_str} for view {nb_view.name}")
if not self.keyring:
msg = "No TSIG keys found in database."
LOGGER.critical(msg)
raise RuntimeError(msg)
def add_arguments(self, parser):
parser.add_argument(
"--port", type=int, default=5354, help="Port number to listen on"
)
parser.add_argument(
"--address", type=str, default="0.0.0.0", help="IP to bind to"
)
def handle(self, *args, **options):
# Load parameters
port = options["port"]
address = options["address"]
self.tsig_keys = SETTINGS.get("tsig_keys", None)
if not self.tsig_keys:
raise RuntimeError("tsig_keys variable not set in plugin settings.")
self._load_tsig_key_settings()
udp_server = UDPDNSServer(
(address, port), UDPRequestHandler, self.keyring, self.tsig_view_map
)
tcp_server = TCPDNSServer(
(address, port), TCPRequestHandler, self.keyring, self.tsig_view_map
)
def _run_udp_server(server):
LOGGER.info(f"Query endpoint listening on {address} udp/{port}")
server.serve_forever()
udp_thread = threading.Thread(
target=_run_udp_server, args=(udp_server,), daemon=True
)
udp_thread.start()
LOGGER.info(f"Query endpoint listening on {address} tcp/{port}")
tcp_server.serve_forever()