Legt den A/AAAA-Record fuer einen Namen in Plesk an bzw. aktualisiert ihn und holt anschliessend per DNS-01-Challenge ein Wildcard-Zertifikat von Let's Encrypt. Alle Bestandteile werden einzeln abgelegt, zusaetzlich als kombinierte bundle.pem und als passwortgeschuetzte cert.pfx. - Plesk-Anbindung ueber die XML-API (Zone finden, Records lesen/anlegen/loeschen) - ACME-Order mit dns-01, Account-Key wird wiederverwendet - Propagations-Check gegen die autoritativen Nameserver der Zone, bricht vor der Validierung ab statt einen Fehlversuch bei Let's Encrypt zu verbrennen - Renewal-Check: laeuft idempotent, taugt so direkt fuer den Cron - Docker-Container, Konfiguration ueber .env, Ausgabe im Projektverzeichnis - Tests ohne echten Plesk-Server bzw. ohne Zertifikatsausstellung Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
"""Check the acme-library API usage against the real Let's Encrypt staging directory.
|
|
|
|
Fetches the directory and builds the client; account registration is stubbed out so
|
|
nothing is created on the CA side.
|
|
"""
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
import tempfile
|
|
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
|
|
os.environ.update(
|
|
PLESK_HOST="p", PLESK_API_KEY="k", ACME_EMAIL="a@b.de", CERT_PASSWORD="x",
|
|
ACME_ACCOUNT_DIR=tempfile.mkdtemp(), ACME_STAGING="true",
|
|
)
|
|
|
|
import acme, josepy
|
|
from app.config import load_config
|
|
from app import acme_client
|
|
from acme import challenges, messages
|
|
|
|
print("acme:", getattr(acme, "__version__", "?"), "josepy:", getattr(josepy, "__version__", "?"))
|
|
|
|
registered = []
|
|
acme_client.AcmeManager._register = lambda self, c: registered.append(c)
|
|
|
|
cfg = load_config(env_file="/dev/null")
|
|
m = acme_client.AcmeManager(cfg)
|
|
print("directory newOrder:", m.client.directory["newOrder"])
|
|
assert registered, "register hook not called"
|
|
assert m.account_key_file.is_file() and oct(m.account_key_file.stat().st_mode)[-3:] == "600"
|
|
|
|
# reload must reuse the very same account key
|
|
m2 = acme_client.AcmeManager(cfg)
|
|
assert m2.account_key.thumbprint() == m.account_key.thumbprint()
|
|
print("account key reused ok")
|
|
|
|
# dns-01 challenge helpers used in obtain_certificate()
|
|
chall = challenges.DNS01(token=b"0123456789abcdef0123456789abcdef")
|
|
validation = chall.validation(m.account_key)
|
|
name = chall.validation_domain_name("example.com")
|
|
assert name == "_acme-challenge.example.com", name
|
|
assert isinstance(validation, str) and len(validation) == 43, validation
|
|
resp = chall.response(m.account_key)
|
|
assert isinstance(resp, challenges.DNS01Response)
|
|
print("dns-01 helpers ok:", name, validation)
|
|
|
|
# the API surface obtain_certificate() relies on
|
|
for attr in ("new_order", "answer_challenge", "poll_and_finalize"):
|
|
assert hasattr(m.client, attr), attr
|
|
assert hasattr(messages, "STATUS_VALID")
|
|
assert hasattr(messages.NewRegistration, "from_data")
|
|
print("client API surface ok")
|
|
print("\nACME API CHECK PASSED")
|