Compare commits

..

2 Commits

Author SHA1 Message Date
Gemini Bot
c39f973279 fix: Enforce DNS Name matches Device Name for Forward Zone generation
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 8s
2025-12-15 03:55:09 +00:00
Gemini Bot
4ab0e0102c feat: Add NETBOX_EXTRA_FILTER to support filtering IPs (e.g. by VRF or Tag) 2025-12-15 03:51:16 +00:00
2 changed files with 30 additions and 1 deletions

View File

@@ -29,6 +29,7 @@ Die Konfiguration erfolgt vollständig über Umgebungsvariablen.
| :--- | :--- | :--- |
| `REFRESH_INTERVAL` | `600` | Synchronisationsintervall in Sekunden. |
| `NETBOX_SSL_VERIFY` | `true` | SSL-Zertifikatsprüfung aktivieren (`true`/`false`). Bei selbstsignierten Zertifikaten auf `false` setzen. |
| `NETBOX_EXTRA_FILTER` | `""` | Zusätzliche Filterparameter für die NetBox-API-Abfrage (z.B. `&vrf=internal` oder `&tag__n=external`). |
| `OUTPUT_FILE_FWD` | `/zones/db.fwd` | Pfad im Container für die Forward-Zonendatei. |
| `OUTPUT_FILE_REV` | `/zones/db.rev` | Pfad im Container für die Reverse-Zonendatei. |
| `FALLBACK_NS_HOSTNAME`| `ns1` | Hostname, der als NS-Eintrag verwendet wird, falls keiner in NetBox existiert. |

30
sync.py
View File

@@ -35,6 +35,9 @@ HEALTH_FILE = os.getenv("HEALTH_FILE", "/tmp/healthy")
verify_ssl_env = os.getenv("NETBOX_SSL_VERIFY", "true").lower()
VERIFY_SSL = verify_ssl_env in ("true", "1", "yes")
# Zusätzlicher Filter für IP-Abfragen (z.B. "&vrf=internal" oder "&tag__n=external")
EXTRA_FILTER = os.getenv("NETBOX_EXTRA_FILTER", "")
# Fallback Konfiguration (wenn KEIN NS in NetBox gefunden wird)
FALLBACK_NS_HOSTNAME = os.getenv("FALLBACK_NS_HOSTNAME", "ns1")
FALLBACK_NS_IP = os.getenv("FALLBACK_NS_IP", "127.0.0.1")
@@ -57,7 +60,7 @@ def fetch_ipam_data():
Holt alle aktiven IPs mit DNS-Namen aus NetBox.
Wirft eine Exception, wenn NetBox nicht erreichbar ist.
"""
url = f"{NETBOX_URL}/api/ipam/ip-addresses/?status=active&dns_name__n=&limit=0"
url = f"{NETBOX_URL}/api/ipam/ip-addresses/?status=active&dns_name__n=&limit=0{EXTRA_FILTER}"
log(f"Abruf IPAM: {url}")
r = requests.get(url, headers=HEADERS, timeout=10, verify=VERIFY_SSL)
@@ -168,6 +171,31 @@ def generate_zone_file_fwd(ipam_data, plugin_records):
short_name = dns_name.replace(f".{ZONE_NAME}", "")
if short_name == "": short_name = "@"
# Check: DNS Name muss zum Device/VM Namen passen (wenn zugewiesen)
# Verhindert, dass externe IPs auf anderen Hosts (Split-Brain) hier landen.
assigned = ip.get('assigned_object')
if assigned:
device_name = None
if 'device' in assigned and assigned['device']:
device_name = assigned['device']['name']
elif 'virtual_machine' in assigned and assigned['virtual_machine']:
device_name = assigned['virtual_machine']['name']
if device_name:
# Case-Insensitive Vergleich
d_norm = device_name.lower()
s_norm = short_name.lower()
# Erlaube Match mit Shortname ODER Full-FQDN als Devicename
fqdn_norm = dns_name.lower().rstrip('.')
if d_norm != s_norm and d_norm != fqdn_norm:
# Ausnahme: Wenn der Shortname "device-name-irgendwas" ist?
# Nein, User will strikte Trennung.
# Wir loggen das als Info, damit man es debuggen kann.
# log(f"DEBUG: Skipping {dns_name} on device {device_name} (Mismatch)")
continue
if ":" in address:
rtype = "AAAA"
else: