# Wildcard Let's Encrypt Cert Creator für Plesk Legt in Plesk den DNS-Eintrag für einen Namen an (bzw. aktualisiert ihn) und holt anschließend per **DNS-01-Challenge** ein **Wildcard-Zertifikat** von Let's Encrypt. Alle Bestandteile des Zertifikats landen als einzelne Dateien im Projektverzeichnis – zusätzlich als kombinierte `bundle.pem` und als passwortgeschützte `cert.pfx`. ``` ./run.sh vpn.example.com 192.0.2.10 ``` erzeugt ein Zertifikat für `vpn.example.com` **und** `*.vpn.example.com`. --- ## 1. Voraussetzungen * Docker (oder lokal Python 3.10+) * Die **Zone** muss auf dem Plesk-Server liegen und Plesk muss der **autoritative Nameserver** dafür sein (sonst kann die DNS-Challenge nicht validiert werden). Der Name selbst braucht *kein* eigenes Abo und keine eigene Domain zu sein: für `endian.fon-aria.de` reicht die Zone `fon-aria.de` – das Tool sucht sich die passende Zone von unten nach oben zusammen und legt dort einfach die Records an. * Ein Plesk-API-Zugang (API-Key empfohlen). ### Plesk API-Key erzeugen Auf dem Plesk-Server (SSH, als root): ```bash plesk bin secret_key --create -ip-address \ -description "wildcard cert creator" ``` Der ausgegebene Key kommt in die `.env` als `PLESK_API_KEY`. Alternativ geht auch `PLESK_USER=admin` + `PLESK_PASSWORD=...`. > Der Port 8443 des Plesk-Panels muss von dem Rechner aus erreichbar sein. ## 2. Einrichten ```bash cp .env.example .env $EDITOR .env # Plesk-Daten, ACME_EMAIL, CERT_PASSWORD eintragen chmod +x run.sh ``` Wichtig in der `.env`: | Variable | Bedeutung | |---|---| | `PLESK_HOST` / `PLESK_PORT` | Plesk-Panel (Standard-Port 8443) | | `PLESK_API_KEY` | API-Key (oder `PLESK_USER`/`PLESK_PASSWORD`) | | `PLESK_VERIFY_TLS` | bei selbstsigniertem Panel-Zertifikat `false` | | `ACME_EMAIL` | Kontaktadresse für Let's Encrypt | | `ACME_STAGING` | `true` zum Testen (kein Rate-Limit-Risiko) | | `CERT_PASSWORD` | Passwort der `.pfx` und des verschlüsselten Keys | | `KEY_TYPE` | `rsa` (Standard, 4096 Bit) oder `ec` | | `PFX_LEGACY_COMPAT` | `true` für alte Windows-/Java-Importer (SHA1/3DES) | ## 3. Benutzen ```bash # DNS-Eintrag anlegen/aktualisieren + Wildcard-Zertifikat holen ./run.sh vpn.example.com 192.0.2.10 # erstmal testen (Staging-CA von Let's Encrypt) ./run.sh vpn.example.com 192.0.2.10 --staging # nur den DNS-Eintrag setzen, kein Zertifikat ./run.sh vpn.example.com 192.0.2.10 --dns-only # Zertifikat erneuern, obwohl das alte noch gültig ist ./run.sh vpn.example.com 192.0.2.10 --force # ohne Wildcard, dafür mit weiteren Namen ./run.sh vpn.example.com 192.0.2.10 --no-wildcard --san www.example.com ``` Ohne Docker: ```bash python3 -m venv .venv && . .venv/bin/activate pip install -r requirements.txt python -m app.main vpn.example.com 192.0.2.10 ``` ### Alle Optionen | Option | Wirkung | |---|---| | `--ip ` | Alternative zum zweiten Positionsargument (IPv4 → A, IPv6 → AAAA) | | `--san ` | zusätzlicher Name im Zertifikat (mehrfach möglich) | | `--zone ` | Plesk-Zone explizit angeben statt sie zu ermitteln | | `--no-wildcard` | nur der reine Name, ohne `*.` | | `--skip-dns` | A/AAAA-Record unangetastet lassen | | `--dns-only` | nur DNS, kein Zertifikat | | `--staging` | Let's-Encrypt-Staging-Umgebung | | `--force` | erneuern, auch wenn noch gültig | | `--keep-txt` | `_acme-challenge`-Records stehen lassen (Debugging) | | `--ignore-propagation-timeout` | trotz fehlender DNS-Propagation weitermachen | | `--env ` | andere `.env` verwenden | | `-v` | ausführliche Ausgabe (inkl. XML-Requests) | ## 4. Was wird erzeugt Alles unter `certs//`: | Datei | Inhalt | |---|---| | `privkey.pem` | Private Key, PKCS#8, unverschlüsselt | | `privkey-traditional.pem` | derselbe Key im klassischen OpenSSL-Format | | `privkey-encrypted.pem` | Key, verschlüsselt mit `CERT_PASSWORD` | | `pubkey.pem` | öffentlicher Schlüssel | | `csr.pem` | der verwendete Certificate Signing Request | | `cert.pem` / `cert.crt` | das reine Zertifikat (Leaf) | | `cert.der` | Leaf im DER-Format (für Windows) | | `chain.pem` | nur die Zwischenzertifikate | | `chain-01.pem`, … | jedes Zwischenzertifikat einzeln | | `fullchain.pem` | Leaf + Kette (das, was Webserver meist wollen) | | `bundle.pem` | Key + Leaf + Kette in einer Datei | | `cert.pfx` | PKCS#12 mit Key + Kette, Passwort = `CERT_PASSWORD` | | `cert-info.txt` / `cert-info.json` | Subject, SANs, Gültigkeit, Fingerprints | Der ACME-Account-Key liegt in `data/` – **nicht löschen**, sonst wird bei jedem Lauf ein neuer Let's-Encrypt-Account registriert. Schnellprüfung: ```bash openssl x509 -in certs/vpn.example.com/cert.pem -noout -text openssl pkcs12 -info -in certs/vpn.example.com/cert.pfx -nodes -passin pass: ``` ## 5. Wie es abläuft 1. Plesk nach der Zone fragen – erst nach dem vollen Namen, dann Label für Label nach oben (`endian.fon-aria.de` → `fon-aria.de`), jeweils als Domain und als Abo. Der Server muss dafür nicht alle Domains herausrücken; mit `--zone` lässt sich die Zone auch fest vorgeben. 2. A/AAAA-Record anlegen oder auf die neue IP korrigieren. 3. Prüfen, ob überhaupt erneuert werden muss (`RENEW_DAYS_BEFORE_EXPIRY`, Standard 30 Tage). 4. Key + CSR für `` und `*.` erzeugen. 5. ACME-Order bei Let's Encrypt, für jede Autorisierung einen `_acme-challenge`-TXT-Record in Plesk setzen (alte Reste werden vorher entfernt). 6. Warten, bis **alle autoritativen Nameserver** die TXT-Records ausliefern (`DNS_PROPAGATION_TIMEOUT`). 7. Challenges beantworten, Zertifikat abholen, TXT-Records wieder löschen. 8. Alle Dateien schreiben und die Zertifikatsdaten ausgeben. ## 6. Automatische Erneuerung Das Skript ist idempotent: Läuft es, obwohl das Zertifikat noch länger als `RENEW_DAYS_BEFORE_EXPIRY` Tage gültig ist, passiert nichts. Also einfach per Cron: ```cron 17 3 * * * cd /home/duffy/Dokumente/programmierung/wildcard-lets-encrypt-cert-plesk-creator && ./run.sh vpn.example.com 192.0.2.10 >> renew.log 2>&1 ``` ## 7. Wenn etwas klemmt | Symptom | Ursache / Lösung | |---|---| | `Plesk rejected the credentials (HTTP 401)` | API-Key falsch oder nicht für diese Quell-IP erzeugt | | `No Plesk DNS zone found for …` | Zone liegt nicht auf diesem Plesk oder der API-User sieht sie nicht. Die Meldung listet auf, welche Namen probiert wurden und was Plesk dazu gesagt hat. Notfalls die Zone mit `--zone example.com` fest vorgeben | | `DNS propagation timed out` | Plesk ist nicht der autoritative NS, oder die Zone ist deaktiviert. Mit `-v` prüfen, welcher Nameserver antwortet | | `no dns-01 challenge` | Wildcards gehen ausschließlich über DNS-01 – Domain-Validierung per HTTP ist nicht möglich | | `urn:ietf:params:acme:error:rateLimited` | Rate-Limit von Let's Encrypt (5 Zertifikate pro Woche pro Domain). Erst mit `--staging` testen | | PFX lässt sich in Windows nicht importieren | `PFX_LEGACY_COMPAT=true` setzen und neu erzeugen | Bei `-v` werden auch die XML-Requests an Plesk geloggt – hilfreich, wenn ein Record nicht so landet wie erwartet. ## 8. Tests Ohne echten Plesk-Server und ohne Zertifikatsausstellung: ```bash .venv/bin/python tests/test_offline.py # XML-Parsing, DNS-Logik, Dateiausgabe, PFX .venv/bin/python tests/test_cli.py # kompletter Ablauf gegen ein Plesk-Fake .venv/bin/python tests/test_acme_api.py # prueft die Let's-Encrypt-Anbindung (nur Directory-Abruf) ```