IdP-Authn-Plugin fudiscr

Das Plugin kann als weiterer Faktor für die Multi-Faktor-Authentifizierung (MFA) vom Shibboleth Identity Provider (IdP) verwendet werden. Es unterstützt tokenbasierte Authentifizierungen und folgt im Kern dem Challenge-Response-Pattern. Aus einem der vorherigen Faktoren in der MFA Abfolge muss als Basis ein Benutzername ermittelt werden. Ein häufiger Fall ist eine vorangestellte Authentifizierung mit Benutzername und Passwort gegen LDAP mit dem authn/Password-Flow.

Das Plugin besitzt selbst keine Funktionen zur Verwaltung von Token. Es enthält auch selbst keine speziellen Algorithmen zur Validierung von Token. Über ein generisches ChallengeResponseClientInterface kann ein Token-System angebunden werden. Das Plugin enthält bereits eine Implementierung dieses Interfaces für privacyIDEA. Zudem ist eine Mockup-Implementierung für Tests enthalten. Eine externe Implementierung für Fortinet existiert auch bereits.

Die bisherigen Entwicklungsarbeiten wurden von Steffen Hofmann von der Freien Universität Berlin durchgeführt. Er ist Mitarbeiter des Rechenzentrums (ZEDAT – Zentraleinrichtung für Datenverarbeitung) und zuständig für den Bereich Identity Management. Das Identity Management System der Freien Universität Berlin trägt den Namen FUDIS (FU Directory and Identity Service). Aus diesem Grund wird für das Challenge-Response-Plugin in Konfigurationsparametern u.a. das Kürzel fudiscr verwendet. Die zukünftige Pflege des Plugins erfolgt durch die Freien Universität Berlin. Sollte dies durch die Hochschule nicht mehr sichergestellt werden können, so stellen Partnerorganisationen und -unternehmen dies sicher.

Aktuell werden die folgenden Tokenverfahren aus privacyIDEA (Token types in privacyIDEA) unterstützt:

  • Email
  • HOTP Token
  • Indexed Secret Token
  • mOTP Token
  • Paper Token (PPR)
  • Questionnaire Token (Einschränkung: Es darf nur eine Antwort per Richtlinie verlangt werden.)
  • SMS Token
  • TAN Token
  • TOTP
  • WebAuthn (ab Version 1.2.0)

In der Entwicklung befindet sich die Unterstützung von Push Token.

Das Plugin kann ab der Shibboleth IdP Version 4.1.2 eingesetzt werden.

Die Installation sowie Updates erfolgen über den Shibboleth Plugin-Mechanismus (siehe offizielle Dokumentation PluginInstallation)

Achtung: Der Shibboleth Identity Provider führt nach einer Plugin-Installation automatisch einen Neustart durch.

Mit dem folgenden Aufruf wird das Plugin installiert und aktiviert:

%{idp.home}/bin/plugin.sh -i https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/authn/fudiscr/current/fudis-shibboleth-idp-plugin-authn-fudiscr-current.tar.gz

Zukünftige Updates werden automatisch mit dem folgendem Kommando installiert:

%{idp.home}/bin/plugin.sh -u de.zedat.fudis.shibboleth.idp.plugin.authn.fudiscr

Für ein vollautomatische Installation können die PGP-Keys vorab heruntergeladen und bei der Installation mit angeben werden:

wget -O %{idp.home}/PGP_KEYS_FUDIS https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/PGP_KEYS
%{idp.home}/bin/plugin.sh -i https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/authn/fudiscr/current/fudis-shibboleth-idp-plugin-authn-fudiscr-current.tar.gz --truststore %{idp.home}/PGP_KEYS_FUDIS

Einrichtung eines Funktionsnutzers in privacyIDEA

Das Shibboleth-Plugin kommuniziert mit PrivacyIDEA über dessen API. Es werden drei Endpunkte verwendet:

  • /token/ (Abrufen einer Liste von Tokens)
  • /validate/triggerchallenge (Initiieren einer Token-Challenge)
  • /validate/check (Überprüfen eines OTP ggf. in Kombination mit einer PIN)

Ein Nutzer muss also über die Berechtigungen verfügen, die Aktionen, die bei Aufruf dieser Endpunkte ausgelöst werden, ausführen zu dürfen. Am besten sollte dieser Nutzer über keine weiteren Rechte verfügen.

Anlegen eines Admin-Nutzers

Zunächst muss ein Admin-Nutzer angelegt werden. Es empfiehlt sich, hierfür auch einen eigenen (administrativen) Realm anzulegen, um Berechtigungen für unterschiedliche Arten von Admin später besser trennen zu können. Der anzulegende Nutzer kann dann aus einer beliebigen externen Quelle kommen und über einen in PrivacyIDEA definierten Resolver importiert werden.

Das Anlegen von Realms und Resolvern ist nicht Thema dieser Anleitung. Wir legen hier jedoch der Einfachheit und Anschaulichkeit wegen einen PrivacyIDEA-internen IdP-Admin-Nutzer an.

pi-manage admin add idp-admin

Zudem gehen wir davon aus, das der initiale Admin-Supernutzer admin vorhanden ist.

Vergeben von Berechtigungen

Die notwendigen Berechtigungen werden via Policy in PrivacyIDEA eingetragen.

Achtung: Admins in PrivacyIDEA haben zu Beginn Rechte für alle Aktionen. Dies gilt solange, wie keine Admin-Policy definiert ist. Definiert man eine Policy im Scope 'admin', so gelten für alle Admins nur noch die den Policies eingetragenen Rechte. Sollte es noch keine weitere Admin-Policy geben, sollte man daher darauf achten, sich nicht selbst aus dem Web-UI auszusperren. PrivacyIDEA weist daher darauf hin, falls man eine Admin-Policy definiert, jedoch in keiner (dieser oder) anderen Policy Rechte zum Bearbeiten von Policies vergeben wurden.

Zunächst wird mit der Richtlinien-Vorlage superuser die funktionserhaltende Richtlinie für den Admin-Supernutzer erstellt, und der Nutzer admin zugewiesen.

Anschließend wird nun eine neue Policy für den Nutzer idp-admin erstellt.

Hierfür wird eine neue Datei idp-admin-policy erstellt, welche die Parameter der Policy als Python-Dictionary enthält. Der Inhalt sollte so aussehen:

{
  'action': {'tokenlist':True, 'triggerchallenge':True},
  'active': True,
  'adminuser': ['idp-admin'],
  'name': 'idp-admin-policy',
  'scope': 'admin'
}

Die Policy kann unter Nutzung dieser Datei erstellt werden:

pi-manage policy create -f idp-admin-policy name scope action
Erstellen eines API-Token

Um sich an der API erfolgreich zu authentifizieren muss ein Authorization-Token übergeben werden. Dieser Token kann entweder via API-Call auf den auth-Endpoint angefordert werden oder mit Hilfe von pi-manage erstellt werden. Verwendet man das pi-manage-Skript, so kann ein Token mit einer beliebigen Gültigkeitsdauer in Tagen (default 365) erzeugt werden. Über die API angeforderte Token sind in der Regel nur eine Stunde gültig. Wir verwenden daher pi-manage:

pi-manage api createtoken -r admin -u idp-admin

Konfiguration privacyIDEA

Die Anbindung in privacyIDEA erfolgt über die Konfigurationsoptionen in %{idp.home}/conf/authn/fudiscr.properties

./conf/authn/fudiscr.properties
fudiscr.privacyidea.base_uri=<z.B. https://localhost>
fudiscr.privacyidea.authorization_token=<aus verherigem Anleitungsschritt>
 
#Funktionsnutzer aus vorherigem Anleitungsschritt. Kann verwendet werden, um den Authorization Token zu erneuern:
#fudiscr.privacyidea.service_username=
#fudiscr.privacyidea.service_realm=
#fudiscr.privacyidea.service_password=
 
#Realm, der dem Benutzer automatisch zugefügt wird:
#fudiscr.privacyidea.default_realm=
 
#Falls das Serverzertifikat vom API-Endpunkt nicht geprüft werden soll, kann der Wert auf 'false' gesetzt werden.
#fudiscr.privacyidea.check_connection_certificate=true

(Zum REFEDS Multi-Factor Authentication Profile siehe unter REFEDS AuthN Profiles - Hinweise für Identity Provider)


Um das Plugin im Rahmen von MFA nutzen zu können, muss das MFA-Modul selbst im Identity Provider aktiviert sein.

Dies erreicht man mit:

%{idp.home}/bin/module.sh -e idp.authn.MFA

In %{idp.home}/conf/authn/authn.properties sind folgende Einstellungen vorzunehmen:

./conf/authn/authn.properties
idp.authn.flows = MFA
 
idp.authn.fudiscr.supportedPrincipals= \
saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR
 
idp.authn.MFA.supportedPrincipals = \
saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol, \
saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport, \
saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password, \
saml1/urn:oasis:names:tc:SAML:1.0:am:password, \
saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR

idp.authn.MFA.supportedPrincipals ist bereits im Standard aktiviert. Es wird lediglich die AuthenticationContextClass saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR ergänzt. Anstelle von saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR kann auch eine andere AuthenticationContextClass oder zusätzliche unter idp.authn.fudiscr.supportedPrincipals UND idp.authn.MFA.supportedPrincipals eingetragen werden. Es wird empfohlen, mindestens eine zusätzliche Klasse zu definieren, über die explizit die Multi-Faktor-Authentifizierung ausgelöst werden kann. Es besteht aber keine Verpflichtung, da eine Multi-Faktor-Authentifizierung auch über andere Mechanismen ausgelöst werden kann.

Über die Transitionen in %{idp.home}/conf/authn/mfa-authn-config.xml wird die Abfolge der einzelnen Faktoren gesteuert. Hier können verschiedene Mechanismen verwendet werden, um die Bedingungen für die Übergänge in die nächsten Faktoren zu formulieren. Die offizielle Dokumentation dazu finden Sie hier: MultiFactorAuthnConfiguration

Beispiele

Im Folgenden werden drei Beispiele für eine MFA-Konfiguration aufgeführt

Beispiel 1: Nach der Authentifizierung mit Benutzername und Passwort erfolgt immer eine tokenbasierte Authentifizierung mit fudiscr.

./conf/authn/mfa-authn-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
       default-init-method="initialize"
       default-destroy-method="destroy">
 
    <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration -->
 
    <util:map id="shibboleth.authn.MFA.TransitionMap">
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/>
        </entry>
        <entry key="authn/Password">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/fudiscr"/>
        </entry>
    </util:map>
 
</beans>

Beispiel 2: Nach der Authentifizierung mit Benutzername und Passwort erfolgt dann eine tokenbasierte Authentifizierung, wenn die AuthenticationContextClass, die der ServiceProvider benötigt, nicht ausreicht.

./conf/authn/mfa-authn-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
       default-init-method="initialize"
       default-destroy-method="destroy">
 
    <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration -->
 
    <util:map id="shibboleth.authn.MFA.TransitionMap">
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/>
        </entry>
        <entry key="authn/Password">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/>
        </entry>
    </util:map>
 
    <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript">
        <constructor-arg>
            <value>
                <![CDATA[
            nextFlow = "authn/fudiscr";
 
            authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
            mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
            if (mfaCtx.isAcceptable()) {
                nextFlow = null;
            }
 
            nextFlow;
        ]]>
            </value>
        </constructor-arg>
    </bean>
 
</beans>

Beispiel 3: Nach der Authentifizierung mit Benutzername und Passwort erfolgt dann eine tokenbasierte Authentifizierung, wenn die AuthenticationContextClass, die der ServiceProvider benötigt, nicht ausreicht oder wenn der/die Benutzer*in nach eduPersonAffiliation zu der Gruppe der Beschäftigten (employee) gehört.

./conf/authn/mfa-authn-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
       default-init-method="initialize"
       default-destroy-method="destroy">
 
    <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration -->
 
    <util:map id="shibboleth.authn.MFA.TransitionMap">
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/>
        </entry>
        <entry key="authn/Password">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/>
        </entry>
    </util:map>
 
    <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" p:customObject-ref="shibboleth.AttributeResolverService">
        <constructor-arg>
            <value>
                <![CDATA[
            nextFlow = "authn/fudiscr";
 
            authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
            mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
            if (mfaCtx.isAcceptable()) {
                nextFlow = null;
 
                resCtx = input.getSubcontext(
                    "net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext", true);
 
                usernameLookupStrategyClass = Java.type("net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy");
                usernameLookupStrategy = new usernameLookupStrategyClass();
                resCtx.setPrincipal(usernameLookupStrategy.apply(input));
 
 
                resCtx.getRequestedIdPAttributeNames().add("eduPersonAffiliation");
                resCtx.resolveAttributes(custom);
 
                attribute = resCtx.getResolvedIdPAttributes().get("eduPersonAffiliation");
                valueType =  Java.type("net.shibboleth.idp.attribute.StringAttributeValue");
                if (attribute != null && attribute.getValues().contains(new valueType("employee"))) {
                    nextFlow = "authn/fudiscr";
                }
 
                input.removeSubcontext(resCtx);
            }
 
            nextFlow;
        ]]>
            </value>
        </constructor-arg>
    </bean>
 
</beans>

Beispiel 4:

Nach der Authentifizierung mit Benutzername und Passwort erfolgt dann eine tokenbasierte Authentifizierung, wenn die AuthenticationContextClass, die der ServiceProvider benötigt, nicht ausreicht oder wenn der/die Benutzer*in bereits mindestens einen Token besitzt. Bei den Tokens wird der Zustand nicht geprüft. Ein gesperrter Token würde z.B. dafür sorgen, dass das fudiscr.UserHasAnyTokenPredicate den Wert true zurückgibt. Dieses Predicate wurde insbesondere für Rollout-Szenarien eingeführt.

./conf/authn/mfa-authn-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
       default-init-method="initialize"
       default-destroy-method="destroy">
 
    <util:map id="shibboleth.authn.MFA.TransitionMap">
        <entry key="">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/>
        </entry>
        <entry key="authn/Password">
            <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/>
        </entry>
    </util:map>
 
    <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" p:customObject-ref="fudiscr.UserHasAnyTokenPredicate">
        <constructor-arg>
            <value>
                <![CDATA[
            nextFlow = "authn/fudiscr";
 
            authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
            mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
            if (mfaCtx.isAcceptable()) {
                nextFlow = null;
 
                if (custom.test(input)) {
                    nextFlow = "authn/fudiscr";
                }
            }
 
            nextFlow;
        ]]>
            </value>
        </constructor-arg>
    </bean>
 
</beans>

Reuse Condition

Für Authentifizierungsverfahren im Shibboleth Identity Provider kann generell eine Reuse Condition definiert werden. In der Regel ist der Default-Wert shibboleth.Conditions.TRUE. Die Bedingung ist also immer erfüllt. Es können als Reuse Condition auch eigene Funktionen definiert werden.

Für die Multi-Faktor-Authentifizierung gelten besondere Regeln. Eine Beschreibung finden Sie in der offiziellen Dokumentation unter MultiFactorAuthnConfiguration.

Mit folgender Konfiguration in %{idp.home}/conf/authn/authn.properties erreichen Sie, dass innerhalb einer Single-Sign-On-Session nur einmal Benutzername und Passwort verlangt wird, aber pro Service Provider Authentifizierung immer eine tokenbasierte Authentifizierung mit fudiscr.

./conf/authn/authn.properties
idp.authn.MFA.reuseCondition=shibboleth.Conditions.FALSE
idp.authn.Password.reuseCondition=shibboleth.Conditions.TRUE
idp.authn.fudiscr.reuseCondition=shibboleth.Conditions.FALSE

Das Ergebnis der MFA-Authentifizierung darf demnach nicht erneut verwendet werden, aber innerhalb der Multi-Faktor-Authentifizierung darf das existierende erfolgreiche Ergebnis der Benutzername-Passwort-Authentifizierung (Password) verwendet werden und das Ergebnis von fudiscr aber nicht.

Weitere Konfigurationsoptionen finden Sie in %{idp.home}/conf/authn/fudiscr.properties. Als Unterstützung dient das Dokument ChallengeResponseFlow.pdf.

Mit der Version 1.2.0 können WebAuthn-Token verwendet werden.

Allgemein gilt, dass die Domain des Identity Providers entweder identisch mit der rpId aus WebAuthn oder eine Subdomain von der rpId aus WebAuthn sein muss. Es findet keine Vorabfilterung statt, ob die Domain des Identity Providers mit der rpId des WebAuthn-Token verträglich ist.

  • 1.0.0
    • Veröffentlichung des Plugins
  • 1.1.0
    • privacyIDEA Version 3.7 wird unterstützt
    • im Falle einer ungültigen/leeren Eingabe wird die Meldung FudiscrNoResponse in den AuthenticationErrorContext geschrieben
    • im Falle einer fehlerhaften Validierung einer Antwort (z.B. OTP) wird die Meldung FudiscrInvalidResponse in den AuthenticationErrorContext geschrieben
    • login-error.vm wird durch insert-response.vm inkludiert.
    • fudiscr.UserHasAnyTokenPredicate: Prädikat testet, ob ein Benutzer ein Token besitzt, egal in welchem Zustand.
  • 1.2.0
    • WebAuthn wird unterstützt
    • main.vm und insert-response.vm wurden geändert
    • Wichtiger Bugfix: Aufgrund einer fehlenden @NameParameter Annotation in ChallengeResponseTokenIdPrincipal konnte ein Serialisierer nicht zugewiesen werden. Dies hatte Auswirkungen auf alle Principal-Serialisierer. Es wurden willkürlich unterschiedliche Serialisierer verwendet.

Fehler, Fragen, Anregungen etc. bitte per E-Mail an fudis@zedat.fu-berlin.de senden.

  • Zuletzt geändert: vor 5 Monaten