Files
netbox-plugin-dns-bridge/netbox_dns_bridge/management/commands/dns-transfer-endpoint.py
T
sven 55c562d5a7 Fix fall-through after OperationalError in TSIG key loading
In _load_tsig_key_settings, the OperationalError handler for the
View.objects.get(name=view_name) lookup called close_old_connections()
but should have exited the script instead. This is the initial startup
and if loading of settings fails, the script should simply exit.

Also contains some code cleaning
2026-08-11 12:08:51 +02:00

118 lines
3.8 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 netbox_dns_bridge.request_handler import UDPRequestHandler, TCPRequestHandler
from netbox_dns_bridge.dns_server import UDPDNSServer, TCPDNSServer
from netbox_dns_bridge.logger 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()