Keine Magie - Certbot mit Webserver, ohne Plugin
Als ich das Blog eingerichtet habe, musste ich auch SSL konfigurieren.
Wie viele benutze ich dafür Letsencrypt über Certbot.
Damit certbot ein Zertifikat ausstellen kann muss nachgewiesen werden das man die praktische Verfügungsgewalt über die Domain hat für die man ein Zertifikat ausgestellt bekommen möchte. Das geht u.a. in dem man den Response für eine Challenge die man gesendet bekommt entweder als DNS-Eintrag hinterlegt, auf der Maschine die über den DNS-Namen erreichbar ist einen Standalone-Webserver starten lässt (certbot bringt den mit) oder in einem vorhandenen Webserver auf jener Maschine ihn in einer Datei hinterlegt.
Der Weg per Domain-Eintrag war mir zu umständlich, ein Standalone-Webserver geht nicht, weil auf dem System schon apache2 läuft der das Blog ausliefern soll und damit die notwendigen Ports belegt sind.
Verblieb also der Weg per vorhandenem Webserver.
Dafür bringt certbot Plugins für apache2 und nginx mit, die Magie machen.
Magie mag ich nicht, jedenfalls nicht wenn ich sie nicht verstehe oder selbst gemacht habe (was nicht zwangsläufig das Gleiche ist ;)).
Sie hat immer den Nachteil das im unwahrscheinlichen Fall eines Druckabfalls… ähh… auftreten eines Problems hat man keine Ahnung was abgeht.
Im folgenden also die Lösung wie man es manuell macht.
Problem
Certbot per Schreiben in das Webroot ermöglichen den Response auf die Challenge für die Zertifikatserstellung zu schreiben. Gleichzeitig Zugriff auf das Blog nur über TLS zulassen.
Lösung
Der HTTP-Teil
Eine neue Datei in /etc/apache2/sites-enabled (in Debian-Distros, bei anderen ist das anders organisiert), mit Inhalt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<VirtualHost *:80>
DocumentRoot /var/www/blog
ServerName blog.shellkraut.de
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<If "%{REQUEST_URI} !~ /\.well-known/">
Redirect "/" "https://blog.shellkraut.de/"
</If>
<Directory "/var/www/blog/.well_known">
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Der Challenge-Response wird auf Port 80 (unverschlüsselt) erwartet. Weil es existiert ja kein gültiges Zertifikat für Port 443.
Das wird durch den virtuellen Host in den Zeilen 1-3 erledigt, dieser lauscht auf Port 80 (Zeile 1), aber für die Domain für die das Zertifikat ausgestellt werden soll (Zeile 3) und in einem anderen VirtualHost noch mal für TLS auf Port 443 konfiguriert ist.
In den Zeilen 9-11:
<If "%{REQUEST_URI} !~ /\.well-known/"> Redirect "/" "https://blog.shellkraut.de/" </If>
wird sichergestellt das alle Anfragen die über http reinkommen für die blog-Domain an https weitergeleitet werden.
Das erledigt der Redirect-Eintrag (Zeile 10).
📝
|
"/" erfasst alles was mit "/" beginnt, also nicht nur http://blog.shellkraut.de/, sondern auch http://blog.shellkraut.de/archiv.html. Im Prinzip wie der reguläre Ausdruck: /.* |
🪛
|
Die URL auf die weitergeleitet wird muss mit "/" enden → "https://blog.shellkraut.de/" / wird beim Umschreiben der URL abgeschnitten (da darauf gematcht wurde) und nur was nach dem / kommt wird an die URL an die weitergeleitet wird angefügt. Aus http://blog.keinhorn.de/archiv.html würde ohne das / https://blog.keinhorn.dearchiv.html |
Certbot/Letsencrypt benötigt eine Ausnahme von der Weiterleitung, damit es den Response für die Zertifikats-Challenge per http://blog.shellkraut.de/.well_known/acme-challenge/ abrufen kann, also unter http, nicht https.
Das erledigt Zeile 9 (Zeile 11 ist der Abschluss des Blocks):
<If "%{REQUEST_URI} !~ /\.well-known/">
-
If sorgt dafür das der Block nur ausgeführt wird wenn der nachfolgende Ausdruck wahr ergibt.
-
REQUEST_URI ist eine Variable die den Pfad (path) der URL enthält → also alles was nach dem Host-Teil kommt - http://blog.shellkraut.de/.well-known/acme-challenge/somefile .
-
!~ der darauffolgende reguläre Ausdruck soll nicht zutreffen.
-
/\.well-known/ der eigentliche reguläre Ausdruck. \ escapes ., weil . eigentlich für ein beliebiges Zeichen steht, hier aber für . stehen soll (verstecktes Verzeichnis)
🪛
|
Der reguläre Ausdruck muss in / eingefasst sein.
AH00526: Syntax error on line 32 of /etc/apache2/sites-enabled/001-blog_shellkraut.conf: Cannot parse condition clause: syntax error, unexpected T_ERROR, expecting T_REGEX or T_REGEX_I: Parse error near '\\' Action 'configtest' failed. Deutet daraufhin das man es vergessen hat ;) |
Directory in Zeile 13 ist die Konfiguration für das Dateisystem-Verzeichnis wo sich .well_known befindet. Nichts spezielles.
Alles in allem also:
-
lausche auf Port 80
-
schaue ob die Anfrage für den Host blog.shellkraut.de ist
-
wenn der Pfad in der URL nicht .well-known/ enthält, dann leite weiter auf https://blog.shellkraut.de/
-
ansonsten beantworte die Anfrage
Aufruf von certbot
Erstellen eines Restart-Scriptes
Damit Apache2 ein neu ausgestelltes Zertifikat auch benutzt muss er neugestartet werden.
Will man zum beziehen neuer Zertifikate den automatisch angelegten Cron-Job benutzen (was Sinn macht), dann sollte man auch den Webserver automatisch neustarten, so dass man sich nicht mehr um die Erneuerung von Zertifikaten kümmern muss.
-
Datei an einer beliebigen Stelle (muss durch root-Benutzer erreichbar sein) erstellen mit folgendem Inhalt:
#! /bin/bash systemctl restart apache2.service
Anschließend Datei ausführbar machen (es wird angenommen das der Besitzer der Datei root ist)
chmod u+x /root/certbot_scripts/restart_apache.sh
Pfad muss ggf. angepasst werden
Aufrufen von certbot
Beim ersten Mal:
certbot certonly -d blog.shellkraut.de --webroot --webroot-path "/var/www/blog/" --deploy-hook /root/certbot_scripts/restart_apache.sh
-
--webroot besagt das die Authentifizierung per Datei im Web-Verzeichnis erfolgen soll
-
--webroot-path gibt den Pfad an wo das Webroot zu finden ist im Dateisystem
-
muss das Verzeichnis sein was ausgeliefert wird wenn man die Domain aufruft (in meinem Fall blog.shellkraut.de)
-
wichtig! man kann das auch weglassen und interaktiv angeben, aber dann funktioniert "certbot renew" nicht, weil der Pfad nicht gespeichert wurde
-
-
--deploy-hook Script welches ausgeführt wird wenn ein neues Zertifikat bezogen wurde
-
es wird nur ausgeführt wenn erfolgreich ein neues Zertifikat bezogen wurde. Das ist wichtig, weil das renew-Script standardmäßig per cron alle 24-Stunden aufgerufen wird → in der Mehrzahl der Fälle wird also kein neues Zertifikat bezogen
-
in meinem Fall startet es den Webserver neu
-
Anschließend werden die Pfade für den privaten und den öffentlichen Schlüssel ausgegeben die man in den VirtualHost für die TLS-Verbindung eintragen muss.
Bei nachfolgenden Zertifikats-Erneuerungen ist es:
certbot renew
oder
certbot certonly -d blog.shellkraut.de
In beiden Fällen wird kein Webroot abgefragt, da sich certbot merkt wo es hinschreiben muss.
Praktisch wird aber auch schon nach dem ersten abrufen des Zertifikats ein Cron-Job angelegt der das Zertifikat vor Ablauf erneuert.
Der HTTPS-Teil
Der Vollständigkeit halber hier noch die https-Konfiguration:
1
2
3
4
5
6
7
8
9
10
11
12
13
<VirtualHost *:443>
ServerName blog.shellkraut.de
DocumentRoot /var/www/blog
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/blog.shellkraut.de/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/blog.shellkraut.de/privkey.pem
</VirtualHost>
-
VirtualHost Zeile 1 lässt den virtuellen Host auf Port 443 lauschen
-
DocumentRoot Zeile 3 ist identisch wie bei der http-Konfiguration und beschreibt den Pfad im Dateisystem wo die Dateien der Seite zu finden sind
-
SSLEngine Zeile 8 definiert das SSL benutzt werden soll für den VirtualHost
-
SSLCertificateFile Pfad zum öffentlichen Schlüssel (Public Key), der wurde beim Aufrufen von certbot ausgegeben
-
SSLCertificateKeyFile Pfad zum privaten Schlüssel (Private Key), der wurde beim Aufrufen von certbot ausgegeben
HINT: Es kann passieren das man beim laden der SSL-Konfiguration (neustart des Servers oder apachectl) einen Fehler bekommt das modSSL nicht vorhanden ist.
Das SSL-Module ist in diesem Fall nicht geladen.
sudo a2enmod ssl sudo service apache2 restart
löst das Problem.
Abschließend
Sollte man:
certbot --dry-run renew
aufrufen um zu sehen das auch wirklich alles funktioniert wenn das Zertifikat automatisch erneuert werden würde.
--dry-run stellt kein neues Zertifikat aus, führt nur alle Schritte aus als wäre ein neues ausgestellt worden.
Fehler/mögliche Probleme
Parameter per certbot certonly geändert aber certbot renew/ certbot certonly benutzt immer noch die alten
-
wenn man
certbot certonly --dry-run --neue Parameter
aufruft werden diese nicht gespeichert.
Man muss tatsächlich certbot ohne --dry-run aufrufen damit die Parameter für eine Domain geändert werden
Certbot renew "Missing command line flag" "webroot"
Folgender Fehler erscheint nach certbot renew
Failed to renew certificate blog.shellkraut.de with error: Missing command line flag or config entry for this setting: Input the webroot for blog.shellkraut.de:
Wahrscheinlich Parameter --webroot-path nicht angegeben aber --webroot konfiguriert für die betroffene Domain
Der ursprüngliche Aufruf von certbot sah in etwa so aus:
certbot certonly -d blog.shellkraut.de --webroot --deploy-hook /root/certbot_scripts/restart_apache.sh
Dabei wurde der Webroot-Pfad manuell abgefragt.
Dieser Pfad wird allerdings nicht gespeichert und damit weiß renew dann nicht wo es die Datei hinschreiben soll.
Kann repariert werden in dem man certbot certonly erneut für die Domain manuell aufruft und den Parameter --webroot-path hinzufügt:
certbot certonly -d blog.shellkraut.de --webroot --webroot-path "/var/www/blog/" --deploy-hook /root/certbot_scripts/restart_apache.sh
Apache wird mehrfach neugestartet
Der Deploy-Hook (genauso der pre- und post-hook wenn angegeben) werden für jedes Zertifikat für das sie konfiguriert sind aufgerufen, nicht nur einmal pro Dienst.
Das heißt gibt es mehrere Zertifikate für einen Dienst (zum Beispiel apache2) und diese werden alle zur gleichen Zeit erneuert, und alle haben ein deploy-hook der apache neustartet, wird apache nach jeder erfolgreichen (im Falle von deploy-hook, bei den beiden anderen bei jedem Versuch) erneuerung neugestartet.
Möglich Lösungen wären:
Neustart über Cron-Script welches certbot renew aufruft, in dem man "&& systemctl restart apache2.service" an den Befelh anhängt. Problem hier das der Cron-Job jede Nacht aufgerufen wird und certbot auch im Falle das keine Zertifikate neu erstellt wurden (weil sie nich abgelaufen sind) 0 als Return-Wert zurückgibt, der Neustart von apache2 also jede Nacht durchgeführt werden würde, auch ohne neue Zertifikate.
Ein Multidomain-Zertifikat benutzen. Man kann mehrere Domains - mehrfach "-d Domainname" bei certbot certonly angeben - beziehen.
In diesem Falle gäbe es nur einen Zertifikatsrequest, das Zertifikat wäre aber für (alle/mehrere) Domains die apache (oder welcher Service auch immer) verwaltet gültig.
Da es nur einen Zertifikats-Refresh-Request gäbe, gäbe es auch nur einen Aufruf der hooks und ggf. nur einen restart.