Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6418ed56e6 | ||
|
|
fd16f7e701 | ||
|
|
f926795d1b | ||
|
|
eedc01303b | ||
|
|
646059bb07 | ||
|
|
f41b4cea56 | ||
|
|
3dec22b9f3 | ||
|
|
b23c773bee |
No files matched your search
@@ -1,9 +1,10 @@
|
||||
# Netbox DNS Bridge
|
||||
(Formerly Netbox Bind Provisioner)
|
||||
|
||||
The Netbox DNS Bridge plugin implements a lightweight DNS server inside Netbox and builds a bridge
|
||||
for BIND and other DNS Servers implementing RFC9432 to retrieve DNS Zones directly from Netbox
|
||||
using DNS native mechanisms.
|
||||
The NetBox DNS Bridge plugin extends [NetBox DNS](https://github.com/sys4/netbox-plugin-dns) by
|
||||
embedding a lightweight DNS server directly within NetBox. It acts as a bridge between NetBox DNS
|
||||
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://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>
|
||||
@@ -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
|
||||
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
|
||||
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`
|
||||
- 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`
|
||||
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
|
||||
to pull the changes: `manage.py dns-settings set catalog-zone-soa-serial yourserialnumber`
|
||||
6. Start the `dns-transfer-endpoint` service.
|
||||
@@ -160,7 +161,7 @@ This guide assumes:
|
||||
|
||||
4. Run migrations
|
||||
```
|
||||
python3 netbox/manage.py migrate
|
||||
manage.py migrate
|
||||
```
|
||||
|
||||
5. Start listener
|
||||
@@ -172,7 +173,7 @@ This guide assumes:
|
||||
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
from netbox.plugins import PluginConfig
|
||||
from django.conf import settings
|
||||
|
||||
__version__ = "1.5.1"
|
||||
__version__ = "1.5.2"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -62,12 +62,10 @@ def _set_serial(new_serial: int) -> bool:
|
||||
def _increment_serial() -> None:
|
||||
if not _set_serial(_SERIAL_OBJ.value + 1):
|
||||
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)
|
||||
logger.debug(
|
||||
f"Catalog zone SOA serial number incremented to {_SERIAL_OBJ.value}"
|
||||
)
|
||||
logger.debug(f"Catalog zone SOA serial number is now {_SERIAL_OBJ.value}")
|
||||
|
||||
|
||||
# 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:
|
||||
CatalogZoneMemberIdentifier.objects.update_or_create(
|
||||
zone=zone,
|
||||
defaults={"name": _generate_member_identifier},
|
||||
defaults={"name": _generate_member_identifier()},
|
||||
)
|
||||
@@ -125,13 +125,13 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
||||
response.set_rcode(dns.rcode.REFUSED)
|
||||
|
||||
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(
|
||||
keyring={}, # empty; we're not signing
|
||||
keyname=query.keyname,
|
||||
tsig_error=tsig_error,
|
||||
)
|
||||
self._deny_request(query)
|
||||
self._send_response(response.to_wire())
|
||||
|
||||
def _send_response(self, data) -> None:
|
||||
raise NotImplementedError
|
||||
@@ -164,23 +164,27 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
||||
self.server.keyring, keyname=query.keyname, original_id=query.id
|
||||
)
|
||||
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)
|
||||
# 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(
|
||||
self.server.keyring,
|
||||
{}, # empty keyring; no signing
|
||||
keyname=query.keyname,
|
||||
tsig_error=dns.rcode.BADKEY,
|
||||
)
|
||||
|
||||
data = response.to_wire(max_size=512)
|
||||
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:
|
||||
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 = []
|
||||
soa_rrset = None
|
||||
for name, rdataset in zone.iterate_rdatasets():
|
||||
@@ -211,9 +215,7 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
||||
# 3. Loop through RRsets
|
||||
tsig_ctx = None
|
||||
for rrset in rrsets:
|
||||
# logger.debug(f"Iterating over rrset {rrset}. tsig_ctx: {tsig_ctx}")
|
||||
try:
|
||||
# logger.debug(f"Adding {rrset} to renderer object")
|
||||
r.add_rrset(dns.renderer.ANSWER, rrset)
|
||||
if r.max_size - len(r.output.getvalue()) < self.RESERVED_TSIG:
|
||||
raise dns.exception.TooBig("TSIG wont fit")
|
||||
@@ -222,7 +224,8 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
||||
r.write_header()
|
||||
tsig_ctx = r.add_multi_tsig(
|
||||
ctx=tsig_ctx,
|
||||
secret=self.server.keyring[query.keyname],
|
||||
secret=tsig_key.secret,
|
||||
algorithm=tsig_key.algorithm,
|
||||
keyname=query.keyname,
|
||||
fudge=300,
|
||||
id=query.id,
|
||||
@@ -245,26 +248,33 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
||||
query.question[0].rdtype,
|
||||
query.question[0].rdclass,
|
||||
)
|
||||
|
||||
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
|
||||
r.write_header()
|
||||
# logger.debug(f"Final message. tsig_ctx: {tsig_ctx}")
|
||||
tsig_ctx = r.add_multi_tsig(
|
||||
ctx=tsig_ctx,
|
||||
secret=self.server.keyring[query.keyname],
|
||||
secret=tsig_key.secret,
|
||||
algorithm=tsig_key.algorithm,
|
||||
keyname=query.keyname,
|
||||
fudge=300,
|
||||
id=query.id,
|
||||
tsig_error=0,
|
||||
other_data=b"",
|
||||
# request_mac=r.mac if tsig_ctx else None,
|
||||
request_mac=r.mac if tsig_ctx else query.mac,
|
||||
)
|
||||
wire = r.get_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:
|
||||
peer = self.client_address[0]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.dispatch import receiver
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
# Create rdataset
|
||||
rdataset = dns.rdataset.Rdataset(rdclass, rdatatype, record.ttl)
|
||||
rdataset.add(rdata)
|
||||
rdataset = dns.rdataset.Rdataset(rdclass, rdatatype)
|
||||
rdataset.add(rdata, record.ttl)
|
||||
|
||||
# Add to zone
|
||||
node = dp_zone.find_node(rname, create=True)
|
||||
|
||||
Reference in new issue
Block a user