Compare commits

..
4 Commits
Author SHA1 Message Date
Sven Luethi a9c59f4c57 modified: .gitignore
Ingore build/ dir

modified:   src/netbox_plugin_bind_provisioner/__init__.py
Set version v0.9.9

modified:   src/netbox_plugin_bind_provisioner/management/commands/bind-transfer-endpoint.py
Fix bug where an exception occurse if a view has no zones.
Feature added - Fixes #5
2025-11-26 22:49:37 +01:00
Sven Luthi 787c3d0a20 modified: README.md
new file:   docs/install-systemd-service.md
Fixes #3
2025-11-25 10:00:31 +01:00
Sven Luethi 57195a822b modified: .github/workflows/python-publish.yml 2025-11-25 01:25:42 +01:00
Sven Luethi 0590caeebd modified: src/netbox_plugin_bind_provisioner/__init__.py
Update version string
2025-11-25 00:13:06 +01:00
6 changed files with 63 additions and 46 deletions

No files matched your search

+11 -20
View File
@@ -6,21 +6,21 @@
# separate terms of service, privacy policy, and support # separate terms of service, privacy policy, and support
# documentation. # documentation.
name: Upload Python Package name: Create and Upload Python Package
on: on:
release: release:
types: [published] types: [published]
# push:
# branches:
# - main
permissions: permissions:
contents: read contents: read
jobs: jobs:
build-release: build-and-publish:
name: Build name: Build and Publish
environment: pypi
permissions:
id-token: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -29,23 +29,14 @@ jobs:
with: with:
python-version: "3.x" python-version: "3.x"
- name: Build release distributions - name: Install build tools
run: | run: |
# NOTE: put your own distribution build steps here. python -m pip install --upgrade pip
python -m pip install build python -m pip install build
python -m build --wheel
- name: Upload distributions - name: Build release
uses: actions/upload-artifact@v4 run: |
with: python -m build --sdist --wheel
name: release-dists
path: dist/
pypi-publish:
name: Upload
environment: pypi
permissions:
id-token: write
steps:
- name: Publish package distributions to PyPI - name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
+1
View File
@@ -1,3 +1,4 @@
src/netbox_plugin_bind_provisioner.egg-info src/netbox_plugin_bind_provisioner.egg-info
dist/ dist/
build/
__pycache__ __pycache__
+5 -2
View File
@@ -116,8 +116,11 @@ This guide assumes:
5. Start listener 5. Start listener
This step runs the DNS endpoint used by bind to configure itself. You may want This step runs the DNS endpoint used by bind to configure itself. You may want
to write a service wrapper that runs this in the background. Dont forget to to write a service wrapper that runs this in the background.
activate the venv if you do decide to run this service in the background. A guide for setting up a systemd service on Ubuntu is provided by Matt
Kollross [here](docs/install-systemd-service.md). Dont forget to activate
the venv if you do decide to run this service in the background.
Note that `--port 5354` is optional. The listener will bind this port Note that `--port 5354` is optional. The listener will bind this port
by default. by default.
``` ```
+34
View File
@@ -0,0 +1,34 @@
1. Create System Unit file
```
cat <<EOF > /etc/systemd/system/netbox-bind-endpoint.service
[Unit]
Description=Netbox BIND Catalog Endpoing
Documentation=https://github.com/Suraxius/netbox-plugin-bind-provisioner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=netbox
Group=netbox
PIDFile=/var/tmp/netbox.pid
WorkingDirectory=/opt/netbox
ExecStart=/opt/netbox/venv/bin/python3 /opt/netbox/netbox/manage.py bind-transfer-endpoint --port 5354
Restart=on-failure
RestartSec=30
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
```
2. Enable and start the service
```
systemctl enable netbox-bind-endpoint
systemctl start netbox-bind-endpoint
systemctl status netbox-bind-endpoint
```
@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
from django.dispatch import receiver from django.dispatch import receiver
from django.conf import settings from django.conf import settings
__version__ = "0.9.6" __version__ = "0.9.9"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -57,7 +57,7 @@ class CatalogZone():
@classmethod @classmethod
def create(cls, view_name, soa_only=False): def create(cls, name, view_name):
# Synchronize following across threads as TCP and UDP listener both use it. # Synchronize following across threads as TCP and UDP listener both use it.
with cls.lock: with cls.lock:
#cls = self.__class__ #cls = self.__class__
@@ -77,12 +77,9 @@ class CatalogZone():
# Setting previous last zone update for next iteration: # Setting previous last zone update for next iteration:
cls._previous_last_zone_update = last_zone_update cls._previous_last_zone_update = last_zone_update
cls._incrementSerial() cls._incrementSerial()
# If there was no update, do nothing.
#else:
# logger.debug(f"No need to update serial for catalog zone")
# Zone origin # Zone origin
origin = dns.name.from_text("catz", dns.name.root) origin = dns.name.from_text(name, dns.name.root)
# Create a new empty zone # Create a new empty zone
zone = dns.zone.Zone(origin) zone = dns.zone.Zone(origin)
@@ -198,11 +195,11 @@ class CatalogZone():
class DNSBaseRequestHandler(socketserver.BaseRequestHandler): class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
# getZoneFromNB rewritten # getZoneFromNB rewritten
def getZoneFromNB(self, zone_name, view_name, soa_only=False): def getZoneFromNB(self, zone_name, view_name):
# Find the zone # Find the zone
try: try:
nb_zone = Zone.objects.get( nb_zone = Zone.objects.get(
name=zone_name, view=view_name, status=ZoneStatusChoices.STATUS_ACTIVE name=zone_name, view__name=view_name, status=ZoneStatusChoices.STATUS_ACTIVE
) )
except Zone.DoesNotExist: except Zone.DoesNotExist:
return None return None
@@ -263,14 +260,6 @@ class DNSBaseRequestHandler(socketserver.BaseRequestHandler):
return zone return zone
# def denyRequest(self, query, rcode: dns.rcode = dns.rcode.REFUSED):
# response = dns.message.make_response(query)
# response.set_rcode(rcode)
# wire = response.to_wire(multi=False)
# length = len(wire).to_bytes(2, byteorder="big")
# self.request.sendall(length + wire)
def denyRequestBadTSIG(self, wire, tsig_error: dns.rcode): def denyRequestBadTSIG(self, wire, tsig_error: dns.rcode):
# Use empty keyring to parse TSIG without validating # Use empty keyring to parse TSIG without validating
query = dns.message.from_wire(wire, keyring={}, ignore_trailing=True, continue_on_error=True) query = dns.message.from_wire(wire, keyring={}, ignore_trailing=True, continue_on_error=True)
@@ -348,11 +337,11 @@ class UDPRequestHandler(DNSBaseRequestHandler):
return return
# Check if catalog zone # Check if catalog zone
if dname == "catz": if dname == "catz" or dname == f"{nb_view.name}.catz":
zone = CatalogZone.create(nb_view.name, soa_only=True) zone = CatalogZone.create(dname, nb_view.name)
# If generic zone, retreive from NB # If generic zone, retreive from NB
else: else:
zone = self.getZoneFromNB(dname, nb_view, soa_only=True) zone = self.getZoneFromNB(dname, nb_view.name)
# When zone was not found, let client know # When zone was not found, let client know
if not zone: if not zone:
logger.warning(f"Zone {nb_view.name}/{dname} not found in NB") logger.warning(f"Zone {nb_view.name}/{dname} not found in NB")
@@ -363,10 +352,9 @@ class UDPRequestHandler(DNSBaseRequestHandler):
soa_rdataset = zone.get_rdataset(zone.origin, dns.rdatatype.SOA) soa_rdataset = zone.get_rdataset(zone.origin, dns.rdatatype.SOA)
# 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_rdataset[0] # Get the first SOA record soa_rdata = soa_rdataset[0] # Get the first SOA record
# Now, create the rrset from the soa_rdata # Now, create the rrset from the soa_rdata
#rrset = dns.rrset.from_rdata(dname, soa_rdataset.ttl, soa_rdata)
rrset = dns.rrset.from_rdata(zone.origin, soa_rdataset.ttl, soa_rdata) rrset = dns.rrset.from_rdata(zone.origin, soa_rdataset.ttl, soa_rdata)
# Append the rrset to the response's answer section # Append the rrset to the response's answer section
@@ -471,10 +459,10 @@ class TCPRequestHandler(DNSBaseRequestHandler):
return return
# Check if catalog zone # Check if catalog zone
if dname == "catz": if dname == "catz" or dname == f"{nb_view.name}.catz":
zone = CatalogZone.create(nb_view.name) zone = CatalogZone.create(dname, nb_view.name)
else: else:
zone = self.getZoneFromNB(dname, nb_view) zone = self.getZoneFromNB(dname, nb_view.name)
# When zone was not found, let client know # When zone was not found, let client know
if not zone: if not zone: