Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 12 additions & 48 deletions de/15.8/config/sso-saml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,7 @@ IdPs mit wiederholten Attributnamen
-----------------------------------

Wenn der IdP denselben Attributnamen auf mehrere ``<Attribute>``-Elemente verteilt, weist |Fess|
die Assertion zurück und die Anmeldung selbst schlägt fehl. Die Prüfung der Assertion -- Signatur,
InResponseTo und Replay -- war zu diesem Zeitpunkt bereits erfolgreich; die Zurückweisung erfolgt
erst beim Lesen der Attribute. Deshalb schlägt auch eine Konfiguration fehl, die
``saml.attribute.role.name`` gar nicht setzt.
die Assertion zurück und die Anmeldung selbst schlägt fehl.

Keycloak sendet standardmäßig Assertions dieser Form: Seine Rollen- und Gruppen-Mapper geben pro
Wert ein eigenes ``<Attribute>``-Element aus, solange deren Option ``single`` nicht aktiviert ist,
Expand Down Expand Up @@ -368,14 +365,8 @@ Signatureinstellungen
.. warning::
Wenn Single Logout konfiguriert ist (``saml.idp.single_logout_service.url``), setzen Sie unbedingt
auch ``saml.security.want_messages_signed=true``.
Solange die Option ``false`` ist, wird für eine an ``/sso/logout`` eingehende LogoutRequest keine
Signatur verlangt. Geprüft werden lediglich das XML-Schema, ``NotOnOrAfter`` (falls vorhanden),
``Destination`` (falls vorhanden) und die Übereinstimmung des Issuers mit ``saml.idp.entityid``
(falls vorhanden); die NameID in der LogoutRequest wird nie mit dem angemeldeten Benutzer verglichen.
Das Issuer-Element ist im SAML-Schema optional, sodass eine LogoutRequest, die es weglässt, nie mit
der Entity-ID des IdP verglichen wird. Ein Angreifer kann daher, ohne die Entity-ID des IdP zu
kennen, eine unsignierte LogoutRequest erzeugen und die Sitzung eines authentifizierten Benutzers
beenden, indem er diesen auf die entsprechende URL lockt.
Solange die Option ``false`` ist, wird eine LogoutRequest ohne Signatur akzeptiert, sodass eine
präparierte URL die Sitzung eines authentifizierten Benutzers beenden kann.
Die Auswirkung ist eine erzwungene Abmeldung (Denial of Service), keine Kontoübernahme.

Verschlüsselungseinstellungen
Expand Down Expand Up @@ -467,14 +458,6 @@ Die vom IdP zurückgegebene SAML-Antwort wird anhand der gespeicherten ID validi

Eine gespeicherte ID wird verworfen, sobald dieser Zeitraum verstrichen ist.
Ist die Gültigkeit abgelaufen (zum Beispiel weil die Anmeldeseite des IdP offen gelassen wurde), kann die zurückgegebene Assertion nicht zugeordnet werden, und die Anmeldung schlägt einmalig fehl.
Wird kein Wert gesetzt, werden 3600 Sekunden verwendet.
Wird ein Wert gesetzt, der sich nicht als Zahl interpretieren lässt, werden ebenfalls 3600 Sekunden verwendet, und es wird eine mit ``Invalid saml.request.id.ttl`` beginnende Warnung protokolliert.
Ein Wert von null oder kleiner würde die AuthnRequest-ID verwerfen, bevor eine Anmeldung vom IdP zurückkehren könnte; daher werden auch in diesem Fall 3600 Sekunden verwendet und eine Warnung protokolliert.

.. note::
Pro Sitzung werden höchstens 10 unbeantwortete AuthnRequests aufbewahrt; wird dieses Limit überschritten, werden die ältesten verworfen.
Dies ermöglicht es, Anmeldungen gleichzeitig aus mehreren Tabs zu starten, und lässt sich nicht über eine ``saml.``-Einstellung ändern.
Wird das Limit mit einem Wert von null oder weniger überschrieben, werden stattdessen 10 verwendet und eine Warnung protokolliert.

Konfigurationsbeispiele
=======================
Expand Down Expand Up @@ -544,45 +527,26 @@ Kann nach der Authentifizierung nicht zu Fess zurückkehren
- Stellen Sie sicher, dass der Wert von ``saml.sp.base.url`` mit der IdP-Konfiguration übereinstimmt
- Die SAML-Assertion trifft als seitenübergreifender POST vom IdP ein. Wenn
``tomcat.sameSiteCookies`` in ``tomcat_config.properties`` auf ``lax`` (Standard) steht, sendet der
Browser das Sitzungs-Cookie nicht mit. |Fess| findet dann keine passende AuthnRequest-ID und lässt
die Anmeldung an dieser Stelle einmalig fehlschlagen. Der Browser kehrt zur Anmeldeseite mit der
Meldung „SSO-Anmeldevorgang fehlgeschlagen." zurück, und im Protokoll erscheint eine Warnung, die
mit ``Received a SAML response with no matching AuthnRequest ID in the session`` beginnt.
Browser das Sitzungs-Cookie nicht mit, und die Anmeldung schlägt einmalig fehl.
Setzen Sie in diesem Fall ``tomcat.sameSiteCookies = none`` (``SameSite=None`` erfordert HTTPS)
- Wenn die Anmeldung am IdP zu lange gedauert hat, ist die AuthnRequest-ID nicht mehr vorhanden,
sobald die Assertion zurückkommt; die Anmeldung schlägt einmalig fehl und muss neu gestartet
werden. Welche Warnung erscheint, zeigt, was abgelaufen ist: Eine Warnung, die mit
``Received a SAML response after the session it belongs to had expired`` beginnt, bedeutet, dass
der Servlet-Container die gesamte Sitzung verworfen hat; eine Warnung, die
``pending AuthnRequest ID(s) of the session had expired`` enthält, bedeutet, dass die Sitzung noch
besteht und lediglich ``saml.request.id.ttl`` abgelaufen ist. Beide werden nur protokolliert, wenn
der Browser sein Sitzungs-Cookie mitgesendet hat, und genau das unterscheidet sie vom
SameSite-Fall oben
sobald die Assertion zurückkommt; die Anmeldung schlägt einmalig fehl und muss neu gestartet werden
- |Fess| setzt in ``app/WEB-INF/web.xml`` kein ``session-timeout``, sodass der Standardwert des
Servlet-Containers von 30 Minuten gilt; er ist kürzer als die 3600 Sekunden von
``saml.request.id.ttl``. Die Sitzung wird also samt der darin gehaltenen AuthnRequest-ID zuerst
verworfen: Ein höherer Wert für ``saml.request.id.ttl`` allein verlängert nicht die Zeit, die
Benutzer für die Anmeldung am IdP haben. Erhöhen Sie dafür auch den Sitzungs-Timeout. Die Warnung
zu ``saml.request.id.ttl`` erscheint daher nur dort, wo die TTL kürzer als der Sitzungs-Timeout
eingestellt ist

.. note::
In 15.7 leitete |Fess| in derselben Situation immer wieder zum IdP weiter, sodass die Anmeldung in
einer Schleife lief. In 15.8 schlägt sie stattdessen einmalig fehl. An der Konfigurationslösung
ändert sich nichts.
``saml.request.id.ttl``, sodass die Sitzung zuerst verworfen wird. Ein höherer Wert für
``saml.request.id.ttl`` allein verlängert daher nicht die Zeit, die Benutzer für die Anmeldung am
IdP haben. Erhöhen Sie dafür auch den Sitzungs-Timeout

Destination-Validierung schlägt hinter einem Reverse Proxy fehl
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wenn |Fess| hinter einem TLS-terminierenden Reverse Proxy oder Load Balancer betrieben wird, kann die
Assertion-Validierung fehlschlagen, obwohl ``saml.sp.base.url`` korrekt gesetzt ist.

Die Ursache: Die SAML-Bibliothek vergleicht das Attribut ``Destination`` der Assertion mit der vom
Servlet-Container rekonstruierten Anfrage-URL und nicht mit der konfigurierten ACS-URL. Wenn der Proxy
HTTPS terminiert, sieht |Fess| eine interne Anfrage-URL wie
``http://<interner-host>:<interner-port>/sso/``, die nicht mit der vom IdP gesendeten URL
``https://fess.example.com/sso/`` übereinstimmt. ``saml.sp.base.url`` wird für diesen Vergleich nicht
herangezogen; die Einstellung allein behebt das Problem daher nicht.
Das Attribut ``Destination`` der Assertion wird mit der URL der Anfrage verglichen, so wie sie bei
|Fess| ankommt -- hinter einem TLS-terminierenden Proxy also eine interne ``http://``-URL und nicht
die externe, an die der IdP die Assertion gesendet hat. ``saml.sp.base.url`` wird für diesen
Vergleich nicht herangezogen; die Einstellung allein behebt das Problem daher nicht.

Setzen Sie ``saml.debug=true``, damit der Grund ins Protokoll geschrieben wird:

Expand Down
57 changes: 13 additions & 44 deletions en/15.8/config/sso-saml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,7 @@ IdPs that repeat an attribute name
----------------------------------

If the IdP splits the same attribute name across several ``<Attribute>`` elements, |Fess| refuses
the assertion and the login itself fails. Validation of the assertion -- signature, InResponseTo and
replay -- has already succeeded at that point; the refusal happens while the attributes are being
read, so a configuration that does not set ``saml.attribute.role.name`` at all fails in exactly the
same way.
the assertion and the login itself fails.

Keycloak sends assertions of this shape by default: its role and group mappers emit one
``<Attribute>`` element per value unless their ``single`` option is enabled, and every Keycloak
Expand Down Expand Up @@ -364,13 +361,8 @@ Signature Settings
.. warning::
When Single Logout is configured (``saml.idp.single_logout_service.url``), always set
``saml.security.want_messages_signed=true`` as well.
While it is ``false``, no signature is required on a LogoutRequest received at ``/sso/logout``.
The only checks performed are the XML schema, ``NotOnOrAfter`` (if present), ``Destination``
(if present), and that the Issuer matches ``saml.idp.entityid`` (if present); the NameID in the
LogoutRequest is never compared against the logged-in user. The Issuer element is optional in the
SAML schema, so a LogoutRequest that omits it is never compared against the IdP entity ID. An
attacker, without needing to know the IdP entity ID, can therefore craft an unsigned LogoutRequest
and terminate an authenticated user's session by luring that user to the URL.
While it is ``false``, a LogoutRequest that carries no signature is accepted, so a crafted URL
can end an authenticated user's session.
The impact is a forced logout (denial of service), not account takeover.

Encryption Settings
Expand Down Expand Up @@ -462,14 +454,6 @@ The SAML response returned by the IdP is validated against the recorded ID.

A recorded ID is discarded once this period passes.
If it expires (for example the IdP login page was left open), the returned assertion cannot be matched and the login fails once.
If no value is set, 3600 seconds is used.
If a value that cannot be parsed as a number is set, 3600 seconds is also used and a warning beginning with ``Invalid saml.request.id.ttl`` is logged.
A value of zero or less would discard the AuthnRequest ID before a login could return from the IdP, so it is likewise replaced with 3600 seconds and a warning is logged.

.. note::
At most 10 unanswered AuthnRequests are kept per session; when the cap is exceeded the oldest are discarded.
This exists so that logins can be started from several tabs at once, and it cannot be changed with a ``saml.`` setting.
If it is overridden with a value of zero or less, 10 is used instead and a warning is logged.

Configuration Examples
======================
Expand Down Expand Up @@ -539,40 +523,25 @@ Cannot return to Fess after authentication
- Ensure the ``saml.sp.base.url`` value matches the IdP configuration
- The SAML assertion arrives as a cross-site POST from the IdP. When ``tomcat.sameSiteCookies`` in
``tomcat_config.properties`` is ``lax`` (the default), the browser does not send the session cookie
with it, so |Fess| finds no matching AuthnRequest ID and fails the login once, on the spot. The
browser returns to the login page showing "SSO login process failed.", and a warning beginning with
``Received a SAML response with no matching AuthnRequest ID in the session`` is written to the log.
Set ``tomcat.sameSiteCookies = none`` in that case (``SameSite=None`` requires HTTPS)
with it and the login fails once. Set ``tomcat.sameSiteCookies = none`` in that case
(``SameSite=None`` requires HTTPS)
- If the login took too long at the IdP, the AuthnRequest ID is no longer there when the assertion
comes back, so the login fails once and has to be started again. Which warning appears says what
ran out: one beginning with ``Received a SAML response after the session it belongs to had
expired`` means the servlet container discarded the whole session, while one containing
``pending AuthnRequest ID(s) of the session had expired`` means the session is still alive and
only ``saml.request.id.ttl`` elapsed. Both are written only when the browser did send its session
cookie, which is what separates them from the SameSite case above
comes back, so the login fails once and has to be started again
- |Fess| leaves ``session-timeout`` unset in ``app/WEB-INF/web.xml``, so the servlet container's
default of 30 minutes applies and is shorter than the 3600 seconds of ``saml.request.id.ttl``. The
session, and with it the AuthnRequest ID it holds, is discarded first, so raising
``saml.request.id.ttl`` on its own does not give users longer to finish logging in at the IdP:
raise the session timeout as well. The ``saml.request.id.ttl`` warning is therefore only seen
where the TTL is set shorter than the session timeout

.. note::
In 15.7 the same situation caused |Fess| to redirect to the IdP again and again, looping the login.
15.8 fails once instead of looping. The configuration remedy is unchanged.
default of 30 minutes applies and is shorter than the 3600 seconds of ``saml.request.id.ttl``.
Raising ``saml.request.id.ttl`` on its own therefore does not give users longer to finish logging
in at the IdP: raise the session timeout as well

Destination validation fails behind a reverse proxy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When |Fess| runs behind a TLS-terminating reverse proxy or load balancer, assertion validation can
fail even though ``saml.sp.base.url`` is set correctly.

The cause is that the SAML library compares the ``Destination`` attribute of the assertion against
the request URL reconstructed by the servlet container, not against the configured ACS URL. When the
proxy terminates HTTPS, the request URL |Fess| sees is an internal one such as
``http://<internal-host>:<internal-port>/sso/``, which does not match the
``https://fess.example.com/sso/`` sent by the IdP. ``saml.sp.base.url`` is not used for this
comparison, so setting it alone does not fix the problem.
The ``Destination`` attribute of the assertion is compared against the URL of the request as it
reaches |Fess|, which behind a TLS-terminating proxy is an internal ``http://`` URL rather than the
external one the IdP sent the assertion to. ``saml.sp.base.url`` is not used for this comparison,
so setting it alone does not fix the problem.

Set ``saml.debug=true`` to have the reason written to the log:

Expand Down
Loading
Loading