modified: README.md
modified: netbox_dns_bridge/notify_handler.py modified: netbox_dns_bridge/request_handler.py modified: netbox_dns_bridge/signals/netbox_dns_zones.py Disable NOTIFY feature by default and add setting to enable it.
This commit is contained in:
1 parent
cb4886f032
commit
73544a4335
4 files changed
+69
-52
No files matched your search
@@ -1,10 +1,10 @@
|
|||||||
# Netbox DNS Bridge
|
# Netbox DNS Bridge
|
||||||
(Formerly Netbox Bind Provisioner)
|
|
||||||
|
|
||||||
The NetBox DNS Bridge plugin extends [NetBox DNS](https://github.com/sys4/netbox-plugin-dns) by
|
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
|
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.
|
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).
|
These include zone transfers (RFC 5936), catalog zones (RFC 9432), and notifying clients about zone
|
||||||
|
changes (RFC 1996).
|
||||||
|
|
||||||
<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-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-dns-bridge/stargazers"><img src="https://img.shields.io/github/stars/suraxius/netbox-plugin-dns-bridge?style=flat" alt="Stars Badge"></a>
|
||||||
@@ -18,12 +18,6 @@ These include zone transfers (RFC 5936), catalog zones (RFC 9432), and dynamic u
|
|||||||
<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=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/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=weekly&left_color=BLACK&right_color=BLUE&left_text=Downloads%2fWeek" alt="Downloads/Week"></a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
DISCLAIMER: The dynamic update endpoint isn't here yet. Its planned - without a deadline.
|
|
||||||
|
|
||||||
## Plugin configuration
|
## Plugin configuration
|
||||||
While providing Zone transfers via AXFR, the Server also exposes specialized catalog zones that BIND
|
While providing Zone transfers via AXFR, the Server also exposes specialized catalog zones that BIND
|
||||||
and other RFC9432 compliant DNS Servers use to automatically discover newly created zones and remove
|
and other RFC9432 compliant DNS Servers use to automatically discover newly created zones and remove
|
||||||
@@ -52,44 +46,55 @@ Parameter | Description
|
|||||||
--address | IP of interface to bind to (defaults to 0.0.0.0)
|
--address | IP of interface to bind to (defaults to 0.0.0.0)
|
||||||
|
|
||||||
### Plugin settings
|
### Plugin settings
|
||||||
|
Plugin settings should be configured under `PLUGINS_CONFIG` in `netbox_dns_bridge`:
|
||||||
|
```json
|
||||||
|
PLUGINS_CONFIG = {
|
||||||
|
'netbox_dns_bridge': {
|
||||||
|
...
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TSIG Authentication
|
||||||
|
Following sets the TSIG key that allows clients to query the transfer endpoint and also they key to
|
||||||
|
be used to sign NOTIFY messages to clients on zone changes. Each view should have its own unique
|
||||||
|
key to allow the plugin to identify the view the client is trying to access. Re-using the key for
|
||||||
|
multiple views yields unpredicted behavior.
|
||||||
|
```json
|
||||||
|
'tsig_keys': {
|
||||||
|
'the-view-name-here': {
|
||||||
|
'keyname': 'the-tsig-key-name-here',
|
||||||
|
'algorithm': 'hmac-sha256',
|
||||||
|
'secret': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Client notification on zone change
|
||||||
|
To enable the NOTIFY feature, set following:
|
||||||
|
```
|
||||||
|
'notify_clients': True
|
||||||
|
```
|
||||||
|
This causes the plugin to schedule background jobs which send out NOTIFY messages to all clients
|
||||||
|
that have previously successfullly requested a zone transfer from the transfer endpoint.
|
||||||
|
|
||||||
|
After how many hours that are client last checked in on a zone with a SOA or transfer request,
|
||||||
|
should the client be considered dead and no longer be notified. Default is 24 hours.
|
||||||
|
```
|
||||||
|
notify_client_dead_after_hours: 24
|
||||||
|
```
|
||||||
|
|
||||||
Setting | Description
|
Setting | Description
|
||||||
--------------------| ---------------------------------------------------------
|
--------------------| ---------------------------------------------------------
|
||||||
tsig_keys | Maps a TSIG Key to be used for each view.
|
tsig_keys | Maps a TSIG Key to be used for each view.
|
||||||
|
notify_clients | When set to true, turns on the notify mechanism to inform downsteams
|
||||||
|
|
||||||
## Plugin compatibility
|
## Plugin compatibility
|
||||||
This plugin is an extension to the netbox-plugin-dns plugin. As such the versioning of this plugin
|
This plugin extends the netbox-plugin-dns plugin. As such the versioning was changed to match the
|
||||||
was changed to match the one of the netbox-plugin-dns plugin closely. To guarantee compatability,
|
one of netbox-plugin-dns more closely. To guarantee compatability, ensure that the major and minor
|
||||||
ensure that the major and minor version match between both plugins.
|
version match between both plugins. For example, when using netbox-plugin-dns `v1.5.5` install
|
||||||
For example, when using netbox-plugin-dns `v1.5.5` install netbox-plugin-dns-bridge `v1.5.x`.
|
netbox-plugin-dns-bridge `v1.5.x`.
|
||||||
|
|
||||||
## 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).
|
|
||||||
|
|
||||||
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
|
|
||||||
another as the package name has changed.
|
|
||||||
|
|
||||||
This should not be an issue since the slave DNS Servers connected to Netbox can operate
|
|
||||||
independently while this plugin is being upgraded.
|
|
||||||
|
|
||||||
Make sure to note down the catalog zone serial number before going further.
|
|
||||||
|
|
||||||
1. Remove Netbox Bind Provisioner package
|
|
||||||
- Remove `netbox-plugin-bind-provisioner` from **local_dependencies.txt**
|
|
||||||
- Uninstall the package: `pip uninstall netbox-plugin-bind-provisioner`
|
|
||||||
2. Install Netbox DNS Bridge package
|
|
||||||
- Install the package `pip install netbox-plugin-dns-bridge`
|
|
||||||
- Put `netbox-plugin-dns-bridge` in your **local_dependencies.txt** so it will be installed on
|
|
||||||
next upgrade.
|
|
||||||
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. 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.
|
|
||||||
7. Mission accomplished.
|
|
||||||
|
|
||||||
## Installation guide
|
## Installation guide
|
||||||
This setup provisions a BIND9 server directly with DNS data from NetBox. BIND9 can optionally run on
|
This setup provisions a BIND9 server directly with DNS data from NetBox. BIND9 can optionally run on
|
||||||
@@ -222,6 +227,7 @@ This guide assumes:
|
|||||||
########## ZONES ##########
|
########## ZONES ##########
|
||||||
view "public" {
|
view "public" {
|
||||||
match-clients { public; };
|
match-clients { public; };
|
||||||
|
allow-notify { key "public_view_key"; };
|
||||||
|
|
||||||
catalog-zones {
|
catalog-zones {
|
||||||
zone "catz"
|
zone "catz"
|
||||||
@@ -240,6 +246,7 @@ This guide assumes:
|
|||||||
|
|
||||||
view "private" {
|
view "private" {
|
||||||
match-clients { private; };
|
match-clients { private; };
|
||||||
|
allow-notify { key "private_view_key"; };
|
||||||
|
|
||||||
catalog-zones {
|
catalog-zones {
|
||||||
zone "catz"
|
zone "catz"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from .logger import get_logger
|
|||||||
from .models import SeenTransferClients
|
from .models import SeenTransferClients
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"]
|
||||||
|
|
||||||
|
|
||||||
class SendDNSNotify(JobRunner):
|
class SendDNSNotify(JobRunner):
|
||||||
@@ -19,7 +20,7 @@ class SendDNSNotify(JobRunner):
|
|||||||
name = "Send DNS NOTIFY"
|
name = "Send DNS NOTIFY"
|
||||||
|
|
||||||
def _load_tsig_key(self, view_name: str) -> dns.tsig.Key:
|
def _load_tsig_key(self, view_name: str) -> dns.tsig.Key:
|
||||||
tsig_keys = settings.PLUGINS_CONFIG["netbox_dns_bridge"].get("tsig_keys", {})
|
tsig_keys = SETTINGS.get("tsig_keys", {})
|
||||||
if view_name not in tsig_keys:
|
if view_name not in tsig_keys:
|
||||||
raise RuntimeError(f"No TSIG key configured for view '{view_name}'")
|
raise RuntimeError(f"No TSIG key configured for view '{view_name}'")
|
||||||
|
|
||||||
@@ -50,7 +51,8 @@ class SendDNSNotify(JobRunner):
|
|||||||
view = View.objects.get(name=view_name)
|
view = View.objects.get(name=view_name)
|
||||||
tsig_key = self._load_tsig_key(view_name)
|
tsig_key = self._load_tsig_key(view_name)
|
||||||
|
|
||||||
seen_cutoff = timezone.now() - timezone.timedelta(hours=1)
|
cutoff_hours = SETTINGS.get("notify_client_dead_after_hours", 24)
|
||||||
|
seen_cutoff = timezone.now() - timezone.timedelta(hours=cutoff_hours)
|
||||||
clients = SeenTransferClients.objects.filter(
|
clients = SeenTransferClients.objects.filter(
|
||||||
view=view, last_seen__gte=seen_cutoff
|
view=view, last_seen__gte=seen_cutoff
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import dns.exception
|
|||||||
import dns.renderer
|
import dns.renderer
|
||||||
from .logger import get_logger
|
from .logger import get_logger
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from netbox_dns.models import Zone, Record
|
from netbox_dns.models import Zone, Record, View
|
||||||
from netbox_dns_bridge import catalog_zone_manager as catzm
|
from netbox_dns_bridge import catalog_zone_manager as catzm
|
||||||
from .models import SeenTransferClients
|
from .models import SeenTransferClients
|
||||||
|
|
||||||
@@ -103,7 +103,8 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
# Ensure rdataset has the same rdclass as the zone
|
# Ensure rdataset has the same rdclass as the zone
|
||||||
if rdataset.rdclass != zone.rdclass:
|
if rdataset.rdclass != zone.rdclass:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"rdataset rdclass {rdataset.rdclass} does not match zone rdclass {zone.rdclass}"
|
f"rdataset rdclass {rdataset.rdclass} does not match "
|
||||||
|
f"zone rdclass {zone.rdclass}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the rdataset has any rdata before creating an RRset
|
# Check if the rdataset has any rdata before creating an RRset
|
||||||
@@ -143,6 +144,13 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
wire = response.to_wire(multi=False)
|
wire = response.to_wire(multi=False)
|
||||||
self._send_response(wire)
|
self._send_response(wire)
|
||||||
|
|
||||||
|
def _track_seen_client(source_ip: str, view: View) -> None:
|
||||||
|
SeenTransferClients.objects.update_or_create(
|
||||||
|
source_ip=source_ip,
|
||||||
|
view=view,
|
||||||
|
defaults={"last_seen": timezone.now()},
|
||||||
|
)
|
||||||
|
|
||||||
def _handle_soa_request(self, query, soa_rrset, zone, peer, nb_view, dname) -> None:
|
def _handle_soa_request(self, query, soa_rrset, zone, peer, nb_view, dname) -> None:
|
||||||
# We assume that the SOA rdataset has at least one record (it usually does).
|
# We assume that the SOA rdataset has at least one record (it usually does).
|
||||||
soa_rdata = soa_rrset[0] # Get the first SOA record
|
soa_rdata = soa_rrset[0] # Get the first SOA record
|
||||||
@@ -274,13 +282,6 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
)
|
)
|
||||||
wire = r.get_wire()
|
wire = r.get_wire()
|
||||||
self._send_response(wire)
|
self._send_response(wire)
|
||||||
|
|
||||||
SeenTransferClients.objects.update_or_create(
|
|
||||||
source_ip=peer,
|
|
||||||
view=nb_view,
|
|
||||||
defaults={"last_seen": timezone.now()},
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(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:
|
||||||
@@ -361,8 +362,10 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
|
|||||||
|
|
||||||
if qtype == dns.rdatatype.SOA:
|
if qtype == dns.rdatatype.SOA:
|
||||||
self._handle_soa_request(query, soa_rrset, zone, peer, nb_view, dname)
|
self._handle_soa_request(query, soa_rrset, zone, peer, nb_view, dname)
|
||||||
|
self._track_seen_client(peer, nb_view)
|
||||||
elif qtype == dns.rdatatype.AXFR:
|
elif qtype == dns.rdatatype.AXFR:
|
||||||
self._handle_axfr_request(query, zone, peer, nb_view, dname)
|
self._handle_axfr_request(query, zone, peer, nb_view, dname)
|
||||||
|
self._track_seen_client(peer, nb_view)
|
||||||
|
|
||||||
|
|
||||||
class UDPRequestHandler(DNSBaseRequestHandler):
|
class UDPRequestHandler(DNSBaseRequestHandler):
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
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 django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.conf import settings
|
||||||
from netbox.context import current_request
|
from netbox.context import current_request
|
||||||
from netbox_dns.models import Zone
|
from netbox_dns.models import Zone
|
||||||
from netbox_dns_bridge import catalog_zone_manager as catzm
|
from netbox_dns_bridge import catalog_zone_manager as catzm
|
||||||
from netbox_dns_bridge import notify_handler
|
from netbox_dns_bridge import notify_handler
|
||||||
|
|
||||||
|
SETTINGS = settings.PLUGINS_CONFIG["netbox_dns_bridge"]
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Zone)
|
@receiver(pre_save, sender=Zone)
|
||||||
def zone_pre_save(sender, instance, **kwargs):
|
def zone_pre_save(sender, instance, **kwargs):
|
||||||
@@ -31,6 +34,8 @@ def zone_post_save(sender, instance, created, **kwargs):
|
|||||||
|
|
||||||
def _on_commit():
|
def _on_commit():
|
||||||
catzm.increment_soa_serial()
|
catzm.increment_soa_serial()
|
||||||
|
|
||||||
|
if SETTINGS.get("notify_clients", False):
|
||||||
notify_handler.schedule(zone)
|
notify_handler.schedule(zone)
|
||||||
|
|
||||||
transaction.on_commit(_on_commit)
|
transaction.on_commit(_on_commit)
|
||||||
|
|||||||
Reference in new issue
Block a user