Status: verifiziert. Vollständige Kette real getestet: unauthentifiziert → 401, korrekter Nutzer über LDAPS → 200 mit korrektem Rollen-Ergebnis, falsches Passwort → 401 — alles über eine echte TLS-verschlüsselte Verbindung zum LDAP-Container, nicht mehr über Klartext-LDAP wie in den früheren Ausbaustufen.
02-ldap-registry.md beschreibt die ursprüngliche Konfiguration gegen
Klartext-LDAP (Port 1389) — funktional korrekt, aber eine echte Lücke beim Anzeigen-Punkt
"Security Lifecycle: ... Zertifikate, SSL": Zugangsdaten (Bind-Passwort, später auch die
Nutzer-Credentials beim Login) liefen unverschlüsselt über die Leitung. Dieses Dokument schließt
die Lücke — und zeigt dabei zwei reale, in dieser Session tatsächlich aufgetretene Stolpersteine,
die für die Anzeigen-Punkte "Troubleshooting" und "Zertifikate" beide lehrreich sind.
infra/docker-compose.yml setzt LDAP_TLS=true — das osixia/openldap-Image erzeugt daraufhin
beim Start automatisch ein selbstsigniertes Zertifikat. Realer Befund: die vom Image
mitgelieferte Standard-CA (docker-light-baseimage) war zum Zeitpunkt dieser Session bereits
abgelaufen (notAfter=Jan 15 2026, geprüft mit openssl s_client -connect localhost:1636).
Ein Zertifikat, dessen Aussteller-Kette ein abgelaufenes Glied enthält, ist kein verlässlicher
Vertrauensanker — Java/Liberty hätte das bei strikter Kettenprüfung zu Recht abgelehnt.
Konsequenz für dieses Projekt: statt der Image-Standard-CA zu vertrauen, erzeugt
scripts/ldap-tls-cert-generate.sh ein eigenes
selbstsigniertes Zertifikat (openssl, 365 Tage gültig), das über LDAP_TLS_CRT_FILENAME/
LDAP_TLS_KEY_FILENAME/LDAP_TLS_CA_CRT_FILENAME in den Container eingespielt wird — volle
Kontrolle über Gültigkeit und Inhalt, statt einer stillen Abhängigkeit von einem
Drittanbieter-Image-Default.
Erster Versuch: ldapRegistry auf Port 1636 mit sslEnabled="true" umgestellt, Zertifikat des
Containers reingezogen und in einen Liberty-Truststore importiert. Ergebnis: 401 trotz
korrekter Credentials. Log-Auszug (echt, aus messages.log):
CWIML4520E: ... javax.net.ssl.SSLHandshakeException: (certificate_unknown)
No subject alternative DNS name matching localhost found.
Diagnose: das vom Image automatisch erzeugte Zertifikat trug als Subject Alternative Name
nur den zufälligen Container-Hostnamen (z. B. 553f56c5256c) — nicht localhost, worüber
Liberty sich verbindet. Das Zertifikat war korrekt vertrauenswürdig (im Truststore), aber die
Hostname-Prüfung (SAN muss zum verwendeten Hostnamen passen) schlug fehl — ein Zertifikat
kann vertrauenswürdig UND trotzdem für die falsche Adresse ausgestellt sein, zwei unabhängige
Prüfungen.
Die falsche Antwort wäre gewesen, die Hostname-Verifikation abzuschalten. Die richtige
Antwort: ein Zertifikat erzeugen, dessen SAN tatsächlich zur verwendeten Adresse passt — siehe
scripts/ldap-tls-cert-generate.sh
(subjectAltName=DNS:localhost,DNS:concordia-openldap).
keyStoreRef ist Pflicht)Ein dritter, kleinerer realer Fund beim ersten Anlauf:
CWWKG0058E: Im Element ssl mit der eindeutigen ID ldapSSLConfig fehlt das
erforderliche Attribut keyStoreRef.
Liberty verlangt bei <ssl> immer ein keyStoreRef, auch für eine reine
Outbound-Verbindung ohne eigenes Client-Zertifikat. Lösung: den bereits vorhandenen,
automatisch erzeugten HTTPS-Default-Keystore (defaultKeyStore, siehe
03-zertifikate-ssl.md) mitreferenzieren, statt einen weiteren eigens
dafür anzulegen — er wird für diese Verbindung inhaltlich nicht gebraucht, das Attribut ist
trotzdem Pflicht.
<ldapRegistry id="concordiaLdap"
realm="ConcordiaBankLDAP"
host="localhost"
port="1636"
sslEnabled="true"
sslRef="ldapSSLConfig"
...>
...
</ldapRegistry>
<ssl id="ldapSSLConfig" keyStoreRef="defaultKeyStore" trustStoreRef="ldapTrustStore"/>
<keyStore id="ldapTrustStore"
location="resources/security/ldap-truststore.p12"
type="PKCS12"
password="ConcordiaTrust123!"/>
bash scripts/ldap-tls-cert-generate.sh # 1) eigenes Zertifikat mit korrektem SAN erzeugen
cd infra && python -m podman_compose down -v && python -m podman_compose up -d # 2) Container damit neu starten
cd .. && bash scripts/ldap-tls-trust-setup.sh # 3) Liberty-Truststore daraus bauen
cd concordia-portal && CONCORDIA_LDAP_BIND_PASSWORD="..." mvn liberty:run
Warum drei separate Skripte statt eines eingecheckten Zertifikats: das Zertifikat enthält
einen privaten Schlüssel (gehört nicht ins Repo) und müsste bei jedem podman-compose down -v
ohnehin neu zum Container passen — Vertrauens-Bootstrapping ist hier bewusst als wiederholbarer,
lokaler Ablauf modelliert, nicht als statische Datei (siehe Kommentar in
ldap-tls-trust-setup.sh).
curl-Ergebnisse)$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:19080/concordia-portal/secure/whoami
401
$ curl -s -u "m.bauer:AdminDemo123!" http://localhost:19080/concordia-portal/secure/whoami
{"principal":"m.bauer","isPortalAdmin":true,"isPortalUser":false}
$ curl -s -o /dev/null -w "%{http_code}\n" -u "m.bauer:falsch123" http://localhost:19080/concordia-portal/secure/whoami
401
Alle drei Fälle liefen jetzt über den durch LDAPS verschlüsselten Kanal — dieselben Testfälle wie
in 01-rollen-und-rechte.md, nur mit verschlüsselter statt
unverschlüsselter LDAP-Verbindung.