Dynamic DNS selbst gemacht

It's a maexotic world ...


Da wir mehrere Linux/Unix-Rechner im Familienumfeld hinter DSL-Zugängen stehen haben und diese betreuen, ist es ziemlich essentiell, diese auch zu finden. Der Standardansatz sind hier sicherlich dynamische DNS-Updates, vor allem, wenn dem Benutzer nicht zugemutet werden will mit der Kommandozeile zu arbeiten oder in der Routerkonfiguration zu navigieren, um die externe IP-Adresse festzustellen.

Es gibt zwar jede Menge Anbieter für dynamische DNS Services, hat man aber - wie wir - eigene Domains und macht auch den DNS Service selbst, so kann man sich den Rückgriff auf diese Anbieter sparen.
Ziemlich verwunderlich finde ich, dass die meisten Hoster, die auch Domainhosting anbieten, nicht ermöglichen, dass man NS-Einträge für die Delegation von Subdomains konfiguriert. Dass sie mit den ganzen dynamischen Updates und dem Key-Management nix zu tun haben wollen kann ich ja aus Gründen der Arbeitslast noch verstehen, aber nicht mal NS-Records per Interface?

Nachfolgend eine kleine Anleitung ...

Auch wenn ich sonst kein riesen Fan des BIND bin, habe ich ihn hierfür verwendet.

Zuerst muss man eine Zone festlegen. Natürlich kann man auch eine bestehende verwenden, aber ich trenne immer gerne und verwende deshalb eine Subdomain. Für unser Beispiel wollen wir dyn.example.com verwenden.

Zur Autorisierung - Schlüssel generieren

(Kleiner Hinweis an Möchtegern-Hacker: ich bin nicht ganz blöd, und deshalb habe ich extra für diesen Artikel alles speziell erstellt und nicht irgendetwas "anonymisiert". Die Schlüssel sind definitiv nicht in Produktion, auch nicht unter anderem Namen.)

Das Problem bei dynamisch ist, dass man eben gerade keine festen IP-Adressen hat, auf die man ACLs konfigurieren kann. Natürlich will man aber auch nicht, dass jederman beliebige Updates durchführen kann und man will auch nicht, dass Benutzer-A Updates für Benutzer-B durchführt (und vice versa). Die Lösung sind Host-Keys, also kryptographische Schlüssel. Nur wer den entsprechenden Schlüssel hat, kann ein Update durchführen. Prinzipiell gibt es zwei verschiedene Arten von Schlüsseln: TSIG Keys und SIG(0) Keys. Beide haben Vor- und Nachteile, die z.B. im Artikel "Secure dynamic DNS howto" beschrieben sind. Dieser Artikel liefert darüberhinaus noch einiges an Hintergrundinformationen zu dynamischen DNS-Updates.

Im Beispiel wollen wir zwei dynamischen Hosts alice und bob mit Hilfe von TSIG Keys Zugriff erlauben und verwenden der Übersichtlichkeit wegen den Hostnamen als Namen für die Schlüssel.

Erstellt werden die Schlüssel mit dem Programm dnssec-keygen, das Bestandteil des BIND Pakets ist (oder sein sollte!).

dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST alice.dyn.example.com
dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST bob.dyn.example.com

Das Ergebnis sind vier Dateien, die ähnlich heißen wie:

Kalice.dyn.example.com.+165+nnnnn.key
Kalice.dyn.example.com.+165+nnnnn.private
Kbob.dyn.example.com.+165+nnnnn.key
Kbob.dyn.example.com.+165+nnnnn.private

Wir verwenden den Inhalt der Dateien mit der Endung .private, der z.B. so aussieht:

Private-key-format: v1.2
Algorithm: 165 (HMAC_SHA512)
Key: ICkAUGTxWMAI2q8C04NOF7gk8mmoI42D6rJZ4TofMgmsQMpg/kh5Hw4you+U3QWn6jHjFMCRZ4gbJzZ4R3tOlg==
Bits: AAA=

DNS Server Konfiguration

Zuerst muss der BIND über die verwendeten Schlüssel Bescheid wissen. Dies erfolgt über die key-Anweisung. Der Name des Schlüssels ist der Name, mit dem wir ihn generiert haben, der algorithm ist "HMAC_SHA512" (damit haben wir den Schlüssel mit dnssec-keygen generiert) und als secret verwenden wir das, was jeweils in der .private-Datei hinter Key: steht:

key "alice.dyn.example.com." {
    algorithm "HMAC_SHA512";
    secret "ICkAUGTxWMAI2q8C04NOF7gk8mmoI42D6rJZ4TofMgmsQMpg/kh5Hw4you+U3QWn6jHjFMCRZ4gbJzZ4R3tOlg==";
};
key "bob.dyn.example.com." {
    algorithm "HMAC_SHA512";
    secret "pZJvLSDoBhddxWl+TqTUZ1nPXzvvHTwDBVGb9xl1Q6afEEuCcUya7yWKrosFHZAgTQXKBw0SfXVge7nMQUNZrg==";
};

Als nächstes konfiguriert man die Zone. Welcher Schlüssel welche Records der Zone aktualisieren darf steht in der update-policy-Anweisung:

zone "dyn.example.com" {
    type master;
    allow-query {any; };
    file "dynamic/dyn.example.com";
    update-policy {
        grant alice.dyn.example.com. name alice.dyn.example.com;
        grant   bob.dyn.example.com. name   bob.dyn.example.com;
    };
};

Die grant-Anweisung kann man auf bestimmte DNS Typen einschränken, also z.B.:

    grant bob.dyn.example.com. name bob.dyn.example.com A TXT;

Man könnte für die grant-Anweisung, da wir Hostnamen und Schlüssel gleich benannt haben, sogar verkürzt schreiben:

    grant * self * A TXT;

Nach einem reload des DNS Servers ist der serverseitige Teil erledigt:

# rndc reconfig

Der Client

Auf Seite des Clients stellen sich zwei Anforderungen:

  1. Herausfinden der externen IP-Adresse
  2. Durchführen des DNS-Updates

Ein sehr einfacher, aber wirkungsvoller Ansatz die externe IP-Adresse zu erfahren ist der gleiche, den die Anbieter von dynamischen DNS-Services auch verfolgen: man macht eine Verbindung "nach draussen" auf und der Verbindungspartner liefert die IP-Adresse zurück. Recht leicht ist das mit einen CGI-Skript erledigt:

#!/bin/sh
echo 'Content-type: text/plain'
echo ''
echo "${REMOTE_ADDR}"
exit 0

Zusammen mit ein paar Zeilen Shell-Code ist der updater dann auch gleich fertig:

#!/bin/sh

NAMESERVER=ns.example.com
HOST=alice.dyn.example.com.
TSTAMP=`date --rfc-3339=ns`
TTL=60
KEY="/etc/dnskeys/Kalice.dyn.example.com.+165+nnnnn.key"

WAN=`curl \
        --max-time 30                   \
        --user-agent 'DDNS-maex/1.0'    \
        'http://www.example.com/cgi-bin/getextip' 2>/dev/null`

[ $? -ne 0 -o "" = "${WAN}" ] && {
    echo "$0: curl failed"
    exit 1
}

/usr/bin/nsupdate -k  "${KEY}" << _EOUPDATE_ >/dev/null
server ${NAMESERVER}
update delete ${HOST} A
update delete ${HOST} TXT
update add ${HOST} ${TTL} A ${WAN}
update add ${HOST} ${TTL} TXT "${TSTAMP}"
show
send
_EOUPDATE_
  • Ich verwende einen eigenen User-Agent, damit ich bei den Webserver Logfiles die Zugriffe auseinander sortieren kann
  • Die .key-Datei habe ich in ein Verzeichnis /etc/dnskeys kopiert
  • Das Script lasse ich von cron alle 10 Minuten ausführen.
  • Der Update-Schlüssel sollte RO nur für einen Benutzer sein, also
    # chown <benutzer> /etc/dnskeys/Kalice.dyn.example.com.+165+nnnnn.key
    # chmod 400 /etc/dnskeys/Kalice.dyn.example.com.+165+nnnnn.key
    Unter der Berechtigung dieses Benutzers soll dann auch der cronjob laufen.

Eleganter ist es natürlich den DSL-Router, wenn er verbunden ist, per UPnP nach der externen IP-Adresse zu fragen und mit dieser das Update durchzuführen. Das UPnP Forumâ„  hat dafür eine Reihe von Spezifikationen, relevant ist das PDF mit dem Titel "WANIPConnection:1 Service Template Version 1.01". Darin werden State Variables und Actions beschrieben, mit denen sich kompatible Router abfragen lassen.

Das lässt sich aber nicht mehr mit ein paar Zeilen Shell-Code sauber erledigen, so dass ich dafür ein kleines python-Programm names pydnsup geschrieben habe. Viel Spaß damit :-)




Comments

    • Posted byflybyray
    • on
    Einige Router wie die in Deutschland weitverbreitete Fritzbox haben ein integrierten Dynamic DNS Client. Ist es möglich mit deiner beschriebenen Technik ein DNS-Update mit der Fritzbox zu senden?
    Reply
    • Posted byMarkus Stumpf
    • on
    Nicht dass ich wüsste.
    Alle diese dynamic DNS Anwendungen (zumindest die in der Fritz!Box vorkonfigurierten und die frei definierbare) arbeiten User+Passwort basiert und nicht per DNSSEC Crypto-Schlüssel.
    Zudem arbeiten die soweit ich weiss alle per Web und nicht per DNS updates.
    Reply