Compare commits

...
12 Commits
Author SHA1 Message Date
sven da6a5e09a5 modified: netbox_dns_bridge/__init__.py
Remove unneeded definition.

modified:   MANIFEST.in
Correct path

new file:   netbox_dns_bridge/migrations/__init__.py
Fixes missing migration.

new file:   netbox_dns_bridge/management/__init__.py
new file:   netbox_dns_bridge/management/commands/__init__.py
Just in case
2026-04-10 13:23:37 +02:00
Sven Luthi 3eb58f9b29 modified: docs/install-systemd-service.md
Correct naming in service unit
2026-04-10 11:16:58 +02:00
Sven Luthi ad52457c48 modified: README.md
Fix link alt text
2026-04-10 10:59:49 +02:00
Sven Luthi 9c3d7cce7e modified: README.md 2026-04-09 19:18:35 +02:00
Sven Luthi 6418ed56e6 modified: README.md 2026-04-09 19:10:39 +02:00
Sven Luthi fd16f7e701 modified: netbox_dns_bridge/__init__.py
Bump version to 1.5.2
2026-04-09 18:51:54 +02:00
Sven Luthi f926795d1b modified: netbox_dns_bridge/request_handler.py
Change query logging from debug to info. Seeing queries is very useful :)
2026-04-09 18:36:14 +02:00
Sven Luthi eedc01303b modified: netbox_dns_bridge/catalog_zone_manager.py
modified:   netbox_dns_bridge/request_handler.py
modified:   netbox_dns_bridge/utils.py
Various little bug fixes. Analysed by AI but fixed by hand to ensure integrity.
2026-04-09 18:28:05 +02:00
Sven Luthi 646059bb07 modified: netbox_dns_bridge/signals.py
Fix import path
2026-04-09 17:27:00 +02:00
Sven Luthi f41b4cea56 modified: netbox_dns_bridge/request_handler.py
Closes issue #15
2026-04-09 15:04:39 +02:00
Sven Luthi 3dec22b9f3 modified: README.md 2026-04-09 14:32:50 +02:00
Sven Luthi b23c773bee modified: README.md 2026-04-09 14:27:46 +02:00
11 changed files with 58 additions and 50 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
recursive-include netbox_bind_provisioner *.py recursive-include netbox_dns_bridge *.py
+16 -15
View File
@@ -1,21 +1,22 @@
# Netbox DNS Bridge # Netbox DNS Bridge
(Formerly Netbox Bind Provisioner) (Formerly Netbox Bind Provisioner)
The Netbox DNS Bridge plugin implements a lightweight DNS server inside Netbox and builds a bridge The NetBox DNS Bridge plugin extends [NetBox DNS](https://github.com/sys4/netbox-plugin-dns) by
for BIND and other DNS Servers implementing RFC9432 to retrieve DNS Zones directly from Netbox embedding a lightweight DNS server directly within NetBox. It acts as a bridge between NetBox DNS
using DNS native mechanisms. and your existing DNS infrastructure, leveraging native DNS mechanisms for seamless integration.
These include zone transfers (RFC 5936), catalog zones (RFC 9432), and dynamic updates (RFC 2136).
<a href="https://pypi.org/project/netbox-plugin-dns-bridge/"><img src="https://img.shields.io/pypi/v/netbox-plugin-dns-bridge" alt="PyPi"></a> <a href="https://pypi.org/project/netbox-plugin-dns-bridge/"><img src="https://img.shields.io/pypi/v/netbox-plugin-dns-bridge" alt="PyPi"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/stargazers"><img src="https://img.shields.io/github/stars/suraxius/netbox-plugin-bind-provisioner?style=flat" alt="Stars Badge"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/stargazers"><img src="https://img.shields.io/github/stars/suraxius/netbox-plugin-dns-bridge?style=flat" alt="Stars Badge"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/network/members"><img src="https://img.shields.io/github/forks/suraxius/netbox-plugin-bind-provisioner?style=flat" alt="Forks Badge"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/network/members"><img src="https://img.shields.io/github/forks/suraxius/netbox-plugin-dns-bridge?style=flat" alt="Forks Badge"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/issues"><img src="https://img.shields.io/github/issues/suraxius/netbox-plugin-bind-provisioner" alt="Issues Badge"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/issues"><img src="https://img.shields.io/github/issues/suraxius/netbox-plugin-dns-bridge" alt="Issues Badge"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/pulls"><img src="https://img.shields.io/github/issues-pr/suraxius/netbox-plugin-bind-provisioner" alt="Pull Requests Badge"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/pulls"><img src="https://img.shields.io/github/issues-pr/suraxius/netbox-plugin-dns-bridge" alt="Pull Requests Badge"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/graphs/contributors"><img src="https://img.shields.io/github/contributors/suraxius/netbox-plugin-bind-provisioner?color=2b9348" alt="GitHub contributors"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/graphs/contributors"><img src="https://img.shields.io/github/contributors/suraxius/netbox-plugin-dns-bridge?color=2b9348" alt="GitHub contributors"></a>
<a href="https://github.com/suraxius/netbox-plugin-bind-provisioner/blob/master/LICENSE"><img src="https://img.shields.io/github/license/suraxius/netbox-plugin-bind-provisioner?color=2b9348" alt="License Badge"></a> <a href="https://github.com/suraxius/netbox-plugin-dns-bridge/blob/master/LICENSE"><img src="https://img.shields.io/github/license/suraxius/netbox-plugin-dns-bridge?color=2b9348" alt="License Badge"></a>
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style Black"></a> <a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style Black"></a>
<a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=total&left_color=BLACK&right_color=BLUE&left_text=Downloads" alt="Downloads"></a> <a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=total&left_color=BLACK&right_color=BLUE&left_text=Downloads" alt="Downloads"></a>
<a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=monthly&left_color=BLACK&right_color=BLUE&left_text=Downloads%2fMonth" alt="Downloads/Week"></a> <a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=monthly&left_color=BLACK&right_color=BLUE&left_text=Downloads%2fMonth" alt="Downloads/Month"></a>
<a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=weekly&left_color=BLACK&right_color=BLUE&left_text=Downloads%2fWeek" alt="Downloads/Month"></a> <a href="https://pepy.tech/project/netbox-plugin-dns-bridge"><img src="https://static.pepy.tech/personalized-badge/netbox-plugin-dns-bridge?period=weekly&left_color=BLACK&right_color=BLUE&left_text=Downloads%2fWeek" alt="Downloads/Week"></a>
@@ -63,7 +64,7 @@ For example, when using netbox-plugin-dns `v1.5.5` install netbox-plugin-dns-bri
## Post 1.0.7 Upgrade Guide ## Post 1.0.7 Upgrade Guide
Applies only if you have a <= 1.0.7 installation. If you are freshly installing this plugin, Applies only if you have a <= 1.0.7 installation. If you are freshly installing this plugin,
go on to [Installation guide](installation-guide). go on to [Installation Guide](#installation-guide).
After version 1.0.7, the project was restructured and renamed. Until that point version updates After version 1.0.7, the project was restructured and renamed. Until that point version updates
happened more or less automatically. However, this shift is more of a move from one plugin to happened more or less automatically. However, this shift is more of a move from one plugin to
@@ -84,7 +85,7 @@ Make sure to note down the catalog zone serial number before going further.
3. Adjust your `configuration.py` 3. Adjust your `configuration.py`
- Change the plugin name from `netbox-plugin-bind-provisioner` to `netbox-plugin-dns-bridge` - Change the plugin name from `netbox-plugin-bind-provisioner` to `netbox-plugin-dns-bridge`
- Change the key in `PLUGIN_CONFIG` from `netbox_plugin_bind_provisioner` to `netbox_dns_bridge` - Change the key in `PLUGIN_CONFIG` from `netbox_plugin_bind_provisioner` to `netbox_dns_bridge`
4. Ensure the `dns-transfer-endpoint` service is not running before the next step. 4. Run migrations: `manage.py migrate`
5. Restore the catalog zone serial you noted down previously so that your slave dns servers continue 5. Restore the catalog zone serial you noted down previously so that your slave dns servers continue
to pull the changes: `manage.py dns-settings set catalog-zone-soa-serial yourserialnumber` to pull the changes: `manage.py dns-settings set catalog-zone-soa-serial yourserialnumber`
6. Start the `dns-transfer-endpoint` service. 6. Start the `dns-transfer-endpoint` service.
@@ -160,7 +161,7 @@ This guide assumes:
4. Run migrations 4. Run migrations
``` ```
python3 netbox/manage.py migrate manage.py migrate
``` ```
5. Start listener 5. Start listener
@@ -172,7 +173,7 @@ This guide assumes:
Note that `--port 5354` is optional. The listener will bind this port by default. Note that `--port 5354` is optional. The listener will bind this port by default.
``` ```
python3 netbox/manage.py dns-transfer-endpoint --port 5354 manage.py dns-transfer-endpoint --port 5354
``` ```
6. Configuring a Bind9 to interact with Netbox via the dns-transfer-endpoint endpoint. Note that its 6. Configuring a Bind9 to interact with Netbox via the dns-transfer-endpoint endpoint. Note that its
+7 -7
View File
@@ -1,9 +1,9 @@
1. Create System Unit file 1. Create System Unit file
``` ```
cat <<EOF > /etc/systemd/system/netbox-bind-endpoint.service cat <<EOF > /etc/systemd/system/netbox-dns-endpoint.service
[Unit] [Unit]
Description=Netbox BIND Catalog Endpoing Description=Netbox DNS transfer endpoint
Documentation=https://github.com/Suraxius/netbox-plugin-bind-provisioner Documentation=https://github.com/Suraxius/netbox-plugin-dns-bridge
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
@@ -15,7 +15,7 @@
PIDFile=/var/tmp/netbox.pid PIDFile=/var/tmp/netbox.pid
WorkingDirectory=/opt/netbox WorkingDirectory=/opt/netbox
ExecStart=/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py dns-transfer-endpoint --port 5354 ExecStart=/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py dns-transfer-endpoint --address 0.0.0.0 --port 5354
Restart=on-failure Restart=on-failure
RestartSec=30 RestartSec=30
@@ -28,7 +28,7 @@
2. Enable and start the service 2. Enable and start the service
``` ```
systemctl enable netbox-bind-endpoint systemctl enable netbox-dns-endpoint
systemctl start netbox-bind-endpoint systemctl start netbox-dns-endpoint
systemctl status netbox-bind-endpoint systemctl status netbox-dns-endpoint
``` ```
+1 -2
View File
@@ -2,7 +2,7 @@ import logging
from netbox.plugins import PluginConfig from netbox.plugins import PluginConfig
from django.conf import settings from django.conf import settings
__version__ = "1.5.1" __version__ = "1.5.3"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -33,4 +33,3 @@ class DNSBridgeConfig(PluginConfig):
config = DNSBridgeConfig config = DNSBridgeConfig
default_app_config = ".apps.NetboxDNSBridgeConfig"
+3 -5
View File
@@ -62,12 +62,10 @@ def _set_serial(new_serial: int) -> bool:
def _increment_serial() -> None: def _increment_serial() -> None:
if not _set_serial(_SERIAL_OBJ.value + 1): if not _set_serial(_SERIAL_OBJ.value + 1):
logger.warning( logger.warning(
f"Failed to incremenet catalog serial {_SERIAL_OBJ.value}. Will overflow serial back to 1" f"Catalog serial {_SERIAL_OBJ.value} reached max — wrapping back to 1"
) )
_set_serial(1) _set_serial(1)
logger.debug( logger.debug(f"Catalog zone SOA serial number is now {_SERIAL_OBJ.value}")
f"Catalog zone SOA serial number incremented to {_SERIAL_OBJ.value}"
)
# If a zone has no catz identifier yet, create it: # If a zone has no catz identifier yet, create it:
@@ -230,5 +228,5 @@ def _generate_member_identifier() -> None:
def update_member_identifier(zone: Zone) -> None: def update_member_identifier(zone: Zone) -> None:
CatalogZoneMemberIdentifier.objects.update_or_create( CatalogZoneMemberIdentifier.objects.update_or_create(
zone=zone, zone=zone,
defaults={"name": _generate_member_identifier}, defaults={"name": _generate_member_identifier()},
) )
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
+27 -17
View File
@@ -125,13 +125,13 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
response.set_rcode(dns.rcode.REFUSED) response.set_rcode(dns.rcode.REFUSED)
if query.had_tsig: if query.had_tsig:
# Add TSIG with BADKEY, but do not sign (no MAC) # Add TSIG with error code, but do not sign (no MAC, empty keyring)
response.use_tsig( response.use_tsig(
keyring={}, # empty; we're not signing keyring={}, # empty; we're not signing
keyname=query.keyname, keyname=query.keyname,
tsig_error=tsig_error, tsig_error=tsig_error,
) )
self._deny_request(query) self._send_response(response.to_wire())
def _send_response(self, data) -> None: def _send_response(self, data) -> None:
raise NotImplementedError raise NotImplementedError
@@ -164,23 +164,27 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
self.server.keyring, keyname=query.keyname, original_id=query.id self.server.keyring, keyname=query.keyname, original_id=query.id
) )
else: else:
# If key is unknown # unknown key — use an empty keyring so use_tsig() does not
# raise KeyError trying to look up a key that isn't present.
response.set_rcode(dns.rcode.REFUSED) response.set_rcode(dns.rcode.REFUSED)
# Generate new key for keyname provided
# keyname = query.keyname
# b64 = base64.b64encode(urandom(32)).decode("ascii")
# newkey = dns.tsigkeyring.from_text({ keyname: b64 })
response.use_tsig( response.use_tsig(
self.server.keyring, {}, # empty keyring; no signing
keyname=query.keyname, keyname=query.keyname,
tsig_error=dns.rcode.BADKEY, tsig_error=dns.rcode.BADKEY,
) )
data = response.to_wire(max_size=512) data = response.to_wire(max_size=512)
self._send_response(data) self._send_response(data)
logger.debug(f"{peer} SOA {nb_view.name}/{dname}") logger.info(f"{peer} SOA {nb_view.name}/{dname}")
def _handle_axfr_request(self, query, zone, peer, nb_view, dname) -> None: def _handle_axfr_request(self, query, zone, peer, nb_view, dname) -> None:
if query.keyname not in self.server.keyring:
logger.error(f"AXFR aborted: keyname {query.keyname} not in keyring")
self._deny_request(query)
return
tsig_key = self.server.keyring[query.keyname]
rrsets = [] rrsets = []
soa_rrset = None soa_rrset = None
for name, rdataset in zone.iterate_rdatasets(): for name, rdataset in zone.iterate_rdatasets():
@@ -211,9 +215,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# 3. Loop through RRsets # 3. Loop through RRsets
tsig_ctx = None tsig_ctx = None
for rrset in rrsets: for rrset in rrsets:
# logger.debug(f"Iterating over rrset {rrset}. tsig_ctx: {tsig_ctx}")
try: try:
# logger.debug(f"Adding {rrset} to renderer object")
r.add_rrset(dns.renderer.ANSWER, rrset) r.add_rrset(dns.renderer.ANSWER, rrset)
if r.max_size - len(r.output.getvalue()) < self.RESERVED_TSIG: if r.max_size - len(r.output.getvalue()) < self.RESERVED_TSIG:
raise dns.exception.TooBig("TSIG wont fit") raise dns.exception.TooBig("TSIG wont fit")
@@ -222,7 +224,8 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
r.write_header() r.write_header()
tsig_ctx = r.add_multi_tsig( tsig_ctx = r.add_multi_tsig(
ctx=tsig_ctx, ctx=tsig_ctx,
secret=self.server.keyring[query.keyname], secret=tsig_key.secret,
algorithm=tsig_key.algorithm,
keyname=query.keyname, keyname=query.keyname,
fudge=300, fudge=300,
id=query.id, id=query.id,
@@ -245,26 +248,33 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
query.question[0].rdtype, query.question[0].rdtype,
query.question[0].rdclass, query.question[0].rdclass,
) )
r.add_rrset(dns.renderer.ANSWER, rrset)
try:
r.add_rrset(dns.renderer.ANSWER, rrset)
except dns.exception.TooBig:
logger.error(
f"RRset {rrset.name}/{dns.rdatatype.to_text(rrset.rdtype)} "
f"exceeds MAX_WIRE ({self.MAX_WIRE}) and cannot be sent; "
f"skipping."
)
# 4. Final message with terminating TSIG # 4. Final message with terminating TSIG
r.write_header() r.write_header()
# logger.debug(f"Final message. tsig_ctx: {tsig_ctx}")
tsig_ctx = r.add_multi_tsig( tsig_ctx = r.add_multi_tsig(
ctx=tsig_ctx, ctx=tsig_ctx,
secret=self.server.keyring[query.keyname], secret=tsig_key.secret,
algorithm=tsig_key.algorithm,
keyname=query.keyname, keyname=query.keyname,
fudge=300, fudge=300,
id=query.id, id=query.id,
tsig_error=0, tsig_error=0,
other_data=b"", other_data=b"",
# request_mac=r.mac if tsig_ctx else None,
request_mac=r.mac if tsig_ctx else query.mac, request_mac=r.mac if tsig_ctx else query.mac,
) )
wire = r.get_wire() wire = r.get_wire()
self._send_response(wire) self._send_response(wire)
logger.debug(f"{peer} AXFR {nb_view.name}/{dname}") logger.info(f"{peer} AXFR {nb_view.name}/{dname}")
def _handle_dns_query(self, wire) -> None: def _handle_dns_query(self, wire) -> None:
peer = self.client_address[0] peer = self.client_address[0]
+1 -1
View File
@@ -1,7 +1,7 @@
from django.db.models.signals import pre_save, post_save from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver from django.dispatch import receiver
from netbox_dns.models import Zone from netbox_dns.models import Zone
from .service.endpoint import catalog_zone_manager as catzm from . import catalog_zone_manager as catzm
@receiver(pre_save, sender=Zone) @receiver(pre_save, sender=Zone)
+2 -2
View File
@@ -20,8 +20,8 @@ def export_bind_zone_file(nb_zone: netbox_dns.models.Zone, file_path: str):
rdata = dns.rdata.from_text(rdclass, rdatatype, record.value) rdata = dns.rdata.from_text(rdclass, rdatatype, record.value)
# Create rdataset # Create rdataset
rdataset = dns.rdataset.Rdataset(rdclass, rdatatype, record.ttl) rdataset = dns.rdataset.Rdataset(rdclass, rdatatype)
rdataset.add(rdata) rdataset.add(rdata, record.ttl)
# Add to zone # Add to zone
node = dp_zone.find_node(rname, create=True) node = dp_zone.find_node(rname, create=True)