Automatische Zertifikate via Letsencrypt mit DNS Verifikation as-a-Service

Daniel Baumann daniel.baumann at bfh.ch
Sun Sep 5 13:41:45 CEST 2021


Hallo zusammen

TL;DR
=====

Wir koennen ab sofort DNS Verification as-a-Service fuer
Letsencrypt mit beliebigen Domains anbieten.


Kurzfassung
===========

Das "Let's Encrypt" Projekt stellt seit Ende 2015 kostenlos
Signaturen fuer X.509 Zertifikate aus, denen alle relevanten
Browser vertrauen:

   * https://letsencrypt.org/
   * https://de.wikipedia.org/wiki/Let's_Encrypt

Der haeufigste Anwendungsfall fuer solche Signaturen sind
Zertifikate fuer Webseiten die ueber https:// erreichbar sind.

Abgesehen davon dass man vor Letsencrypt fuer Signaturen
meistens bezahlen musste (und das nicht zu knapp), ist auch
die damit einhergehende, meistens jaehrliche Erneuerung der
Zertifikate manuelle Arbeit und muessig. Je mehr Systeme man
betreibt umso schlimmer.

Letsencrypt ermoeglich das Signieren von Zertifikaten
vollautomatisch ueber eine Schnittstelle, dazu ist eine
Verifikation eines Tokens ueber HTTP oder ueber einen DNS
Record notwendig.

Das ist alles nichts neues - seit Ende 2015 benutzen wir
fuer alle Linux Systeme Letsencrypt. Bis auf wenige Ausnahmen
bisher immer via HTTP Verification.

Weil wir aufgrund von Infrastruktur-Aenderungen im Zusammenhang
mit dem Umbau der Rechenzentren dringend Zertifikate erneuern
mussten bei denen HTTP Verifikation umstaendlich gewesen waere,
haben wir uns letzte Woche kurz mal etwas Zeit genommen und die
notwendige Integration fuer DNS Verification in unser Setup
umgesetzt.

Neu koennen wir jetzt zentral, Domain- und Netzwerkzonen-
unabhaengig DNS Verification einsetzen und allen weiteren
Nutzenden von Letsencrypt an der BFH zur Verfuegung stellen.

Alle dazu notwendigen Komponenten sind generisch und BFH-
unabhaengig, weshalb wirs wie immer auch gleich als Paket
in Debian hochgeladen haben.

Im Folgenden die technische Beschreibung der Funktionsweise
fuer die, die es interessiert und fuer uns als Dokumentation.


Hintergrund
===========

1. Domain Verification im Allgemeinen
=====================================

Wenn man eine gueltige Signatur fuer ein Zertifikat seines
Systemes erhalten moechte, muss man zuerst nachweisen koennen,
dass man die Kontrolle ueber das System hat das man behauptet
zu sein.

Wenn ich ein einen Webserver unter foo.bfh.ch betreibe,
kann ich diesen Nachweis erbringen, indem ich ein mir zuvor von
Letsencrypt mitgeteilten, "geheimen" String unter
/.well-known/acme-challenges/ ablege. Letsencrypt kann dies dann
durch Aufrufen von http://foo.bfh.ch/.wellknown/acme-challenges/$string
nachpruefen.

Im Anschluss mir dann mein Zertifikat fuer foo.bfh.ch von
Letsencrypt signiert und mir uebermittelt. Ich kann dann meinen
Webserver diese Signatur mitgeben, so dass wenn ein Browser
https://foo.bfh.ch aufruft, meiner Webseite automatisch vertraut wird.

Durch diese einfache Verifikation ist sichergestellt, dass
ich keine gueltige Signatur fuer z.B. www.bfh.ch beziehen kann
(ich habe selbst keinen System-Zugriff auf www.bfh.ch und kann dort
nicht einen solchen String ablegen).

Anderfalls, d.h. wenn ich an eine gueltige Signatur fuer ein
Zertifikat von www.bfh.ch bekaeme, koennte ich (unter Zuhilfenahme
von DNS Cache Poisoning, IP Spoofing, etc.) meine eigene Seite als
www.bfh.ch ausgeben und die Browser von Besuchern wuerden keine
Warnung zeigen.

Dieses Challange-Response Verfahren wird natuerlich durch
ein Stueck Software implementiert und geschieht automatisiert.
Letsencrypt bietet eine Referenzimplementierung (certbot), das viele
Abhaengigkeiten hat und vergleichsweise kompliziert ist. Wir setzen
daher seit jeher auf eine der verbreitetsten Alternativ-
Implementierungen (dehydrated), die viel schlanker und besser
integrierbar ist.

Signaturen von Letsencrypt haben eine Ablaufzeit von 90
Tagen. Je nach Client wird in der Regel nach 60 Tagen das
Zertifikat und die Signatur dafuer selbststaendig erneuert.


2. DNS Verification im Allgemeinen
==================================

Neben der HTTP Verification gibt es wie angesprochen die
Moeglichkeit der Verifikation ueber einen DNS TXT Record.

Diese hat folgende Vorteile:

   * der Verifikation-Vorgang laeuft schneller ab.

   * das zu verifizierende System muss keinen Webserver
     enthalten und eignet sich daher insbesondere fuer alle
     Systeme die nicht ein Webserver sind oder sowieso schon
     einen Webserver haben.

   * das zu verifizierende System muss nicht vom Internet
     heraus direkt erreichbar sein (DMZ), sondern kann sich in
     einem internen Netz befinden.

   * Wildcard Zertifikate (z.B. *.bfh.ch) werden
     unterstuetzt.

Der Ablauf einer DNS Verificiation fuer z.B. foo.bfh.ch
benoetigt einen TXT Record mit Namen _acme-challenge.foo.bfh.ch.
Enthaelt dieser den passenden "geheimen" String, so gelingt der
Nachweis gegenueber Letsencrypt.

Somit stuetzt die DNS Verification darauf ab, dass ich
nicht zwingend das System selbst kontrolliere fuer welches
ich eine Verifikation durchfuehren moechte, sondern das (oder
zumindest den Teil des) DNS fuer welchen Namen ich ein
Zertifikat signieren lassen moechte.

Das ist deshalb kein Problem, weil wenn ich DNS
kontrolliere kann ich ja den Eintrag fuer foo.bfh.ch auf
eine beliebige IP Adresse zeigen lassen. D.h. wuerde ich
das System hinter foo.bfh.ch nicht kontrollieren, koennte
ich jederzeit den DNS Record fuer foo.bfh.ch aendern und
auf ein anderes, von mir kontrolliertes System umschreiben.

Dadurch wird die Berechtigung um eine Abstraktionsebene von
einer Endsystem-operativen Ebene auf eine Organisations/
Service-administrative Ebene verschoben. Dies ist eine wichtige
Grundvoraussetzung fuer die "Multi-User-Faehigkeit"
(siehe weiter unten).


3. DNS Verification via CNAME
=============================

Im Beispiel mit foo.bfh.ch wird als TXT Record
_acme-challenge.foo.bfh.ch abgefragt.

Als weitere Variante besteht die Moeglichkeit, anstatt
eines TXT Records einen CNAME abzulegen und diesen auf eine
anderen Record zeigen zu lassen, z.B.:

   _acme-challenge.foo.bfh.ch IN CNAME verify.example.org.

Wuerde ein solcher Eintrag existieren, wird Letsencrypt
diesem folgen und den "geheimen" String als TXT Record
unter verify.example.org erwarten.

Das ist nuetzlich fuer den Fall dass man mehrere Domains
hat (wir haben >160 Domains), da man so saemtliche
Letsencrypt-Anfragen auf "eine" (Sub)Zone konzentieren
kann.


4. Warum CNAME fuer DNS Verification so toll sind
=================================================

Warum ist die Moeglichkeit fuer CNAMEs so relevant?

Damit die DNS Verification automatisiert ablaufen kann,
muss der Client (certbot, dehydrated, etc.) der die
Signatur fuer sein Zertifikat ausgestellt bekommen moechte,
den notwendigen TXT Record selbst ins DNS schreiben koennen.

Dadurch ergeben sich mindestens folgende Problemstellungen:

   * wenn man hunderte Domains hat, moechte man sicher nicht
     alle Domains fuer Schreibzugriffe "oeffnen".

   * selbst wenn man nur eine Domain hat, moechte man nicht
     dass Clients "uneingeschraenkt" in die Zone schreiben koennen.

     fuer foo.bfh.ch ist _acme-challenge.foo.bfh.ch noetig,
     fuer bar.foo.bfh.ch ist _acme-challenge.bar.foo.bfh.ch noetig,
     usw.

     d.h. ein Client muesste so rekursiv fuer die gesamte
     Zone alle TXT Records schreiben koennen.

     Da Software generell unerwartete Bugs haben kann, moechte man
     nicht auf deren Abwesenheit vertrauen und moeglichst
     sicherstellen, dass im Falle eines Falles so wenig wie
     moeglich passieren kann... und gibt daher sowenige
     DNS Berechtigungen wie irgendwie moeglich und so isoliert
     wie moeglich heraus.

   * wenn man die TXT Records in die Original Zone schreiben wuerde,
     muss man demnach auch ins "Haupt-DNS" schreiben koennen.

     DNS ist, nach IP, der am meisten kritische Teil einer
     Infrastruktur. Da will man keine Clients von irgendwoher
     reinschreiben lassen.

   * je nach Client wird nach der erfolgreichen Verifikation
     der dazu benutzt TXT Record im DNS automatisch auch wieder
     geloescht.

     Trotzdem gibt es immer wieder Faelle wo alte Records
     liegen bleiben. Diesen Muell will man nicht im "Haupt-DNS"
     fuer alle Ewigkeit mitschleppen und sicher auch nicht manuell
     aufraeumen.

   * und schlussendlich: bei groesseren Infrastrukturen
     sind die "Haupt-DNS" Server optimiert auf moeglichst hohen
     Read-only Durchsatz (das haben wir bei uns auch so gemacht),
     und nicht auf vergleichsweise extrem seltenen Write-Operationen.

     werden die DNS Server "staendig" durch Write-Operationen
     unterbrochen, sinkt die allgemeine Performance der DNS
     Server. Bei opimierten und modernen DNS Server wie das bei
     uns fuer BFH.science und BFH.info ist, ist das sehr schoen
     vorzeigbar.

     Das ist aber eine andere Geschichte fuer ein anderes
     Mal.. mehr dazu wenn wir BFH.ch DNS im weiteren Verlauf
     bis Ende Jahr neu machen und darueber informieren werden.

Durch die Benutzung von CNAMEs kann man also die Write-Operationen
fuer jegliche TXT Records fuer Letsencrypt auf eine dezidierte Zone
umleiten und diese durch dafuer dezidierte, separate DNS Server
betrieben und alle obigen Probleme auf einen Schlag loesen.


5. Warum CNAME fuer Multi-User pro Domain so toll sind
======================================================

Wenn man fuer alle zu verifizierenden Systeme einen
ensprechenden CNAME auf eine andere Domain anlegt, muessen
einfach dort alle Write-Operationen getaetigt werden. Es
muessten also die Clients auf die gesamte Zone (im obigen
Beispiel verify.example.org) schreiben koennen. Damit
scheint auf den ersten Blick das "Clients schreiben ins
die gesamte Zone"-Problem von oben nur weiter verschoben worden
zu sein.

Gegeben seine folgende Eintraege:

   _acme-challenge.foo.bfh.ch IN CNAME foo.verify.example.org.
   _acme-challenge.bar.bfh.ch IN CNAME bar.verify.example.org.

Wenn Clients (certbot, dehydrated, etc.) uneingeschraenkt
auf verify.example.org schreiben koennten, so kann man
nicht verhindern dass jemand der foo.bfh.ch verifizieren
kann, auch den Nachweis fuer bar.bfh.ch erbringen kann und
so unbereichtigt eine gueltige Signatur fuer ein System
(bar.bfh.ch) das er nicht kontrolliert, erlangt.

Alle gaengigen DNS Server (wir benutzen Knot) erlauben ein
Einschraenken von Write-Operationen nur auf folgende Arten:

   * Einschraenken auf IP Adressen oder Subnetze
   * Einschraenken auf Subzonen
   * Notwendigkeit einer Transfer-Signatur (TSIG),
     das ist im wesentlich einfach ein Passwort
   * Eine Kombination aus obigem.

Durch Nutzen von Subzonen und TSIGs koennen wir also
erreichen, dass bestimmte Personen oder Systeme die durch
eine "eigene" TSIG abgebildet werden, nur in eine ihnen
zugewiesene Subzone schreiben koennen, auf welche alle
fuer sie zu verwendenden CNAME Eintraege verweisen.

Damit kann dieses Problem also sehr schoen geloest werden.
Als naechstes ein konkretes Beispiel dazu.


6. BFH Server fuer Letsencrypt DNS Verification
===============================================

Wie oben erlaeutert kann man die "Verifikations-Domain" als
separate Zone betreiben. Bei uns ist dies _acme.bfh.info.

   [ Randbemerkung: bfh.info ist die unsere
     Infrastruktur-Domain fuer das neue Netz welches
     BFH.ch und BFH.science vereint/abloest und ist im Hintergrund,
     die man als User nie sieht, dazu mehr ein anderes Mal ]

fiktives Beispiel:

   * Hans Muster (BFH Kuerzel: mth1) betreibt mehrere Server
     und moechte Letsencrypt benutzen.

   * Hans meldet sich bei uns via Servicedesk und kriegt
     eine fuer ihn (resp. seine Systeme) spezifische TSIG
     mitgeteilt.

   * Wir legen folgende DNS Subzone an und weisen seine TSIG zu:

       mth1._acme.bfh.info IN NS node1.acme.bfh.info.
       mth1._acme.bfh.info IN NS node2.acme.bfh.info.

     D.h. Hans kann mit Hilfe seiner TSIG jegliche Records
     innerhalb von mth1._acme.bfh.info hinzufuegen/loeschen.

   * Fuer jedes seiner Systeme in bfh.ch fuer die er gerne
     Letsencrypt benutzen moechte, legen wir CNAMEs an, z.B.:

       _acme-challenge.hans.bfh.ch IN CNAME \
         hans.bfh.ch.mth1._acme.bfh.info.

       _acme-challenge.muster.bfh.ch IN CNAME \
         muster.bfh.ch.mth1._acme.bfh.info.

   * Obwohl Hans zwar alle Records in mth1._acme.bfh.info
     rekursiv schreiben kann...

     - dass er sich beliebige Signaturen fuer Zertifikate
       unterhalb von mth1._acme.bfh.info ausstellen kann, ist
       grundsaetzlich moeglich aber uns egal da wir die Zone ja
       fuer sonst nichts einsetzen.

     - Fun Fact: mit Letsencrypt waere das nicht moeglich
       weil diese verweigern das Signieren von Zertifikaten mit einem
       Underscore, d.h. de-facto ist die Schreibmoeglichkeit der
       gesamten Subzone im Kontext Letsencrypt fuer Hans komplett
       nutzlos/nicht ausnuetzbar.

     - da wir die bfh.ch CNAMEs setzen, kontrollieren wir genau fuer
       welche Domains Hans (via Umweg ueber mth1._acme.bfh.info) sich
       Zertifikate signieren lassen kann. D.h. die Schreibmoeglichkeit
       der gesamten Subzone ist auch hier fuer Hans komplett nutzlos/
       nicht ausnuetzbar.

     - wuerde Hans (oder ein System von Hans) "Amok" laufen und uns mit
       Anfragen bombardieren, so wuerden wirs genau zuordnen und wuerden
       einfach seine TSIG sperren (analog einer Account-Sperrung).

    * ...kann hans trotzdem *nur* fuer hans.bfh.ch und muster.bfh.ch
      Signaturen holen.

Wer alles bisherige nachvollziehen kann, hat sich jetzt ein
"oh, das ist ja ziemlich cool" gedacht :)


7. BFH Implementierungs-Details
===============================

Nun zum Schluss.. wers bis ganz nach hier unten geschafft
hat.. gratuliere!

Zur Belohnung gibts noch ein paar BFH-spezifische
Implementierungsdetails als Bonus:

   * Wie im obigen Beispiel mit Hans Muster legen wir die
     CNAMEs in z.B. bfh.ch nicht direkt auf mth1._acme.bfh.info,
     sondern auf $original_name.mth1._acme.bfh.info.

     Wuerden wir alle CNAMEs auf mth1._acme.bfh.info zeigen
     lassen, haette mth1._acme.bfh.info einfach mehrere TXT Records.

     Letsencrypt kann mit mehreren TXT Records umgehen und
     solange einer davon "richtig" ist, gelingt die Verifikation.

     Trotzdem machen wir das nicht, weil:

     - mehrere TXT Records sind unnuetz und verlaengern die
       Verifikationsdauer unnoetig.

     - werden zuviele TXT Records zurueckgegeben so dass die DNS
       Antwort eine gewisse Netzwerkpaket-Groesse uebersteigen lassen
       (ist nicht dokumentiert wie viele, aber es muessen schon einige
       sein), wird Letsencrypt die Antwort verwerfen und die Verifikation
       scheitert.

     - man sieht in den Logs auf unseren Nameservern genau,
       welche Records noch benutzt werden und welche nicht. Dadurch
       koennten wir zukuenftig automatisch unbenutzte CNAMEs wieder
       entfernen und die original-Zone (bfh.ch) vor (zusaetzlichem)
       Muell verschonen.

       Naetuerlich sieht man fuer den Debug-Fall auch separat und
       einfach, welche konkreten Einzelsysteme Probleme
       haben/verursachen.

   * Grundsaetzlich wuerde ein Nameserver fuer diese "Wegwerf-Zonen"
     reichen, wir haben abweichend von unserer n=4 Default-Redundanz
     nur zwei Nameserver fuer die Letsencrypt DNS Verifikation.

     Je weniger Nameserver die Clients die TXT Records ein-
     und austragen muessen, je schneller ist die Verifikation
     insgesamt.

     Weil ein System aber kein System ist, haben wir also zwei
     Nameserver und dadurch ensprechend Redundanz.

     Da die ueblichen Clients 30 Tage vor Ablauf taeglich
     versuchen, Zertifikat+Signatur zu erneuern, waere auch
     ein Komplett-Ausfall beider Nameserver selbst fuer ein
     paar Tage kein Problem.

   * Damit Verifikationen besonders "snappy" sind, benutzen
     wir eine TTL von 0 (wahrscheinlich gar nicht noetig, weil
     Letsencrypt die TTL wohl eh ignoriert.. aber da man eine
     TTL setzen  muss, warum nicht direkt auf 0.. hilft zumindest
     minimal beim Debuggen).

   * Grundsaetzlich koennten wir statt einer TSIG auch DNS
     Updates von Clients nur aufgrund einer IP Einschraenkung annehmen.

     Das haette zwei betriebliche Nachteile:

     - um die notwendigen Berechtigungen einzuschraenken,
       muessten wir pro System das verifizieren will, eine eigene
       Subzone einrichten. Es gibt an der BFH weniger Personen als
       die von diesen Personen verwaltenden Systeme, daher waere das
       Mehraufwand.

     - die Systeme muessten alle eine fixe IP haben und wir
       muessten diese Liste von fixen IPs zu Subzonen pflegen und
       nachfuehren, wenn ein System die IP wechselt.

     - durch die Kombination einer TSIG (= Person identifiziert)
       und einer Einschraenkung auf das BFH Netzwerk (2a07:6b40::/29
       und 147.87.0.0/16) koennten wir umstaendlicher machen,
       dass Personen mir Ihrer TSIG den Service auch fuer ihre privaten,
       nicht-BFH Server benutzen wuerden.

       Weil die Kombination aus TSIG und IP Einschraenkung es nur
       umstaendlicher macht, aber nicht komplett verhindert,
       machen wir das natuerlich nicht. Abgesehen davon ists ja auch gar
       kein Problem, die private Mitbenutzung durch BFH-Mitarbeitende ist
       voellig in Ordnung (analog der privaten Nutzung von z.B. BFH
       Notebooks etc.).

       Trotzdem waere, wenn wir eine TSIG spaeter einfuehren muessten,
       das mit viel Migrationsaufwand fuer bestehende User verbunden,
       weshalb es auch aus diesem Grund besser ist, es von Anfang an
       zu machen.

   * Wer gerne einen CNAME-aware dehydrated hook fuer DNS Verification
     benutzen moechte der fuer alle und alles funktioniert, sei
     auf unsere Sammlung von dehydrated-tools hingewiesen:

     https://git.open-infrastructure.net/software/service-tools/

     Fuer Debian-basierte Systeme sei das Debian Paket mit
     ensprechendem Preseeding Support zur natlosen und komplett
     automatischen, nicht-interaktiven Integration empfohlen:

     https://git.progress-linux.org/users/daniel.baumann/debian/
     packages/open-infrastructure-service-tools/

     Letzteres befindet sich in Debian unstable, Debian testing
     und unserem Backports Repository fuer Debian stable (11 aka
     bullseye).

Gruesse,
Daniel

-- 
Berner Fachhochschule / Bern University of Applied Sciences
Services / IT-Services
Daniel Baumann
Teamleiter Linux & Infrastructure Services
___________________________________________________________
Dammweg 3, CH-3013 Bern
Telefon direkt +41 31 848 48 22
Telefon Servicedesk +41 31 848 48 48
daniel.baumann at bfh.ch
https://bfh.ch
https://bfh.science


More information about the bfh-linux-announce mailing list