Go to English page

ViaThinkSoft CodeLib

Dieser Artikel befindet sich in der Kategorie:
CodeLibHowTosApache

Revision: 20. November 2024

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.1. Anlegen der relevanten Ordner

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

1.2. Installieren und Einrichten von Apache2-Modulen:

Führen Sie folgende Befehle aus:

sudo a2enmod macro
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod ssl

1.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>

1.4. Aktivieren Sie die Konfigurationsdatei:

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

1.5. OCSP-Stapling in Apache aktivieren

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


SSLUseStapling          on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

1.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/

1.7. Neustarten von Apache:

sudo service apache2 restart

1.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" )

TOTALRES=0

for subdir in "$DIR"/*/; do
    if [ -f "${subdir}renew.sh" ]; then
        "${subdir}renew.sh"
        if [ $? -ne 0 ]; then
            TOTALRES=1
        fi
        sleep 1
    fi
done

service apache2 restart

# 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 mysql restart
# ...

exit $TOTALRES

1.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

1.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

1.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":

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

2.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
...

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

EMAIL="..."

ECCURVE=secp384r1
#RSASIZE=4096

SERVER="https://acme-v02.api.letsencrypt.org/directory"
#SERVER="https://acme-staging-v02.api.letsencrypt.org/directory"

## If WILDCARD_APITOKEN is missing, reverse proxy method will be used, otherwise DNS method.
## Wildcard DNS method requires the scripts wildcard_authenticator.sh and wildcard_cleanup.sh
## which must be prepared for your domain provider
#WILDCARD_APITOKEN=""

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

#!/bin/bash

# --- Initialization

SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
DIR=$( dirname "$0" )
cd "$DIR"

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

if [ ! -d "old/" ]; then
        mkdir old
fi

. config

# --- Clean up

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

# --- Create private key

if [ "$ECCURVE" != "" ]; then
        openssl ecparam -name "$ECCURVE" -genkey -out 0000_priv.key
else
        openssl genrsa -out 0000_priv.key $RSASIZE
fi
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

if [ "$WILDCARD_APITOKEN" == "" ]; then
        $EX certonly \
                --authenticator standalone \
                --preferred-challenges http-01 --http-01-port 999 \
                --server $SERVER \
                --text \
                --email $EMAIL \
                --agree-tos \
                --must-staple \
                --staple-ocsp \
                --csr 0000_req.csr
        RES=$?
else
        $EX certonly \
                --manual --manual-auth-hook "$SELF_PATH/wildcard_authenticator.sh" --manual-cleanup-hook "$SELF_PATH/wildcard_cleanup.sh" \
                --preferred-challenges dns \
                --server $SERVER \
                --text \
                --email $EMAIL \
                --agree-tos \
                --must-staple \
                --staple-ocsp \
                --csr 0000_req.csr
        RES=$?
fi

if [ -f /var/log/letsencrypt/letsencrypt.log ]; then
        cp /var/log/letsencrypt/letsencrypt.log letsencrypt.log
fi
if [ $RES -ne 0 ]; then
        echo "CERTBOT FAILED WITH EXIT CODE $RES ($DIR)" >&2
        exit 1
fi

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

if [ "$ECCURVE" != "" ]; then
        # Extract the public key from the certificate
        a=$( openssl x509 -in 0000_cert.pem -pubkey -noout 2>/dev/null | openssl ec -pubin -outform der 2>/dev/null | openssl dgst -sha256 )
        # Derive the public key from the private key
        b=$( openssl ec -in 0000_priv.key -pubout -outform der 2>/dev/null | openssl dgst -sha256 )
else
        a=$( openssl x509 -noout -modulus -in 0000_cert.pem | openssl sha256 )
        b=$( openssl rsa -noout -modulus -in 0000_priv.key | openssl sha256 )
fi

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

# --- PKCS#12 erstellen

# TODO: add $PREVDIR to the name
openssl pkcs12 -export -in 0000_cert.pem -inkey 0000_priv.key -certfile 0000_chain.pem -name "Server Certificate" -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

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

# --- 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 -f 0000_req.csr
rm -f certbot.log 2> /dev/null
rm -f 0001_chain.pem

# --- Delete expired archived certificates

FILES=old/*.p12
for f in $FILES
do
        # TODO: das ist nicht sauber, denn wenn ein anderer fehler im zertifikat vorliegt, dann würde auch $?=1 sein
        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 "$f"
        fi
done

# --- Post create: Restart servers etc.

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

2.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."

2.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-v02", 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.

Wenn Apache nicht startet, können folgende Befehle helfen, den Fehler zu finden:

  • apachectl configtest
  • journalctl -u apache2.service
  • systemctl status apache2.service
  • journalctl -xe
Daniel Marschall
ViaThinkSoft Mitbegründer