Letsencrypt DNS Verification RFC2136: Unterschied zwischen den Versionen

Aus Xinux Wiki
Zur Navigation springen Zur Suche springen
Neue Seite: DNS-Challenge mit dem Certbot-Plugin dns-rfc2136 (eigener BIND, TSIG, automatische Erneuerung)
 
 
(8 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 17: Zeile 17:
=Wann braucht man das=
=Wann braucht man das=
*Port 80 ist nicht erreichbar oder wird gefiltert (Firewall, Sperrlisten gegen Bots).
*Port 80 ist nicht erreichbar oder wird gefiltert (Firewall, Sperrlisten gegen Bots).
*Wildcard-Zertifikate (<code>*.example.org</code>), das geht nur per DNS-Challenge.
*Wildcard-Zertifikate (<code>*.example.com</code>), das geht nur per DNS-Challenge.
*Das Zertifikat soll '''vor''' einem DNS-Umzug fertig sein, der Name zeigt noch auf den alten Server.
*Das Zertifikat soll '''vor''' einem DNS-Umzug fertig sein, der Name zeigt noch auf den alten Server.
*Die HTTP-Prüfung macht Probleme (Weiterleitungen, mehrere Server hinter einem Namen).
*Die HTTP-Prüfung macht Probleme (Weiterleitungen, mehrere Server hinter einem Namen).
Zeile 45: Zeile 45:
include "/etc/bind/acme-update.key";
include "/etc/bind/acme-update.key";


zone "example.org" {
zone "example.com" {
     type master;
     type master;
     file "/var/lib/bind/db.example.org";
     file "example.com";
     update-policy {
     update-policy {
         grant acme-update name _acme-challenge.example.org.      TXT;
         grant acme-update name _acme-challenge.example.com. TXT;
        grant acme-update name _acme-challenge.www.example.org.  TXT;
        grant acme-update name _acme-challenge.wiki.example.org. TXT;
     };
     };
};
};
</syntaxhighlight>
</syntaxhighlight>
*Pro Zertifikatsname gibt es '''genau einen eigenen''' <code>_acme-challenge</code>-Namen: Für <code>wiki.example.org</code> ist es <code>_acme-challenge.wiki.example.org</code>, für <code>www.example.org</code> entsprechend <code>_acme-challenge.www.example.org</code>.
*Wildcard und Hauptname (<code>example.com</code> und <code>*.example.com</code>) teilen sich '''denselben''' Namen <code>_acme-challenge.example.com</code> — eine einzige Freigabe reicht für beide, dort stehen dann zwei TXT-Werte gleichzeitig.
*Wildcard und Hauptname (<code>example.org</code> und <code>*.example.org</code>) teilen sich '''denselben''' Namen <code>_acme-challenge.example.org</code>, dort stehen dann zwei TXT-Werte gleichzeitig.
*<code>file</code> ohne Pfad und ohne Endung, wie bei den anderen Zonen auch: BIND sucht sie im konfigurierten Zonenverzeichnis.
*Der Schlüssel darf '''nur TXT''' an diesen Namen ändern, sonst nichts in der Zone.
*Zusätzliche, eigenständige Namen außerhalb der Wildcard (z. B. ein eigener vHost wie <code>wiki.example.com</code>, der '''nicht''' im Wildcard-Zertifikat steckt) brauchen einen eigenen Grant: <code>grant acme-update name _acme-challenge.wiki.example.com. TXT;</code> — nur nötig, wenn dafür wirklich ein eigenes Zertifikat per DNS-Challenge ausgestellt wird.
*Der Schlüssel darf '''nur TXT''' an diese Namen ändern, sonst nichts in der Zone.
*Die Kurzform <code>grant acme-update zonesub TXT;</code> erlaubt TXT überall in der Zone. Das ist bequemer, aber weniger streng.
*Die Kurzform <code>grant acme-update zonesub TXT;</code> erlaubt TXT überall in der Zone. Das ist bequemer, aber weniger streng.


;Wichtig: die Zone ist jetzt dynamisch
;Wichtig: die Zone ist jetzt dynamisch
*Die Zonendatei muss für den BIND-Benutzer beschreibbar sein (unter Debian <code>/var/lib/bind/</code>, nicht <code>/etc/bind/</code>).
*Die Zonendatei muss für den BIND-Benutzer beschreibbar sein (unter Debian <code>/var/cache/bind/</code>, Besitzer <code>bind:bind</code> — <code>/etc/bind/</code> und <code>/var/lib/bind/</code> gehören root und sind für den BIND-Prozess nicht beschreibbar).
*Von Hand ändern geht nur noch mit Einfrieren, sonst gehen Änderungen verloren:
*Von Hand ändern geht nur noch mit Einfrieren, sonst gehen Änderungen verloren:
**rndc freeze example.org
**rndc freeze example.com
**Zonendatei bearbeiten
**Zonendatei bearbeiten
**rndc thaw example.org
**rndc thaw example.com
*Neben der Zonendatei liegt ein Journal (<code>.jnl</code>), das gehört zur Zone und darf nicht gelöscht werden.
*Neben der Zonendatei liegt ein Journal (<code>.jnl</code>), das gehört zur Zone und darf nicht gelöscht werden.


Zeile 74: Zeile 73:
=2. Zugang testen (noch ohne Certbot)=
=2. Zugang testen (noch ohne Certbot)=
;Vom Webserver aus, mit kopierter Schlüsseldatei
;Vom Webserver aus, mit kopierter Schlüsseldatei
*nsupdate -k /etc/bind/acme-update.key
Auf dem Webserver ist kein BIND installiert, <code>/etc/bind/</code> gibt es dort also nicht. Die Schlüsseldatei landet einfach irgendwo, z. B. unter <code>/root/</code>:
  server ns1.example.org
*scp /etc/bind/acme-update.key webserver:/root/acme-update.key
  zone example.org
*nsupdate -k /root/acme-update.key
  update add _acme-challenge.wiki.example.org. 60 TXT "test"
  server ns1.example.com
  zone example.com
  update add _acme-challenge.example.com. 60 TXT "test"
  send
  send
  quit
  quit
*dig +short TXT _acme-challenge.wiki.example.org @ns1.example.org
*dig +short TXT _acme-challenge.example.com @ns1.example.com
  "test"
  "test"
;Aufräumen
;Aufräumen
*nsupdate -k /etc/bind/acme-update.key
*nsupdate -k /root/acme-update.key
  server ns1.example.org
  server ns1.example.com
  zone example.org
  zone example.com
  update delete _acme-challenge.wiki.example.org. TXT
  update delete _acme-challenge.example.com. TXT
  send
  send
  quit
  quit
Zeile 112: Zeile 113:


==Zertifikat ausstellen==
==Zertifikat ausstellen==
*certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10 --cert-name wiki.example.org -d wiki.example.org -d example.org --agree-tos -m admin@example.org
*certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10 --cert-name example.com -d example.com -d '*.example.com' --agree-tos -m admin@example.com
;Ergebnis
;Ergebnis
  /etc/letsencrypt/live/wiki.example.org/fullchain.pem
  /etc/letsencrypt/live/example.com/fullchain.pem
  /etc/letsencrypt/live/wiki.example.org/privkey.pem
  /etc/letsencrypt/live/example.com/privkey.pem
;Wildcard
*Das Sternchen '''unbedingt in Anführungszeichen''', sonst expandiert ihn die Shell gegen Dateinamen im aktuellen Verzeichnis, bevor certbot ihn überhaupt sieht.
*certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10 --cert-name example.org -d example.org -d '*.example.org'
*Existiert das Zertifikat schon ohne Wildcard, reicht ein erneutes <code>certbot certonly</code> mit demselben <code>--cert-name</code> und der zusätzlichen SAN <code>-d '*.example.com'</code> — es wird dann um den Wildcard-Eintrag erweitert, nicht neu angelegt.
*Ohne Anführungszeichen vergessen und stattdessen zweimal denselben Namen angegeben (<code>-d example.com -d example.com</code>) stellt certbot klaglos ein Zertifikat '''ohne''' Wildcard aus — kein Fehler, nur fehlt dann die zweite SAN.


==Bestehendes Zertifikat (HTTP) auf DNS umstellen==
==Bestehendes Zertifikat (HTTP) auf DNS umstellen==
Ab Certbot 4.0 (Debian 13) ohne neu auszustellen:
Ab Certbot 4.0 (Debian 13) ohne neu auszustellen:
*certbot reconfigure --cert-name wiki.example.org --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10
*certbot reconfigure --cert-name example.com --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10
*grep -E "authenticator|dns_rfc2136" /etc/letsencrypt/renewal/wiki.example.org.conf
*grep -E "authenticator|dns_rfc2136" /etc/letsencrypt/renewal/example.com.conf
  authenticator = dns-rfc2136
  authenticator = dns-rfc2136
Certbot macht dabei einen Testlauf. Danach erneuert sich das Zertifikat mit der DNS-Challenge.
Certbot macht dabei einen Testlauf. Danach erneuert sich das Zertifikat mit der DNS-Challenge.
Zeile 159: Zeile 161:
*tail -f /var/log/letsencrypt/letsencrypt.log
*tail -f /var/log/letsencrypt/letsencrypt.log
;Was Let's Encrypt sieht
;Was Let's Encrypt sieht
*dig +short TXT _acme-challenge.wiki.example.org @ns1.example.org
*dig +short TXT _acme-challenge.wiki.example.com @ns1.example.com
*Die Abfrage geht an die '''autoritativen''' Server der Zone, nicht an deinen Resolver.
*Die Abfrage geht an die '''autoritativen''' Server der Zone, nicht an deinen Resolver.



Aktuelle Version vom 24. Oktober 2026, 12:20 Uhr

Prinzip

  • Certbot hat ein eingebautes Plugin, das den TXT-Eintrag für die DNS-Challenge selbst per RFC 2136 (dynamisches DNS-Update) im eigenen BIND setzt und danach wieder löscht.
  • Das ist der neue Weg gegenüber dem alten mit Hook-Skript (siehe Letsencrypt DNS Verification):
Alter Weg Neuer Weg (Plugin)
Skript eigenes certbot-dns-validation.sh keins, das Plugin macht das
Schlüssel steht im Skript eigene Datei, nur root lesbar
Erneuerung langer certbot renew …-Aufruf mit Hook läuft automatisch über certbot.timer
Rechte im BIND meist ganze Zone nur TXT an den _acme-challenge-Namen
  • Voraussetzung: ein eigener BIND, in dem du Zonen dynamisch ändern darfst.

Wann braucht man das

  • Port 80 ist nicht erreichbar oder wird gefiltert (Firewall, Sperrlisten gegen Bots).
  • Wildcard-Zertifikate (*.example.com), das geht nur per DNS-Challenge.
  • Das Zertifikat soll vor einem DNS-Umzug fertig sein, der Name zeigt noch auf den alten Server.
  • Die HTTP-Prüfung macht Probleme (Weiterleitungen, mehrere Server hinter einem Namen).

Überblick

Certbot (Webserver) --nsupdate (TSIG)--> BIND  <--TXT-Abfrage-- Let's Encrypt
  • Certbot setzt den TXT-Eintrag _acme-challenge.NAME im BIND.
  • Let's Encrypt fragt die autoritativen Nameserver ab und prüft den Wert.
  • Certbot löscht den Eintrag wieder.

1. BIND vorbereiten

Schlüssel erzeugen

Auf dem Nameserver
  • tsig-keygen -a hmac-sha256 acme-update > /etc/bind/acme-update.key
  • chown root:bind /etc/bind/acme-update.key
  • chmod 640 /etc/bind/acme-update.key
  • cat /etc/bind/acme-update.key
key "acme-update" {
    algorithm hmac-sha256;
    secret "BASE64-GEHEIMNIS==";
};

Der Name des Schlüssels (hier acme-update) ist die Identität im BIND. Das Geheimnis wird gleich für Certbot gebraucht und nirgends sonst abgelegt.

Schlüssel einbinden und Zone freigeben

Datei named.conf.local
include "/etc/bind/acme-update.key";

zone "example.com" {
    type master;
    file "example.com";
    update-policy {
        grant acme-update name _acme-challenge.example.com. TXT;
    };
};
  • Wildcard und Hauptname (example.com und *.example.com) teilen sich denselben Namen _acme-challenge.example.com — eine einzige Freigabe reicht für beide, dort stehen dann zwei TXT-Werte gleichzeitig.
  • file ohne Pfad und ohne Endung, wie bei den anderen Zonen auch: BIND sucht sie im konfigurierten Zonenverzeichnis.
  • Zusätzliche, eigenständige Namen außerhalb der Wildcard (z. B. ein eigener vHost wie wiki.example.com, der nicht im Wildcard-Zertifikat steckt) brauchen einen eigenen Grant: grant acme-update name _acme-challenge.wiki.example.com. TXT; — nur nötig, wenn dafür wirklich ein eigenes Zertifikat per DNS-Challenge ausgestellt wird.
  • Der Schlüssel darf nur TXT an diese Namen ändern, sonst nichts in der Zone.
  • Die Kurzform grant acme-update zonesub TXT; erlaubt TXT überall in der Zone. Das ist bequemer, aber weniger streng.
Wichtig
die Zone ist jetzt dynamisch
  • Die Zonendatei muss für den BIND-Benutzer beschreibbar sein (unter Debian /var/cache/bind/, Besitzer bind:bind — /etc/bind/ und /var/lib/bind/ gehören root und sind für den BIND-Prozess nicht beschreibbar).
  • Von Hand ändern geht nur noch mit Einfrieren, sonst gehen Änderungen verloren:
    • rndc freeze example.com
    • Zonendatei bearbeiten
    • rndc thaw example.com
  • Neben der Zonendatei liegt ein Journal (.jnl), das gehört zur Zone und darf nicht gelöscht werden.
Konfiguration laden
  • named-checkconf
  • rndc reconfig

2. Zugang testen (noch ohne Certbot)

Vom Webserver aus, mit kopierter Schlüsseldatei

Auf dem Webserver ist kein BIND installiert, /etc/bind/ gibt es dort also nicht. Die Schlüsseldatei landet einfach irgendwo, z. B. unter /root/:

  • scp /etc/bind/acme-update.key webserver:/root/acme-update.key
  • nsupdate -k /root/acme-update.key
server ns1.example.com
zone example.com
update add _acme-challenge.example.com. 60 TXT "test"
send
quit
  • dig +short TXT _acme-challenge.example.com @ns1.example.com
"test"
Aufräumen
  • nsupdate -k /root/acme-update.key
server ns1.example.com
zone example.com
update delete _acme-challenge.example.com. TXT
send
quit

Klappt das, ist der BIND-Teil fertig. Meldet er REFUSED, siehe Fehlersuche.

3. Certbot einrichten

Plugin installieren

  • apt install certbot python3-certbot-dns-rfc2136
  • certbot --version

Zugangsdatei

  • install -m 600 /dev/null /etc/letsencrypt/rfc2136.ini
  • vim /etc/letsencrypt/rfc2136.ini
dns_rfc2136_server = IP-DES-NAMENSERVERS
dns_rfc2136_port = 53
dns_rfc2136_name = acme-update.
dns_rfc2136_secret = BASE64-GEHEIMNIS==
dns_rfc2136_algorithm = HMAC-SHA256
  • dns_rfc2136_server ist die Adresse des Nameservers, bei dem die Zone dynamisch änderbar ist (der Primary).
  • dns_rfc2136_name ist der Schlüsselname, dns_rfc2136_secret der Wert aus der acme-update.key.
  • Die Datei muss 600 sein, sonst warnt Certbot.
  • Vom Webserver zum Nameserver müssen UDP und TCP auf Port 53 offen sein.

Zertifikat ausstellen

  • certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10 --cert-name example.com -d example.com -d '*.example.com' --agree-tos -m admin@example.com
Ergebnis
/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem
  • Das Sternchen unbedingt in Anführungszeichen, sonst expandiert ihn die Shell gegen Dateinamen im aktuellen Verzeichnis, bevor certbot ihn überhaupt sieht.
  • Existiert das Zertifikat schon ohne Wildcard, reicht ein erneutes certbot certonly mit demselben --cert-name und der zusätzlichen SAN -d '*.example.com' — es wird dann um den Wildcard-Eintrag erweitert, nicht neu angelegt.
  • Ohne Anführungszeichen vergessen und stattdessen zweimal denselben Namen angegeben (-d example.com -d example.com) stellt certbot klaglos ein Zertifikat ohne Wildcard aus — kein Fehler, nur fehlt dann die zweite SAN.

Bestehendes Zertifikat (HTTP) auf DNS umstellen

Ab Certbot 4.0 (Debian 13) ohne neu auszustellen:

  • certbot reconfigure --cert-name example.com --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini --dns-rfc2136-propagation-seconds 10
  • grep -E "authenticator|dns_rfc2136" /etc/letsencrypt/renewal/example.com.conf
authenticator = dns-rfc2136

Certbot macht dabei einen Testlauf. Danach erneuert sich das Zertifikat mit der DNS-Challenge.

4. Erneuerung

  • Das Debian-Paket bringt den Timer mit, Cron ist nicht nötig:
    • systemctl list-timers | grep certbot
  • Damit der Webserver das neue Zertifikat auch lädt, braucht es einen Hook:
    • vim /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx
    • chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
  • Testen (Certbot wartet im automatischen Modus bis zu 8 Minuten per Zufallsverzögerung, mit dem Schalter geht es sofort):
    • certbot renew --dry-run --no-random-sleep-on-renew
  • Kontrolle:
    • certbot certificates

Fehlersuche

Meldung / Symptom Ursache Lösung
REFUSED oder NOTAUTH bei nsupdate Schlüsselname oder update-policy passt nicht, oder die Adresse ist nicht der Primary Name des Schlüssels prüfen, _acme-challenge… in der Policy mit Punkt am Ende, named-checkconf
update failed: NOTIMP Zone ist nicht dynamisch update-policy in der Zone, Zonendatei beschreibbar
TSIG error with server: tsig verify failure falsches Geheimnis oder falscher Algorithmus Secret aus acme-update.key kopieren, dns_rfc2136_algorithm passend setzen
TXT-Eintrag ist da, Let's Encrypt findet ihn nicht Sekundäre Nameserver haben die Zone noch nicht übernommen --dns-rfc2136-propagation-seconds erhöhen, also-notify und Zonentransfer prüfen
Zugangsdatei wird bemängelt Rechte zu offen chmod 600 /etc/letsencrypt/rfc2136.ini
Nichts passiert, Zone lässt sich nicht von Hand ändern Zone ist dynamisch rndc freeze, ändern, rndc thaw
Logs
  • journalctl -u named -e
  • tail -f /var/log/letsencrypt/letsencrypt.log
Was Let's Encrypt sieht
  • dig +short TXT _acme-challenge.wiki.example.com @ns1.example.com
  • Die Abfrage geht an die autoritativen Server der Zone, nicht an deinen Resolver.

Sicherheit

  • Der Schlüssel erlaubt nur TXT an den _acme-challenge-Namen. Wer ihn stiehlt, kann Zertifikate für diese Namen bestellen, aber sonst nichts in der Zone ändern. Gib ihm trotzdem nur die Namen, die du wirklich brauchst.
  • Das Geheimnis liegt nur in /etc/letsencrypt/rfc2136.ini (600) und in der Schlüsseldatei am Nameserver (640, root:bind). Nicht in ein Skript, nicht in ein Git-Repository, nicht in ein Wiki.
  • Schlüssel regelmäßig tauschen: neuen mit tsig-keygen erzeugen, in der update-policy austauschen, Zugangsdatei anpassen, den alten entfernen.
  • Je Server einen eigenen Schlüssel. Wird einer entwendet, sperrst du nur diesen.

Merksatz

Plugin statt Skript | Schlüssel nur für TXT an _acme-challenge | Zone wird dynamisch, Änderungen von Hand nur mit rndc freeze und thaw | Erneuerung macht der Timer, nginx lädt per Hook | erst mit nsupdate testen, dann mit Certbot

Links