Virtualisierter Server – automatische DNS-Einträge für IPv6-Adressen mit wechselndem Präfix
In diesem Beitrag erkläre ich, wie ich stets aktuelle DNS-Einträge für die IPv6-Adressen meiner VMs sicherstelle, auch wenn sich das IPv6-Präfix ändert, weil mir der Provider ein neues zugewiesen hat. Dazu habe ich ein mehrteiliges System programmiert, bei dem Clients auf den VMs ihre aktuelle IP-Adresse an einen zentralen Server im Netzwerk schicken, welcher notwendige DNS-Änderungen bei CloudFlare, meinem DNS-Hoster im Internet, vornimmt.
Weitere Beiträge aus dieser Reihe:
- Puppet-Module: Apache-VHosts mit SSL und Kerberos aus FreeIPA sichern
- Kerberos-basiertes Single-Sign-On (SSO) für SSH und Firefox
- Authentifizierung gegen FreeIPA für Proxmox, pfsense, Puppet und Postfix
- Domäne mit FreeIPA
- Puppet strukturieren mit Profilen, Environments, r10k und git
- Mail-Relay für die VMs mit Postfix und Sendgrid
- SSL überall mit Let’s Encrypt, verteilt durch Puppet
- Puppet Server aufsetzen
- pfsense-Firewall zur Einteilung des Netzwerks mit ipv4 und ipv6
- IPv6-Vorüberlegungen
- Hardware-Setup und Proxmox
Hintergrund
Ich möchte in meinem Netzwerk IPv6 mit DNS
verwenden,
dazu sind AAAA
-Einträge notwendig (IPv4 hat
A
-Einträge). Die IP-Adressen der VMs sind
allerdings nicht stabil genug, um einfach statische Einträge im DNS
anzulegen und die Sache damit abzuhaken. Alle 24 Stunden bekomme ich von
meinem Provider ein neues IPv6-Präfix, d.h. die ersten 56 Bit der
Adressen ändern sich. Der „hintere” Teil bleibt gleich. Ich bekomme also
zwar 4722366482869645213696 Adressen zu meiner Verfügung, aber eben
jeden Tag andere 4722366482869645213696 Adressen; Knappheit der Adressen
kann nicht der Grund sein. Dazu kommt außerdem noch, dass ich auch neue
VMs anlege und die auch gleich im DNS erscheinen sollen.
Für IPv4-Adressen klappt das in meinem Netzwerk bereits automatisch. pfsense stellt einen DHCP-Server zur Vergabe der Adressen bereit und die dort registrierten VMs schicken bei der Anfrage nach einer IP-Adresse auch gleich ihren Hostname mit, also kann auch automatisch eine Zuordnung zwischen DNS-Eintrag und dazugehöriger IP-Adresse festgelegt werden. Bei IPv6 ist das leider anders: Es ist mit automatischer Konfiguration der IP-Adressen (SLAAC) erst mal gar nicht notwendig, überhaupt DHCP zu verwenden, aber auch wenn man DHCPv6 verwendet, ist der Hostname der VM kein Teil der Anfrage – eine automatische Zuordnung von Hostname zu IPv6-Adresse ist also nicht mehr trivial möglich.
Weiterhin bekommt man im Fall von IPv4 daheim meist nur eine Adresse,
die sich ebenfalls regelmäßig ändert. Nach außen hin teilen sich
zwangsweise alle VMs die selbe IPv4-Adresse. Notfalls muss ein
Proxyserver ggf. Anfragen auf die jeweils zuständigen Hosts verteilen,
wenn man von außen auf verschiedene Dienste zugreifen will, die alle den
gleichen Port (z.B. 443
für HTTPS) verwenden.
Bei IPv6 kann man das auch anders lösen: öffnet man den Port in der
Firewall (in meinem Fall in der FritzBox und im pfsense), kann man von
außen direkt die VM ansprechen.
Mit dieser Kombination von Schwierigkeiten habe ich mich entschieden, ein verhältnismäßig einfaches System zu programmieren: Auf jeder VM läuft ein Client, der regelmäßig die (öffentlich routbare, also kein ULA) IPv6-Adresse an einen zentralen Server im Netzwerk schickt, der darüber Buch führt und bei Änderungen die notwendigen Updates im DNS von CloudFlare macht.
Setup
Das Setup besteht aus mehreren Komponenten:
- Das Tool selbst
- Server: mit Ruby/Sinatra programmiert, liegt auf einer zentralen
VM, akzeptiert Aktualisierung der IP-Adresse eines Hosts
über
HTTP POST
-Anfrage. - Client: mit Ruby programmiert, läuft auf jeder VM, schickt
regelmäßig die aktuelle IP-Adresse der VM an den zentralen
Server per
HTTP POST
, mit Authentifizierung. - Worker: mit Ruby programmiert, spricht die CloudFlare-API an und aktualisiert dort die DNS-Einträge.
- Server: mit Ruby/Sinatra programmiert, liegt auf einer zentralen
VM, akzeptiert Aktualisierung der IP-Adresse eines Hosts
über
- Proxyserver: Zum Beispiel Apache oder Nginx, ist dem Server vorgeschaltet und kümmert sich um Authentifizierung (gegen FreeIPA in meinem Fall) und terminiert SSL.
- Puppet: Ein Puppet-Modul zur Installation des Tools sowie ein Profil zur Konfiguration der notwendigen Einstellungen drum herum setzen die ganze Sache in meiner Infrastruktur auf.
Automatische Installation
Wie das meiste andere an meinem Setup findet auch die Installation dieses Tools mit Puppet statt. Dazu habe ich ein Modul in der Puppet Forge hochgeladen: fheinle/ddnsv6. Mit Hilfe dieses Moduls ist es recht einfach, sich die Infrastruktur entsprechend zu konfigurieren:
class profiles::ddns {
$ddns_server = lookup('profiles::ddns::server', String, 'first')
$ddns_server_ssl = lookup('profiles::ddns::server_ssl', Boolean, 'first', true)
$ddns_server_port = lookup('profiles::ddns::server_port', Integer, 'first', 443)
$ddns_user = lookup('profiles::ddns::ddns_user', String, 'first', 'ddns')
$install_client = lookup('profiles::ddns::install_client', Boolean, 'first', true)
$install_server = lookup('profiles::ddns::install_server', Boolean, 'first', false)
$install_directory = lookup('profiles::ddns::install_directory', String, 'first', "/home/${ddns_user}/app")
$domain_whitelist = lookup('profiles::ddns::domain_whitelist', Array, 'unique')
$cloudflare_user = lookup('cloudflare_user', String, 'first')
$cloudflare_password = lookup('cloudflare_password', String, 'first')
$client_username = lookup('profiles::ddns::client_username', String, 'first')
$client_password = lookup('profiles::ddns::client_password', String, 'first')
file { 'ddns_home_dir':
ensure => 'directory',
path => "/home/${ddns_user}",
owner => $ddns_user,
mode => '0700',
}
class { '::ddns':
install_directory => $install_directory,
install_repo => $install_repo,
create_ddns_user => false
}
if $install_client == true {
class {'::ddns::client':
ddns_server => $ddns_server,
ddns_server_ssl => $ddns_server_ssl,
ddns_server_port => $ddns_server_port,
client_username => $client_username,
client_password => $client_password
}
}
elsif $install_server == true {
class { '::ddns::server':
domain_whitelist => $domain_whitelist,
cloudflare_user => $cloudflare_user,
cloudflare_password => $cloudflare_password
}
}
}
Gleich ganz oben werden dabei einige Anfragen an Hiera gestellt, um
dort die notwendigen Einstellungen zu beziehen. Diese müssen natürlich
entsprechend der Parameter hinterlegt sein. Die meisten Parameter können
irgendwo global hinterlegt werden, in einer
common.yaml
oder so. Die Unterscheidung zwischen
client und server wird jedoch auch im Hiera gemacht.
Alle am IPv6-Betrieb beteiligten Hosts brauchen die Klasse
::ddns
.
In der Konfiguration des Servers (z.B. über
nodes/server.domain.invalid
) kann dann
profiles::ddns::install_client
auf
false
und
profiles::ddns_install_server
auf
true
gesetzt werden. Analog kann der Client
konfiguriert werden, mit
profiles::ddns::install_client
auf
true
und
profiles::ddns::install_server
auf
false
.
Benutzung
Das Setup ist mit seinen Cronjobs weitestgehend automatisiert und man muss maximal 10 Minuten warten, wenn ein Präfixwechsel erfolgt ist, bis die neuen IP-Adressen an CloudFlare übertragen wurden. Bei der ersten Installation muss allerdings Puppet zwei mal aufgerufen werden, damit zuerst die Clients ihre Hostnames als Fragmente für die Whitelist hinterlegen und beim zweiten Durchlauf der Server diese Fragmente einsammelt und zur Whitelist zusammenführt. Etwas umständlich, aber so funktionieren exported resources bei Puppet eben.