Refactor: Generalisierung der Konfiguration und atomares Schreiben der Zonefiles
This commit is contained in:
18
.env.example
18
.env.example
@@ -5,9 +5,15 @@
|
|||||||
NETBOX_URL=http://netbox.example.com
|
NETBOX_URL=http://netbox.example.com
|
||||||
NETBOX_TOKEN=0123456789abcdef0123456789abcdef01234567
|
NETBOX_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||||
|
|
||||||
# --- Optional (Defaults shown) ---
|
# --- Configuration (Defaults shown are generic) ---
|
||||||
# ZONE_NAME=klenzel.net
|
ZONE_NAME=example.com
|
||||||
# REVERSE_ZONE_NAME=172.in-addr.arpa
|
REVERSE_ZONE_NAME=1.168.192.in-addr.arpa
|
||||||
# REFRESH_INTERVAL=600
|
REFRESH_INTERVAL=600
|
||||||
# FALLBACK_NS_HOSTNAME=fks-01-cl-cdns
|
|
||||||
# FALLBACK_NS_IP=172.25.16.152
|
# Paths inside the container
|
||||||
|
OUTPUT_FILE_FWD=/zones/db.fwd
|
||||||
|
OUTPUT_FILE_REV=/zones/db.rev
|
||||||
|
|
||||||
|
# Fallback Nameserver (used if no NS records found in NetBox)
|
||||||
|
FALLBACK_NS_HOSTNAME=ns1
|
||||||
|
FALLBACK_NS_IP=127.0.0.1
|
||||||
12
Dockerfile
12
Dockerfile
@@ -2,13 +2,13 @@ FROM python:3.11-alpine
|
|||||||
|
|
||||||
# Defaults für nicht-sensitive Konfiguration
|
# Defaults für nicht-sensitive Konfiguration
|
||||||
ENV REFRESH_INTERVAL=600 \
|
ENV REFRESH_INTERVAL=600 \
|
||||||
ZONE_NAME="klenzel.net" \
|
ZONE_NAME="example.com" \
|
||||||
REVERSE_ZONE_NAME="172.in-addr.arpa" \
|
REVERSE_ZONE_NAME="1.168.192.in-addr.arpa" \
|
||||||
OUTPUT_FILE_FWD="/zones/db.klenzel.net" \
|
OUTPUT_FILE_FWD="/zones/db.fwd" \
|
||||||
OUTPUT_FILE_REV="/zones/db.reverse.arpa" \
|
OUTPUT_FILE_REV="/zones/db.rev" \
|
||||||
DEFAULT_TTL=3600 \
|
DEFAULT_TTL=3600 \
|
||||||
FALLBACK_NS_HOSTNAME="fks-01-cl-cdns" \
|
FALLBACK_NS_HOSTNAME="ns1" \
|
||||||
FALLBACK_NS_IP="172.25.16.152"
|
FALLBACK_NS_IP="127.0.0.1"
|
||||||
|
|
||||||
# Hinweis: NETBOX_URL und NETBOX_TOKEN müssen zur Laufzeit übergeben werden!
|
# Hinweis: NETBOX_URL und NETBOX_TOKEN müssen zur Laufzeit übergeben werden!
|
||||||
|
|
||||||
|
|||||||
64
GEMINI.md
Normal file
64
GEMINI.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# CoreDNS NetBox Sync
|
||||||
|
|
||||||
|
This project automates the generation of DNS zone files for CoreDNS (or BIND) by synchronizing data from [NetBox](https://github.com/netbox-community/netbox). It fetches IPAM data (active IPs with DNS names) and DNS plugin entries to maintain up-to-date Forward and Reverse zones.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
* **Core Logic:** A Python script (`sync.py`) runs in a continuous loop, fetching data from the NetBox API.
|
||||||
|
* **Output:** Generates standard DNS zone files compatible with CoreDNS and BIND.
|
||||||
|
* **Resilience:** If the NetBox API is unreachable, the script preserves existing zone files to prevent DNS outages (NXDOMAIN).
|
||||||
|
* **Environment:** designed to run as a Docker container, sharing the generated zone files via a volume with the CoreDNS container.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
* `sync.py`: The main application logic. Handles API authentication, data fetching, data formatting, and file writing.
|
||||||
|
* `Dockerfile`: Defines the minimal Python 3.11 Alpine-based image for running the script.
|
||||||
|
* `.env.example`: Template for required environment variables.
|
||||||
|
* `README.md`: Official project documentation (German).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuration is handled entirely via environment variables.
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
| :--- | :---: | :--- | :--- |
|
||||||
|
| `NETBOX_URL` | Yes | - | Full URL to NetBox (e.g., `http://netbox.local`). |
|
||||||
|
| `NETBOX_TOKEN` | Yes | - | API Token (Read-only sufficient). |
|
||||||
|
| `ZONE_NAME` | No | `klenzel.net` | The DNS zone to manage. |
|
||||||
|
| `REVERSE_ZONE_NAME` | No | `172.in-addr.arpa` | The reverse lookup zone. |
|
||||||
|
| `REFRESH_INTERVAL` | No | `600` | Sync interval in seconds. |
|
||||||
|
| `OUTPUT_FILE_FWD` | No | `/zones/db.klenzel.net` | Path for the forward zone file. |
|
||||||
|
| `OUTPUT_FILE_REV` | No | `/zones/db.reverse.arpa` | Path for the reverse zone file. |
|
||||||
|
| `FALLBACK_NS_HOSTNAME`| No | `fks-01-cl-cdns` | Fallback NS hostname if none in NetBox. |
|
||||||
|
| `FALLBACK_NS_IP` | No | `172.25.16.152` | Fallback NS IP for glue record. |
|
||||||
|
|
||||||
|
## Development & Usage
|
||||||
|
|
||||||
|
### Building the Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t local/dns-sync .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Locally (for testing)
|
||||||
|
|
||||||
|
1. Create a `.env` file with your NetBox credentials.
|
||||||
|
2. Run the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name dns-sync-test \
|
||||||
|
--env-file .env \
|
||||||
|
-v $(pwd)/zones:/zones \
|
||||||
|
local/dns-sync
|
||||||
|
```
|
||||||
|
|
||||||
|
*(Ensure the `./zones` directory exists locally before running)*
|
||||||
|
|
||||||
|
### Logic Details
|
||||||
|
|
||||||
|
* **IPAM Fetch:** Queries `/api/ipam/ip-addresses/?status=active&dns_name__n=&limit=0`.
|
||||||
|
* **Plugin Fetch:** Queries `/api/plugins/netbox-dns/records/?zone__name={ZONE_NAME}&limit=0`.
|
||||||
|
* **Nameserver Logic:**
|
||||||
|
* If NetBox has NS records for the zone, the first one is used as the Primary SOA.
|
||||||
|
* If **no** NS records exist, it falls back to `FALLBACK_NS_HOSTNAME` and creates a Glue Record (A record) for it to ensure the zone is valid.
|
||||||
63
README.md
63
README.md
@@ -4,8 +4,8 @@ Dieser Container automatisiert die Generierung von DNS-Zonendateien für CoreDNS
|
|||||||
|
|
||||||
## Funktionen
|
## Funktionen
|
||||||
|
|
||||||
* **Automatische Synchronisation:** Ruft periodisch Daten von NetBox ab (Standard: alle 10 Minuten).
|
* **Automatische Synchronisation:** Ruft periodisch Daten von NetBox ab.
|
||||||
* **Ausfallsicherer Betrieb:** Wenn NetBox nicht erreichbar ist oder Fehler zurückgibt, werden die bestehenden Zonendateien beibehalten, um `NXDOMAIN`-Probleme zu vermeiden.
|
* **Ausfallsicherer Betrieb:** Wenn NetBox nicht erreichbar ist oder Fehler zurückgibt, werden die bestehenden Zonendateien beibehalten. Das Schreiben erfolgt atomar, um Korruption bei Abstürzen zu verhindern.
|
||||||
* **Dual-Zone-Unterstützung:** Generiert sowohl Forward- als auch Reverse-(PTR)-Zonen.
|
* **Dual-Zone-Unterstützung:** Generiert sowohl Forward- als auch Reverse-(PTR)-Zonen.
|
||||||
* **Intelligenter Fallback:** Konfiguriert automatisch einen Fallback-Nameserver, falls in NetBox keine NS-Einträge definiert sind.
|
* **Intelligenter Fallback:** Konfiguriert automatisch einen Fallback-Nameserver, falls in NetBox keine NS-Einträge definiert sind.
|
||||||
|
|
||||||
@@ -19,55 +19,54 @@ Die Konfiguration erfolgt vollständig über Umgebungsvariablen.
|
|||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
| `NETBOX_URL` | Die vollständige URL zur NetBox-Instanz (z.B. `http://netbox.local`). |
|
| `NETBOX_URL` | Die vollständige URL zur NetBox-Instanz (z.B. `http://netbox.local`). |
|
||||||
| `NETBOX_TOKEN` | Das API-Token zur Authentifizierung (Nur-Lese-Berechtigungen sind ausreichend). |
|
| `NETBOX_TOKEN` | Das API-Token zur Authentifizierung (Nur-Lese-Berechtigungen sind ausreichend). |
|
||||||
|
| `ZONE_NAME` | Der Name der zu verwaltenden DNS-Zone (z.B. `example.com`). |
|
||||||
|
| `REVERSE_ZONE_NAME` | Der Name der Reverse-Lookup-Zone (z.B. `1.168.192.in-addr.arpa`). |
|
||||||
|
|
||||||
### Optionale Variablen
|
### Optionale Variablen
|
||||||
|
|
||||||
| Variable | Standardwert | Beschreibung |
|
| Variable | Standardwert | Beschreibung |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `REFRESH_INTERVAL` | `600` | Synchronisationsintervall in Sekunden. |
|
| `REFRESH_INTERVAL` | `600` | Synchronisationsintervall in Sekunden. |
|
||||||
| `ZONE_NAME` | `klenzel.net` | Der Name der zu verwaltenden DNS-Zone. |
|
| `OUTPUT_FILE_FWD` | `/zones/db.fwd` | Pfad im Container für die Forward-Zonendatei. |
|
||||||
| `REVERSE_ZONE_NAME` | `172.in-addr.arpa` | Der Name der Reverse-Lookup-Zone. |
|
| `OUTPUT_FILE_REV` | `/zones/db.rev` | Pfad im Container für die Reverse-Zonendatei. |
|
||||||
| `OUTPUT_FILE_FWD` | `/zones/db.klenzel.net` | Pfad im Container für die Forward-Zonendatei. |
|
| `FALLBACK_NS_HOSTNAME`| `ns1` | Hostname, der als NS-Eintrag verwendet wird, falls keiner in NetBox existiert. |
|
||||||
| `OUTPUT_FILE_REV` | `/zones/db.reverse.arpa` | Pfad im Container für die Reverse-Zonendatei. |
|
| `FALLBACK_NS_IP` | `127.0.0.1` | IP-Adresse für den Fallback-NS-Glue-Record. |
|
||||||
| `FALLBACK_NS_HOSTNAME`| `fks-01-cl-cdns` | Hostname, der als NS-Eintrag verwendet wird, falls keiner in NetBox existiert. |
|
|
||||||
| `FALLBACK_NS_IP` | `172.25.16.152` | IP-Adresse für den Fallback-NS-Glue-Record. |
|
|
||||||
|
|
||||||
## Verwendung
|
## Verwendung
|
||||||
|
|
||||||
### Docker
|
### Docker (CLI Beispiel)
|
||||||
|
|
||||||
1. **Image bauen:**
|
Starten Sie den Container mit folgendem Befehl. Passen Sie die Werte entsprechend Ihrer Umgebung an.
|
||||||
```bash
|
|
||||||
docker build -t local/dns-sync .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Mit Umgebungsvariablen starten:**
|
```bash
|
||||||
```bash
|
docker run -d \
|
||||||
docker run -d \
|
--name netbox-dns-sync \
|
||||||
--name klzDNS-worker \
|
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
--net=container:klzDNS-coredns \
|
--network host \
|
||||||
-v klzDNS-data:/zones \
|
-v ./zones:/zones \
|
||||||
-e NETBOX_URL="http://172.30.242.99" \
|
-e NETBOX_URL="http://netbox.example.com" \
|
||||||
-e NETBOX_TOKEN="dein-geheimes-token" \
|
-e NETBOX_TOKEN="0123456789abcdef0123456789abcdef01234567" \
|
||||||
|
-e ZONE_NAME="example.com" \
|
||||||
|
-e REVERSE_ZONE_NAME="1.168.192.in-addr.arpa" \
|
||||||
|
-e OUTPUT_FILE_FWD="/zones/db.example.com" \
|
||||||
|
-e OUTPUT_FILE_REV="/zones/db.192.168.1" \
|
||||||
|
-e FALLBACK_NS_HOSTNAME="ns1" \
|
||||||
|
-e FALLBACK_NS_IP="192.168.1.5" \
|
||||||
|
-e REFRESH_INTERVAL="300" \
|
||||||
local/dns-sync
|
local/dns-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*(Hinweis: Der Pfad `./zones` muss auf dem Host existieren und schreibbar sein)*
|
||||||
|
|
||||||
### Verwendung einer `.env`-Datei
|
### Verwendung einer `.env`-Datei
|
||||||
|
|
||||||
1. Erstellen Sie eine `.env`-Datei basierend auf dem Beispiel:
|
1. Erstellen Sie eine `.env`-Datei basierend auf dem Beispiel (`.env.example`).
|
||||||
```bash
|
2. Starten Sie den Container:
|
||||||
cp .env.example .env
|
|
||||||
# Bearbeiten Sie die .env und fügen Sie Ihre Zugangsdaten hinzu
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Starten Sie den Container unter Verweis auf die Datei:
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name klzDNS-worker \
|
--name netbox-dns-sync \
|
||||||
--restart unless-stopped \
|
|
||||||
--net=container:klzDNS-coredns \
|
|
||||||
-v klzDNS-data:/zones \
|
|
||||||
--env-file .env \
|
--env-file .env \
|
||||||
|
-v ./zones:/zones \
|
||||||
local/dns-sync
|
local/dns-sync
|
||||||
```
|
```
|
||||||
52
sync.py
52
sync.py
@@ -18,16 +18,16 @@ if not NETBOX_URL or not TOKEN:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Optionale Konfiguration mit Defaults
|
# Optionale Konfiguration mit Defaults
|
||||||
ZONE_NAME = os.getenv("ZONE_NAME", "klenzel.net")
|
ZONE_NAME = os.getenv("ZONE_NAME", "example.com")
|
||||||
REVERSE_ZONE_NAME = os.getenv("REVERSE_ZONE_NAME", "172.in-addr.arpa")
|
REVERSE_ZONE_NAME = os.getenv("REVERSE_ZONE_NAME", "1.168.192.in-addr.arpa")
|
||||||
REFRESH_INTERVAL = int(os.getenv("REFRESH_INTERVAL", "600"))
|
REFRESH_INTERVAL = int(os.getenv("REFRESH_INTERVAL", "600"))
|
||||||
OUTPUT_FILE_FWD = os.getenv("OUTPUT_FILE_FWD", "/zones/db.klenzel.net")
|
OUTPUT_FILE_FWD = os.getenv("OUTPUT_FILE_FWD", "/zones/db.fwd")
|
||||||
OUTPUT_FILE_REV = os.getenv("OUTPUT_FILE_REV", "/zones/db.reverse.arpa")
|
OUTPUT_FILE_REV = os.getenv("OUTPUT_FILE_REV", "/zones/db.rev")
|
||||||
DEFAULT_TTL = os.getenv("DEFAULT_TTL", "3600")
|
DEFAULT_TTL = os.getenv("DEFAULT_TTL", "3600")
|
||||||
|
|
||||||
# Fallback Konfiguration (wenn KEIN NS in NetBox gefunden wird)
|
# Fallback Konfiguration (wenn KEIN NS in NetBox gefunden wird)
|
||||||
FALLBACK_NS_HOSTNAME = os.getenv("FALLBACK_NS_HOSTNAME", "fks-01-cl-cdns")
|
FALLBACK_NS_HOSTNAME = os.getenv("FALLBACK_NS_HOSTNAME", "ns1")
|
||||||
FALLBACK_NS_IP = os.getenv("FALLBACK_NS_IP", "172.25.16.152")
|
FALLBACK_NS_IP = os.getenv("FALLBACK_NS_IP", "127.0.0.1")
|
||||||
|
|
||||||
HEADERS = {'Authorization': f'Token {TOKEN}', 'Accept': 'application/json'}
|
HEADERS = {'Authorization': f'Token {TOKEN}', 'Accept': 'application/json'}
|
||||||
|
|
||||||
@@ -113,6 +113,28 @@ def get_ns_config(plugin_records, all_records):
|
|||||||
|
|
||||||
return primary_ns, extra_header_lines
|
return primary_ns, extra_header_lines
|
||||||
|
|
||||||
|
def write_atomic(filepath, content):
|
||||||
|
"""Schreibt Datei atomar: Erst in .tmp, dann move/replace."""
|
||||||
|
tmp_path = filepath + ".tmp"
|
||||||
|
try:
|
||||||
|
# Verzeichnis sicherstellen
|
||||||
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||||
|
|
||||||
|
with open(tmp_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
f.flush()
|
||||||
|
os.fsync(f.fileno())
|
||||||
|
os.replace(tmp_path, filepath)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log(f"ERROR beim Schreiben von {filepath}: {e}")
|
||||||
|
if os.path.exists(tmp_path):
|
||||||
|
try:
|
||||||
|
os.remove(tmp_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise e
|
||||||
|
|
||||||
def generate_zone_file_fwd(ipam_data, plugin_records):
|
def generate_zone_file_fwd(ipam_data, plugin_records):
|
||||||
"""Erstellt die Forward-Zone"""
|
"""Erstellt die Forward-Zone"""
|
||||||
|
|
||||||
@@ -152,11 +174,10 @@ def generate_zone_file_fwd(ipam_data, plugin_records):
|
|||||||
header.extend(extra_headers)
|
header.extend(extra_headers)
|
||||||
header.append("")
|
header.append("")
|
||||||
|
|
||||||
with open(OUTPUT_FILE_FWD, "w") as f:
|
content = "\n".join(header) + "\n; --- Records ---
|
||||||
f.write("\n".join(header))
|
" + "\n".join(all_records) + "\n"
|
||||||
f.write("\n; --- Records ---\n")
|
|
||||||
f.write("\n".join(all_records))
|
write_atomic(OUTPUT_FILE_FWD, content)
|
||||||
f.write("\n")
|
|
||||||
log(f"SUCCESS: Forward Zone geschrieben ({len(all_records)} Records).")
|
log(f"SUCCESS: Forward Zone geschrieben ({len(all_records)} Records).")
|
||||||
|
|
||||||
def generate_zone_file_rev(ipam_data, plugin_records):
|
def generate_zone_file_rev(ipam_data, plugin_records):
|
||||||
@@ -198,11 +219,10 @@ def generate_zone_file_rev(ipam_data, plugin_records):
|
|||||||
|
|
||||||
header.append("")
|
header.append("")
|
||||||
|
|
||||||
with open(OUTPUT_FILE_REV, "w") as f:
|
content = "\n".join(header) + "\n; --- PTR Records ---
|
||||||
f.write("\n".join(header))
|
" + "\n".join(ptr_records) + "\n"
|
||||||
f.write("\n; --- PTR Records ---\n")
|
|
||||||
f.write("\n".join(ptr_records))
|
write_atomic(OUTPUT_FILE_REV, content)
|
||||||
f.write("\n")
|
|
||||||
log(f"SUCCESS: Reverse Zone geschrieben ({len(ptr_records)} Records).")
|
log(f"SUCCESS: Reverse Zone geschrieben ({len(ptr_records)} Records).")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user