Dynamisches DNS mit dem bind DNS server



Der bekannte DNS server bind erlaubt Konfigurationen die es ermöglichen, dass clients ihre DNS Einträge selbst verändern können. Weil einige öffentliche dynamische DNS Services zu einem Bezahlmodell gewechselt sind und weil ich sowieso meinen eigenen Mail- und Webserver bei einem Hoster betreibe, suchte ich nach einer Möglichkeit, meinen eigenen dynamischen DNS Server zu verwenden. Das liegt schon ein paar Jahre zurück, aber weil das von mir damals verwendete Howto vom Netz (oder zumindest aus meiner Google-Bubble) verschwunden ist, dokumentiere ich hier wie ich das gemacht habe.

Die Software läuft bei mir unter Debian Buster zur Zeit dieses Blog-Eintrags, die Details können sich also bei anderen Systemen ändern. In den folgenden Beispielen nenne ich die Subdomain für die dynamischen DNS Einträge dyn.example.com.

Die Konfigurations-Datei von bind braucht ein zusätzliches include um die Konfigurations-Datei für die dynamische Domain einzubinden. In meiner Konfiguration heisst diese Datei named.conf.handedited. In dieser Datei braucht es einen Entrag für jeden dynamischen DNS Client wie folgt:

zone "dyn.example.com" {
        type master;
        allow-transfer {none;};
        file "/etc/bind/slave/pri.dyn.example.com";
        update-policy {
            grant h1.dyn.example.com. name h1.dyn.example.com. A TXT;
            grant h2.dyn.example.com. name h2.dyn.example.com. A TXT;
            [...]
        };
};

In diesem Beispiel dürfen die Hosts h1 und h2 (und möglicherweise weitere) ihre eigenen DNS Einträge ändern. Ich erlaube hier dass sie den A und TXT Record ändern. Für IPv6 möchte man hier noch AAAA dazugeben.

Die Konfigurations-Datei /etc/bind/slave/pri.dyn.example.com enthält dann:

dyn.example.com         IN SOA  ns1.example.com. admin.example.com. (
                                2020080100 ; serial
                                120      ; refresh (2 minutes)
                                120      ; retry (2 minutes)
                                120      ; expire (2 minutes)
                                120      ; minimum (2 minutes)
                                )
                        NS      ns1.example.com.
h1                      A       127.0.0.1
                        KEY     512 3 10 (
                                AwEAAdEvnGmGO4ku+xms4w1c5RWh5BvugiZ4ty9tkIes
                                <more gibberish lines>
                                ); alg = RSASHA512 ; key id = <number>

Die Einträge in spitzen Klammern sind Kommentare und sollten durch die korrekten Werte in der Zielinstallation ersetzt werden. Die Einträge A und KEY werden von Hand für jeden neuen Host der seine eigene IP-Adresse setzen können soll eingetragen. Der KEY ist der öffentliche Schlüssel (public key) der weiter unten erzeugt wird. Nach meiner Erfahrung braucht es den A-Eintrag damit das ganze funktioniert. Ich setze hier eine localhost Adresse weil der Client diese später sowieso überschreiben wird. Es ist üblich die Admin Email-Adresse (wo @ durch einen Punkt ersetzt ist) im SOA-Eintrag an die Stelle zu setzen wo ich admin.example.com. eingetragen habe.

Um einen neuen Client Host anzulegen geht man wie folgt vor:

  • Erzeugen eines Schlüsselpaares (öffentlicher / privater Schlüssel, public/private key), bevorzugt passiert das beim Client und dieser sendet nur den öffentlichen Schlüssel an den DNS-Administrator um Sicherheitsprobleme bei der Schlüssel-Übermittlung zu vermeiden:

    dnssec-keygen -T key -a RSASHA512 -b 2048 -n HOST newhost.dyn.example.com
    
  • Das erzeugt einen privaten und einen öffentlichen Schlüssel. Zu beachten: Der Client braucht beide Schlüssel, sowohl den öffentlichen als auch den privaten, obwohl nur der private Schlüssel dem dynamischen DNS Client übergeben wird!

  • Das letzte Mal als ich einen neuen Schlüssel erzeugt habe, konnte das Kommando keine Schlüssel mit mehr als 2048 bits erzeugen, obwohl der verwendete Hash-Algorithmus SHA2 modern ist und eine große Bitlänge unterstützt.

  • Damit bind seine Datenbank auf eine Datei schreibt (und man diese editieren darf) muss man bind für diese Domain einfrieren:

    rndc freeze dyn.example.com
    
  • Jetzt darf man das Konfigurationsfile ändern und einen neuen Absatz für einen neuen Host eintragen. Nicht vergessen die serial Nummer zu erhöhen:

    $EDITOR /etc/bind/slave/pri.dyn.example.com
    
  • Dann nicht vergessen, die Domain wieder aufzutauen:

    rndc unfreeze dyn.example.com
    
  • Und schließlich nicht vergessen, dem neuen Host die nötigen Berechtigungen in named.conf.handedited zu geben.

  • Zuletzt muss man wohl dem bind ein reload geben:

    systemctl reload bind9.service
    

Auf der Client-Seite heisst das Programm um bind mitzuteilen dass sich die IP-Adresse des Client geändert hat nsupdate. Man findet diese Programm für viele Betriebssysteme.

Ich verwende ein simples Script am Client das die Änderung der dynamischen IP Adresse bemerkt und eine Adressänderung bei bind anstößt wenn sich die IP Adresse geändert hat. Nachdem wir unseren eigenen DNS-Server betreiben sind die Chancen groß, dass es dort auch einen eigenen Webserver gibt. Das folgende einfache Script erlaubt es dem Client, seine eigene IP-Adresse rauszufinden (Clients sind oft hinter einer NAT Firewall und wir wollen ja nicht wieder auf ein anderes öffentliches Service angewiesen sein, wenn wir gerade öffentliche dynamische DNS Services losgeworden sind, oder?):

#!/bin/sh
echo Content-Type: text/plain
echo ""
echo $REMOTE_ADDR

Dieses Script installiert man in ein cgi-bin Verzeichnis auf einem Webserver. Es liefert dem anfragenden Client seine eigene IP-Adresse (in Text Format) zurück. Das script heisst bei mir ip.cgi und ist dann unter der URL http://example.com/cgi-bin/ip.cgi in unserem Beispiel verfügbar. Die URL muss dann unten im Client-Script geändert werden.

Mein bind Script (einige Variablen müssen geändert werden) schaut aus wie folgt (bitte beachten: Das Script geht davon aus dass obiges IP-Adressen Script auf der Domain example.com läuft, das muss natürlich auch geändert werden):

#!/bin/sh
ZONE=dyn.example.com
DOMAIN=h1.dyn.example.com
DNSKEY=/etc/nsupdate/Kh1.dyn.example.com.+010+04711.private
NS="ns1.example.com"

registered=$(host $DOMAIN $NS 2> /dev/null | grep 'has address' | tail -n1 | cut -d' '  -f4)
current=$(wget -q -O- http://example.com/cgi-bin/ip.cgi)
[ -n "$current" \
-a "(" "$current" != "$registered" ")" \
] && {
 nsupdate -d -v -k $DNSKEY << EOF
server $NS
zone $ZONE
update delete $DOMAIN A
update add $DOMAIN 60 A $current
send
EOF
 logger -t dyndns -p daemon.notice "Updated dyndns: $registered -> $current"
} > /dev/null 2>&1

exit 0

Es sei nochmal darauf hingewiesen, dass der private Schlüssel (in obigem Beispiel in /etc/nsupdate/Kh1.dyn.example.com.+010+04711.private nicht ausreicht, das Programm nsupdate braucht auch den öffentlichen Schlüssel im selben Verzeichnis wie das des privaten Schlüssels.