Go to English page

ViaThinkSoft CodeLib

Dieser Artikel befindet sich in der Kategorie:
CodeLibHowTosApache

Revision: 27 April 2019

Dieses Beispiel zeigt auf, wie man Let's Encrypt verwenden kann, um Zertifikate mit einer individuellen Automatik zu erstellen, anstelle sich auf die 100% Automatik von Let's Encrypt zu verlassen. Sie behalten somit stets die volle Kontrolle über Ihre Webserver-Konfiguration und die Zertifikate und müssen sich nicht sorgen, dass ein fremdes Programm Ihre Konfigurationen des Webservers stört. Das Verwenden der Zertifikate für andere Dienste (MySQL, FTP, IMAP, SMTP etc) ist dadurch einfach zu bewerkstelligen.

Diese Anleitung erfordert, dass Sie bereits übere eine fertig eingerichtete Apache-Installation verfügen, und sich mit SSL auskennen. Diese Anleitung spricht insbesondere Webmaster an, die von einer bestehenden CA zu Let's Encrypt wechseln wollen.

In unserem Beispiel speichern wir unsere Zertifikats-Relevanten Daten in /daten/ssl/letsencrypt . Alle hier gezeigten Verzeichnis-Namen und Einstellungen sind selbstverständlich nur Beispiele und können abgeändert werden.

Schritt 1: Einrichten von Certbot, Apache und dem Cronjob (einmalig)

1. Anlegen der relevanten Ordner

sudo mkdir /daten
sudo mkdir /daten/ssl
sudo mkdir /daten/ssl/letsencrypt

2. Installieren und Einrichten von Apache2-Modulen:

Führen Sie folgende Befehle aus:

sudo aptitude update
sudo aptitude install libapache2-mod-macro
sudo a2enmod macro
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod ssl

3. Einrichten einiger Macros in Apache. Erstellen Sie hierfür /etc/apache2/sites-available/000--macros.conf

Achtung! Es handelt sich hierbei um zwei Bindestriche. "000--macros.conf" muss nämlich vor "000-default.conf" einsortiert/geladen werden.

<Macro LetsEncryptProxy>
        <IfModule mod_proxy.c>
                ProxyPass "/.well-known/acme-challenge/" "http://127.0.0.1:999/.well-known/acme-challenge/" retry=1
                ProxyPassReverse "/.well-known/acme-challenge/" "http://127.0.0.1:999/.well-known/acme-challenge/"
                <Location "/.well-known/acme-challenge/">
                        ProxyPreserveHost On
                        Order allow,deny
                        Allow from all
                        Require all granted
                </Location>
        </IfModule>
</Macro>

<Macro LetsEncryptSSL $sitedirname $ssl_log>
        SSLEngine on
        SSLCertificateFile "/daten/ssl/letsencrypt/$sitedirname/certificate.pem"
        SSLCertificateKeyFile "/daten/ssl/letsencrypt/$sitedirname/private.key"
        SSLCertificateChainFile "/daten/ssl/letsencrypt/$sitedirname/intermediate_ca.pem"
        SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
        CustomLog "$ssl_log" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</Macro>

4. Aktivieren Sie die Konfigurationsdatei:

cd /etc/apache2/sites-enabled/
ln -s ../sites-available/000--macros.conf

5. OCSP-Stapling in Apache aktivieren

Die /etc/apache2/mods-enabled/ssl.conf editieren und folgendes ans Ende anhängen:


SSLUseStapling          on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache        shmcb:/var/run/ocsp(128000)

6. Fügen Sie die folgende Zeile in jedem <VirtualHost> Block in Ihren Webseiten-Konfigurationen (/etc/apache2/sites-available/*.conf) ein:

Use LetsEncryptProxy

Sollte die Validierung der Domain im späteren Prozess fehlschlagen, könnte eventuell eine Rewrite-Rule dies verhindern. In diesem Fall muss folgende Zeile dem jeweiligen Rewrite-Abschnitt hinzugefügt werden:

RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/

7. Neustarten von Apache:

sudo service apache2 restart

8. Erstellen Sie folgendes Script: /daten/ssl/letsencrypt/renew-all.sh (mit Ausführungs-Berechtigungen per chmod +x renew-all.sh)

#!/bin/bash

DIR=$( dirname "$0" )

"$DIR"/website1/renew.sh
"$DIR"/website2/renew.sh
"$DIR"/website3/renew.sh
...

# Falls Sie eines der Zertifikate für andere Dienste verwenden, aktivieren Sie bitte die jeweiligen Einträge durch Entfernen der Raute:
#service vsftpd restart
#service postfix restart
#service cyrus-imapd restart

#service icinga2 stop
#service webmin stop
service apache2 stop
service mysql stop

service mysql start
service apache2 start
#service webmin start
#service icinga2 start

9. Fügen Sie einen Cronjob für den Benutzer root hinzu, um die Zertifikats-Erneuerung regelmäßig durchzuführen:

sudo crontab -e

Und fügen Sie folgende Zeile an:

0   0   1   *   *    /daten/ssl/letsencrypt/renew-all.sh

10. Installieren von Certbot:

sudo aptitude update
sudo aptitude install certbot

Sofern das Paket "certbot" in Ihrer Linux-Konfiguration nicht verfügbar ist, dann führen Sie die folgenden Befehle durch:

sudo aptitude update
sudo aptitude install git
cd /daten/ssl/letsencrypt/
git clone https://github.com/letsencrypt/letsencrypt
mv letsencrypt _certbot

11. Einrichten der Linux-Benutzer:

groupadd ssl
usermod -a -G ssl www-data
chown -R root:ssl /daten/ssl/


Schritt 2: Einrichten der Scripts für eine neue Webseite, hier im Beispiel "website1":

1. Erstellen Sie die Verzeichnisse /daten/ssl/letsencrypt/website1/ und /daten/ssl/letsencrypt/website1/old/

2. Erstellen Sie /daten/ssl/letsencrypt/website1/openssl.cnf und ersetzen Sie die Domain-Namen.

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
CN = www.domain1.de

[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
# Extention "Must Staple"
# Remove this line if you want to use the certificate with services that do not support OCSP-Must-Staple (e.g. Postfix)
1.3.6.1.5.5.7.1.24 = DER:30:03:02:01:05

[alt_names]
DNS.1 = domain1.de
DNS.2 = www.domain2.de
DNS.3 = domain2.de
...

3. Erstellen Sie /daten/ssl/letsencrypt/website1/config und tragen Sie Ihre E-Mail-Adresse ein:

EMAIL="..."
RSASIZE=4096
SERVER="https://acme-v02.api.letsencrypt.org/directory"
# Note: staging still uses ACMEv1
#SERVER="https://acme-staging.api.letsencrypt.org/directory"

4. Erstellen Sie /daten/ssl/letsencrypt/website1/renew.sh (mit Ausführungs-Berechtigungen per chmod +x renew.sh)

#!/bin/bash

# --- Initialization

DIR=$( dirname "$0" )
cd "$DIR"

if [ ! -f openssl.cnf ]; then
        echo "Bitte das Script im jeweiligen Verzeichnis aufrufen." >&2
        exit 2
fi

. config

# --- Clean up

rm *_pkcs12.p12 2> /dev/null
rm *_private.key 2> /dev/null
rm *_cert.pem 2> /dev/null
rm *_chain.pem 2> /dev/null
rm *_req.csr 2> /dev/null
rm certbot.log 2> /dev/null

# --- Create private key

openssl genrsa -out 0000_priv.key $RSASIZE
if [ $? -ne 0 ]; then
        echo "FAILED TO CREATE PRIVATE KEY" >&2
        exit 1
fi
chown root:ssl 0000_priv.key
chmod 640 0000_priv.key

# --- Create certificate request

openssl req -new -batch -sha256 \
        -key 0000_priv.key \
        -config openssl.cnf \
        -out 0000_req.csr
if [ $? -ne 0 ]; then
        echo "FAILED TO CREATE CERTIFICATE REQUEST" >&2
        exit 1
fi

# --- Ask server to sign the certificate

if [ -f ../_certbot/certbot-auto ]; then
        EX="../_certbot/certbot-auto"
else
        EX="certbot"
fi

$EX certonly \
        --authenticator standalone \
        --preferred-challenges http-01 --http-01-port 999 \
        --server $SERVER \
        --text \
        --email $EMAIL \
        --must-staple \
        --staple-ocsp \
        --csr 0000_req.csr
if [ $? -ne 0 ]; then
        echo "CERTBOT FAILED" >&2
        exit 1
fi

# --- Security check: check if certificate and private key are matching

a=$( openssl x509 -noout -modulus -in 0000_cert.pem | openssl sha256 )
b=$( openssl rsa -noout -modulus -in 0000_priv.key | openssl sha256 )

if [ "$a" != "$b" ]
then
        echo "FEHLER: Zertifikat stimmt nicht mit privatem Schlüssel überein!" >&2
        exit 1
fi

# --- PKCS#12 erstellen

openssl pkcs12 -export -in 0000_cert.pem -inkey 0000_priv.key -certfile 0000_chain.pem -out 0000_pkcs12.p12 -passout pass:
if [ $? -ne 0 ]
then
        echo "FEHLER bei PCKS#12-Erstellung!" >&2
        if [ -f 0000_pkcs12.p12 ]
        then
                chmod 600 0000_pkcs12.p12
                rm 0000_pkcs12.p12
        fi
        exit 1
fi

if [ ! -f 0000_pkcs12.p12 ]
then
        echo "FEHLER! PCKS#12 konnte nicht erstellt werden!" >&2
        exit 1
fi

chmod 600 0000_pkcs12.p12

# --- Activate certs

# Files created by certbot:
# 0000_cert.pem  = cert.pem (i.e., the server certificate)
# 0000_chain.pem = chain.pem (i.e., the intermediate certificate)
# 0001_chain.pem = fullchain.pem (i.e., a concatenation of cert.pem + chain.pem in one file).

mv -f 0000_pkcs12.p12 "old/$(date +%s).p12"
mv -f 0000_priv.key private.key
mv -f 0000_cert.pem certificate.pem
mv -f 0000_chain.pem intermediate_ca.pem
rm 0000_req.csr
rm certbot.log 2> /dev/null
rm 0001_chain.pem

# --- Additional security check: X509 Lint

if [ -f "../_x509lint/x509lint" ]; then
        ../_x509lint/x509lint certificate.pem
fi

# --- Delete expired archived certificates

FILES=old/*.p12
for f in $FILES
do
        openssl pkcs12 -in "$f" -clcerts -nokeys -passin pass: | openssl x509 -noout -checkend 0 > /dev/null
        if [ $? -eq 1 ]; then
                echo "$f has expired. Deleting."
                rm "$f"
        fi
done

# --- Post create: Restart servers etc.

if [ -f postcreate.sh ]; then
        ./postcreate.sh
fi

5. (Optional) Erstellen Sie folgendes Script /daten/ssl/letsencrypt/website1/recover_cert.sh, das im Notfall aufgerufen werden kann, um ein Zertifikat samt privatem Schlüssel wiederherzustellen (mit Ausführungs-Berechtigungen per chmod +x recover_cert.sh)

#!/bin/bash

DIR=$( dirname "$0" )
cd "$DIR"

if [ "$1" == "--help" ]; then
        echo "Syntax: $0 <p12file>"
        exit 2
fi

if [ ! -f "$1" ]; then
        echo "ERROR: File '$1' does not exist" >&2
        exit 1
fi

openssl pkcs12 -in "$1" -nocerts -out tmp_priv.key -passin pass: -nodes
if [ $? -ne 0 ]; then
        echo "ERROR recovering the private key" >&2
        rm tmp_priv.key 2> /dev/null
        rm tmp_cert.pem 2> /dev/null
        rm tmp_ca.pem 2> /dev/null
        exit 1
fi

openssl pkcs12 -in "$1" -clcerts -nokeys -out tmp_cert.pem -passin pass:
if [ $? -ne 0 ]; then
        echo "ERROR recovering the certificate" >&2
        rm tmp_priv.key 2> /dev/null
        rm tmp_cert.pem 2> /dev/null
        rm tmp_ca.pem 2> /dev/null
        exit 1
fi

openssl pkcs12 -in "$1" -cacerts -nokeys -out tmp_ca.pem -passin pass:
if [ $? -ne 0 ]; then
        echo "ERROR recovering the intermediate certificate" >&2
        rm tmp_priv.key 2> /dev/null
        rm tmp_cert.pem 2> /dev/null
        rm tmp_ca.pem 2> /dev/null
        exit 1
fi

mv -f tmp_priv.key private.key
if [ $? -ne 0 ]; then
        echo "ERROR moving the private key" >&2
        exit 1
fi

mv -f tmp_cert.pem certificate.pem
if [ $? -ne 0 ]; then
        echo "ERROR moving the certificate" >&2
        exit 1
fi

mv -f tmp_ca.pem intermediate_ca.pem
if [ $? -ne 0 ]; then
        echo "ERROR moving the intermediate certificate" >&2
        exit 1
fi

echo "Certificate $1 recovered."

6. Editieren Sie die jeweiligen Einstellungen der jeweiligen Webseite z.B. in /etc/apache2/sites-available/website.conf .

Falls Sie nur einen <VirtualHost> Block haben (für Port 80), duplizieren Sie diesen Block, sodass Sie einen Block für Port 80 (HTTP) und einen Block für Port 443 (HTTPS) haben.
Im HTTPS Block fügen Sie den folgenden Befehl ein um die Let's Encrypt-Zertifikate zu aktivieren. (Falls Sie mehrere Port 443 Blöcke haben, fügen Sie die Zeile zu jedem Block hinzu)

Use LetsEncryptSSL website1 /var/log/.../website1/ssl_request.log


Schritt 3: Testen

Rufen Sie /daten/ssl/letsencrypt/renew-all.sh das erste Mal manuell auf und folgen den Bildschirm-Anweisungen bzw. achten auf etwaige Fehlermeldungen.
Unter anderem müssen Sie einmalig den Regeln zustimmen und angeben, ob Sie in die EFF-Mailingliste aufgenommen werden möchten.

Ersetzen Sie in den config-Dateien "acme-v02" durch "acme-staging", um die Zertifikate von einer Test-CA zu erhalten. Dadurch wird das Erstellen unnötiger Test-Zertifikate verhindert.


Mögliche Fehlerquellen

Tritt ein Timeout auf, obwohl die Webseite öffentlich erreichbar sein kann, so kann es sein, dass IPv6 gestört ist. Let's Encrypt bevorzugt IPv6 (AAAA) DNS-Records.


Getestet mit

- Debian Stretch (amd64)
- Raspberry Pi 3 "Raspbian" (armv7l)
Daniel Marschall
ViaThinkSoft Mitbegründer