blog.bartlweb - a technologist's external brain

Let’s Encrypt in Enterprise-Setups ohne DNS-Validierungsmöglichkeit per HTTP-Validierung einrichten

Let's Encrypt bietet kostenlose SSL-Zertifikate für die Absicherung von Webservices (Webserver, E-Mail, ...) an. Es lassen sich zwar keine Wildcard-Zertifikate erstellen, aber dafür mehrere einzelne Zertifikate beantragen und zusätzlich Mutidomain-Zertifikate erstellen.

Für das Abrufen der Zertifikate gibt es Tools, die direkt auf dem öffentlich erreichbaren Hostsystem ausgeführt werden und jede einzelne Domain (für die ein Zertifikat beantragt wird) per HTTP oder DNS verifizieren. Und da haben wir in komplexen Infrastrukturen auch schon die größte Hürde:

  • Die DNS-Validierung lässt sich nur sinnvoll nutzen, wenn das Tool direkt die DNS-Konfiguration modifizieren kann und fällt damit für alle jene die die eigenen Domains von einem Hoster verwalten lassen flach.
  • Die HTTP-Verifizierung erfordert das Bereitstellen von Dateien über definierte Pfade unter den jeweiligen Domains über die Standardports 80 oder 443. Und das ist bei Reverse-Proxy-Setups, Redirects oder internen Systemen oder Diensten auf nicht Standardports gar nicht so einfach zu bewerkstelligen.

Das folgende Tutorial soll meinen Lösungsweg dafür aufzeigen.

Limitierungen von Let's Encrypt und Strukturierung der Zertifikate

Let's Encrypt limitiert die Anzahl an Zertifikaten auf 20 Anfragen pro Woche und Domain. D.h. es können pro Woche 20 Zertifikate angefragt werden und jede Woche kann die Anzahl um weitere 20 Zertifikate erhöht werden. Das klingt schnell einmal ausreichend, wer aber Zertifikate bei Fehlern neu abrufen muss oder um weitere Domains erweitert, läuft schnell in das vorgegebene Limit. Abhilfe schafft der Ansatz Multidomain-Zertifikate zu beantragen, denn pro Zertifikat können bis zu 100 Domains (Subject Alternative Name) hinterlegt werden. 

Außerdem ist zu beachten, dass alle Zertifikate nur 90 Tage lang gültig sind und daher im Idealfall nach 60 Tagen erneuert werden. Für die Anzahl von gleichzeitigen Erneuerungen von bestehenden Zertifikaten gibt es übrigens keine Limitierung.

Der Vorteil von Wildcard-Zertifikaten gegenüber Multidomain-Zertifikaten ist, dass im Zertifikat keine expliziten Subdomains hinterlegt sind. Das ist bei öffentlich erreichbaren Domains kein Thema, aber meine Management-Domains oder privaten aber öffentlich erreichbaren Dienste möchte ich nicht so einfach bekannt geben. D.h. der erste Schritt besteht darin alle Domains die ein SSL-Zertifikat erhalten sollen einmal aufzulisten und sinnvoll zu gruppieren und dabei das Limit von max. 100 Domains pro Zertifikat zu berücksichtigen.

Nachdem die Zertifikate und vor allem Konfigurationen immer nach der ersten angegebenen Domain benannt werden, habe ich die Chance genutzt und mir ein System mit "Verwaltungsdomains" überlegt, die mir die einzelnen Zertifikate eindeutige bezeichnen. Dafür nutze ich folgendes Schema:

<host>-<domain>-<public/private>.ssl.example.com

z.B. webserver-formletter2pdfcom-public.ssl.bartlweb.net, webserver-formletter2pdfcom-private.letsencrypt.bartlweb.net oder auch vhost-bartlme-public.bartlweb.net.

So erhalte ich für meine unzähligen Domains am Ende eine Liste von mehreren aber so wenig wie möglichen Multidomain-Zertifikaten, die ich durch die Benennung über die Verwaltungsdomain leicht auseinanderhalten kann.

Webserver für zentrale HTTP-Authentifizierung konfigurieren

Let's Encrypt validiert den Besitz einer Domain für ein Zertifikat durch Abfrage einer Datei die unter <domain>/.well-known/acme-challange/<Key> bereitgestellt wird und über die Standardports 80 oder 443 erreichbar sein muss. Die Keys sind dabei für jede Domain einzigartig und überschreiben sich nicht, daher bietet es sich an, ein zentrales Verzeichnis für das Ablegen dieser Verifizierungsdateien zu nutzen und dieses über virtuelle Verzeichnisse in jedem Host des Webservers verfügbar zu machen.

Globaler Alias 

In der zentralen Apache-Konfiguration wird ein global verfügbarer Alias angelegt, der somit für alle virtuellen Hosts gilt und unser zentrales Verzeichnis unter der von Let's Encrypt geforderten URL verfügbar macht.

Alias /.well-known/acme-challenge/ "/server/static/letsencrypt/"
<Directory "/server/static/letsencrypt/">
  Options MultiViews
  AllowOverride None
  Require all granted
</Directory>

Ausnahmen für Rewrites

Damit auch Domains, die eigentlich im Hintergrund umgeschrieben werden, z.B. bartlweb.net auf www.bartlweb.net ein Zertifikat erhalten können, muss der Verifizierungspfad von diesen Rewrites ausgenommen werden. Dafür lassen sich in den Rewrite-Regeln Pfade ausnehmen:

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/.*$ [NC]
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/.*$ [NC]
RewriteRule ^(.*)$ https://anotherurl.com/target/ [R=301,L]

Siehe dazu auch den Beitrag: Pfade oder Dateien von Redirects mit mod_rewrite in Apache ausnehmen

Ausnahmen für Reverse-Proxy

Reverse-Proxy-Konfigurationen lassen sich so anpassen, dass Anfragen an den Verifizierungspfad nicht an den eigentlichen Zielhost weitergeleitet werden, sondern vom lokalen Verzeichnis bedient werden.

ProxyPreserveHost On
 
ProxyPass /.well-known/acme-challenge/ !
 
ProxyPass / http://server.local:8080/
ProxyPassReverse / http://server.local:8080/

Siehe dazu auch den Artikel: Eigene Ressourcen in Website die mit mod_proxy von Apache ausgeliefert werden einbinden

Zertifikate beantragen

Für das Beantragen der Zertifikate bei Let's Encrypt gibt es viele Tools und Skripte, die einem dabei unterstützen. Ich habe mich für das Tool getssl (github.com/srvrco/getssl) entschieden, weil sich dort die einzelnen Zertifikate schön in einzelnen Konfigurationsdateien verwalten lassen und der Name (die erste Domain) dieser Konfiguration selbst gesetzt werden kann. - Daher auch das oben bereits erwähnte Namenssystem, um den Überblick zu behalten.

Mit dem folgenden Befehl lässt sich eine neue Konfiguration anlegen:

getssl -w /etc/letsencrypt/certs -c webserver-domain-public.letsencrypt.example.com

Danach wird die unter dem Arbeitsverzeichnis /etc/letsencrypt/certs neu erstellen Verzeichnis webserver-domain-public.letsencrypt.example.com abgelegte Konfigurationsdatei getssl.cfg bearbeitet:

  • Je nachdem, ob Sie ein gültiges Zertifikat abfragen oder zunächst nur einmal die Konfiguration testen möchten, kann über den Parameter CA das Live- oder Staging-System von Let's Encrypt angesprochen werden. Aufgrund der Limits von 20 Zertifikatsanfragen pro Domain und Woche am Livesystem, würde ich empfehlen, den Abruf der Zertifikate in jedem Fall zunächst einmal mit dem Stagingsystem zu testen
  • Den Account definieren Sie am besten in der globalen Konfiguration getssl.cfg und nicht pro Domain.
  • Der Parameter SANS nimmt die zusätzlichen Domains für das Zertifikat auf. Listen sie hier (ohne führenden Beistrich und ohne Leerzeichen) alle Domains auf, die durch das Zertifikat abgedeckt werden sollen (max. 100).
  • Bei ACL tragen Sie den Pfad des globalen Verzeichnisses am Webserver ein, in dem die Verifizierungsdateien für den Abruf von Let's Encrypt unter dem Pfad <domain>/.well-known/acme-challenge/<key> hinterlegt werden sollen. Nachdem wir hier alle Verifizierungsdateien für alle Domains gesammelt ablegen, setzen Sie den Parameter USE_SINGLE_ACL auf true.
  • DOMAIN_CERT_LOCATIONDOMAIN_KEY_LOCATION und CA_CERT_LOCATION gibt den Speicherort des Privatekey, des eigenen Zertifikats und des Zertifikats der CA an.
  • Mit RELOAD_CMD kann ein Kommando ausgeführt werden, um z.B. nach Erneuern des Zertifikats den Webserver neu zu starten.
  • RENEW_ALLOW gibt die Dauer in Tagen an, die nach Ausstellung des Zertifikats vergehen muss, bevor das Zertifikat erneuert werden darf. 60 Tage ist bei einer maximalen Gültigkeit von 90 Tagen ein guter Wert, so bleiben im Fehlerfall immer noch 30 Tage um einzugreifen.
# Uncomment and modify any variables you need
# see https://github.com/srvrco/getssl/wiki/Config-variables for details
#
# The staging server is best for testing
#CA="https://acme-staging.api.letsencrypt.org"
# This server issues full certificates, however has rate limits
CA="https://acme-v01.api.letsencrypt.org"

#AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"

# Set an email address associated with your account - generally set at account level rather than domain.
#ACCOUNT_EMAIL="me@example.com"
#ACCOUNT_KEY_LENGTH=4096
#ACCOUNT_KEY="/etc/letsencrypt/certs/account.key"
#PRIVATE_KEY_ALG="rsa"

# Additional domains - this could be multiple domains / subdomains in a comma separated list
# Note: this is Additional domains - so should not include the primary domain.
SANS=example.com,www.example.com,test.example.com

# Acme Challenge Location. The first line for the domain, the following ones for each additional domain.
# If these start with ssh: then the next variable is assumed to be the hostname and the rest the location.
# An ssh key will be needed to provide you with access to the remote server.
# Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign.
# If left blank, the username on the local server will be used to authenticate against the remote server.
# If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location
# These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge"
# where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain.
#ACL=('/var/www/webserver-bartlwebnet-public.letsencrypt.bartlweb.net/web/.well-known/acme-challenge'
# 'ssh:server5:/var/www/webserver-bartlwebnet-public.letsencrypt.bartlweb.net/web/.well-known/acme-challenge'
# 'ssh:sshuserid@server5:/var/www/webserver-bartlwebnet-public.letsencrypt.bartlweb.net/web/.well-known/acme-challenge'
# 'ftp:ftpuserid:ftppassword:webserver-bartlwebnet-public.letsencrypt.bartlweb.net:/web/.well-known/acme-challenge')
ACL=('/server/static/letsencrypt')

#Enable use of a single ACL for all checks
USE_SINGLE_ACL="true"

# Location for all your certs, these can either be on the server (full path name)
# or using ssh /sftp as for the ACL
DOMAIN_CERT_LOCATION="/server/certs/webserver-domain-public.crt"
DOMAIN_KEY_LOCATION="/server/certs/webserver-domain-public.key"
CA_CERT_LOCATION="/server/certs/webserver-domain-public.ca.crt"
#DOMAIN_CHAIN_LOCATION="/server/certs/webserver-domain-public.fullchain.crt" # this is the domain cert and CA cert
#DOMAIN_KEY_CERT_LOCATION="" # this is the domain_key and domain cert
#DOMAIN_PEM_LOCATION="" # this is the domain_key. domain cert and CA cert

# The command needed to reload apache / nginx or whatever you use
RELOAD_CMD="/usr/sbin/service apache2 reload"
# The time period within which you want to allow renewal of a certificate
# this prevents hitting some of the rate limits.
RENEW_ALLOW="60"

# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp,
# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which
# will be checked for certificate expiry and also will be checked after
# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true
#SERVER_TYPE="https"
#CHECK_REMOTE="true"

# Use the following 3 variables if you want to validate via DNS
#VALIDATE_VIA_DNS="true"
#DNS_ADD_COMMAND=
#DNS_DEL_COMMAND=
#AUTH_DNS_SERVER=""
#DNS_WAIT=10
#DNS_EXTRA_WAIT=60

Nach der Konfiguration lässt sich das Zertifikat mit dem folgenden Befehl abrufen. Dabei kümmert sich getssl automatisch um das Erstellen des CSR und die Validierung der einzelnen Domains, sowie den Abruf und die Speicherung des Zertifikats.

getssl -w /etc/letsencrypt/certs webserver-domain-public.letsencrypt.example.com

Der zusätzliche Parameter -f forciert den Abruf, um Zertifikate z.B. nach dem Hinzufügen einer zusätzlichen Domain bereits vor Ablauf der Erneuerungsfrist neu abzufragen. Beachten Sie dabei aber immer das Wochenlimit von max. 20 Zertifikaten.

Zertifikat einrichten

In Apache lässt sich das Zertifikat wie gewohnt über die folgenden Parameter einbinden.

SSLCertificateFile /server/certs/webserver-domain-public.crt
SSLCertificateKeyFile /server/certs/webserver-domain-public.key
SSLCACertificateFile /server/certs/webserver-domain-public.ca.crt

Achten Sie generell auf eine sichere Konfiguration des SSL-Servers. Siehe dazu auch Self-signed Zertifikate für lokale Webservices mit OpenSSL selbst erstellen.

Zertifikate automatisiert erneuern

Um die Zertifikate nun alle 60 Tage zu erneuern, erstellen wir basierend auf getssl ein eigenes Skript /server/certs/renew-certificates.sh, dass uns getssl mit allen notwendigen Parametern aufruft und folgenden Inhalt hat.

/server/certs/getssl -u -a -w /etc/letsencrypt/certs/

Dieses wird dann per Cronjob jeden Samstag um 01:00 Uhr früh ausgeführt (bei Fehlern kann so am Wochenende am Problem gearbeitet werden). getssl prüft dabei automatisch, ob ein Erneuern der Zertifikate notwendig ist oder nicht, verifiziert die Domains bei Bedarf, legt die neuen Zertifikate in die in der Konfiguration definierten Dateipfade ab und lädt den Webserver neu.

0 1 * * 6 /server/certs/renew-certificates.sh > /dev/null 2>&1

Interne Systeme mit Zertifikaten ausrüsten

Die oben beschriebene Lösung funktioniert super, wenn der Webserver über Standardports im öffentlichen Netzwerk verfügbar ist. Für Mailserver, die nur mittels Reverse-Proxy im öffentlichen Netz erreichbar, oder interne Systeme, die gar nicht von außen erreichbar sind, lassen sich darüber keine Zertifikate abfragen.

Hier muss nun etwas gebastelt werden:

  • Entweder werden für diese Dienste selbst-signierte Zertifikate erstellt oder Einzeldomainzertifikate zu erschwinglichen Preisen gekauft.
  • Alternativ kann der Webserver für diese Domains Fake-Websites bereitstellen, darüber die Zertifikate beantragen, und diese dann mittels Skript auf die einzelnen Systeme dahinter verteilen.

Dieser Artikel hat Dir deinen Tag gerettet?

... und mühevolles Probieren, Recherchieren und damit Stunden an Zeit gespart? Oder einfach nur Dein Problem gelöst?

Dann würde ich mich freuen, wenn Du meine Zeit für die Erstellung dieses Blogartikels mit einer kleinen Anerkennung honorierst:

Zahlung mit PayPal oder Kreditkarte.

Hinweis zur Verwendung

Die Übermittlung einer Zahlung ist eine persönliche Anerkennung Ihrerseits an den Entwickler (Christian Bartl, Privatperson). Eine Zahlung ist nicht zweckgebunden und es ist keine Gegenleistung zu erwarten. Bitte beachten Sie, dass für eine übermittelte Zahlung keine Quittung ausgestellt werden kann.

Über den Autor

Christian Bartl

Christian Bartl Requirements Engineer
& Solution Architect für Online und Mobile

Als Technologie-Enthusiast und begeisterter Programmierer entwickle ich in meiner Freizeit Websites, Software und IT-Lösungen, die mir selbst und anderen den Alltag vereinfachen.

mehr auf christian.bartl.me

Kommentare

Noch kein Kommentar vorhanden.
Sei der Erste! - Ich freue mich über deine Anmerkung, Kritik oder Frage.

Kommentar schreiben

Der hier angegebene Name wird gemeinsam mit deinem Kommentar auf der Website veröffentlicht.

Deine E-Mail-Adresse wird zur einmaligen Benachrichtigung bei Veröffentlichung des Kommentars genutzt.

Benachrichtigung per E-Mail über Antworten auf meinen Kommentar erhalten.

Bitte tippe die Zahlenkombination "7272" ein, nur dann kann ich deinen Kommentar entgegennehmen.

Bitte fülle dieses Feld nicht aus, nur dann kann ich deinen Kommentar entgegennehmen.