Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: added a step to ensure that principle switching is set to true to avoid MFA bypass during forceAuth when principal is switched

...

  1. Download and install/configure the Unicon Duo Security login flow from GitHub.
  2. Create a new webflow /opt/shibboleth-idp/flows/authn/Duo/duo-authn-flow.xml.  The contents are below.  The changes we made ensure that on a forceAuthn request, the user is prompted to also enter their username-password again (so long as it's not their first pass through the IdP – determined by a 2-minute timer from when the IdPSession is created).

    Code Block
    languagexml
    <flow xmlns="http://www.springframework.org/schema/webflow"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
          parent="authn.abstract, authn/conditions">
        <var name="duoCredential" class="net.unicon.iam.shibboleth.idp.authn.duo.authn.DuoCredential"/>
        <var name="currentTime" class="java.util.Date" />
        <!-- if the IdP session is < 2 minutes then don't check for forceAuthn.  If it's more than that
        then check for forceAuthn and if forceAuthn is in play make the user do a 2nd Password login.
        This eliminates the problem we had with a user hitting a forceAuthn site as their first SP & then
        having to double-enter their username/password.
        -->
        <decision-state id="checkFirstLogin">
    	<if test="(currentTime.getTime() - opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.session.context.SessionContext)).getIdPSession().getCreationInstant()) > 120000"
                then="checkForceAuth"
                else="duo" />	
        </decision-state>
        <decision-state id="checkForceAuth">
            <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).isForceAuthn()"
                then="CallPasswordFlow"
                else="duo" />
        </decision-state>
        <subflow-state id="CallPasswordFlow" subflow="authn/Password">
            <input name="calledAsSubflow" value="true"/>
            <transition on="proceed" to="duo"/>
        </subflow-state>
        <view-state id="duo" view="duo" model="duoCredential">
            <binder>
                <binding property="signedDuoResponse"/>
            </binder>
            <on-render>
                <evaluate expression="environment" result="viewScope.environment"/>
                <evaluate
                        expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))"
                        result="viewScope.authenticationContext"/>
                <set name="viewScope.request" value="flowRequestContext.getExternalContext().getNativeRequest()"/>
                <set name="viewScope.sigRequest"
                     value="duoAuthenticationService.generateSignedRequestToken(authenticationContext)"/>
                <set name="viewScope.apiHost" value="duoAuthenticationService.getApiHost()"/>
            </on-render>
            <transition on="proceed" bind="true" validate="false" to="validateDuo"/>
        </view-state>
    
        <action-state id="validateDuo">
            <evaluate expression="ValidateDuoResponse"/>
            <evaluate expression="'proceed'"/>
            <transition on="proceed" to="proceed"/>
        </action-state>
    </flow>
  3. Setup global.xml to use our local Duo AuthnContextClass instead of the default from the plugin

    Code Block
    languagexml
    titleglobal.xml config
        <bean id="authn/Duo" parent="shibboleth.AuthenticationFlow"
              p:nonBrowserSupported="false" p:forcedAuthenticationSupported="%{duo.forcedAuthenticationSupported:true}">
            <property name="supportedPrincipals">
                <util:list>
                    <bean parent="shibboleth.SAML2AuthnContextClassRef"
                          c:classRef="http://uchicago.edu/duo" />
                                    <bean parent="shibboleth.SAML2AuthnContextClassRef"
                          c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" />
                    <bean parent="shibboleth.SAML1AuthenticationMethod"
                          c:method="http://uchicago.edu/duo" />
                </util:list>
            </property>
        </bean>
  4. Download and install the University of Chicago AuthnClassPredicate IdPv3 extension from GitHub.
  5. Setup the attribute-resolver to configure an attribute to release what AuthnClassContexts a user is allowed to use.  We used eduPersonAssurance.  In our example, we push Grouper groups to our LDAP server & then use a MappedAttribute to translate the Grouper values to AuthnContextClass values

    Code Block
    languagexml
    titleExample eduPersonAssurance attribute
     <resolver:AttributeDefinition xsi:type="Mapped" xmlns="urn:mace:shibboleth:2.0:resolver:ad"
                                      id="eduPersonAssurance" sourceAttributeID="ucisMemberOf">
            <resolver:Dependency ref="directory"/>
            <resolver:AttributeEncoder xsi:type="SAML2String" xmlns="urn:mace:shibboleth:2.0:attribute:encoder"
                                       name="urn:oid:1.3.6.1.4.1.1466.115.121.1.15" friendlyName="eduPersonAssurance"/>
            <ValueMap>
                <ReturnValue>http://id.incommon.org/assurance/silver</ReturnValue>
                <SourceValue>uc:reference:account:assurance:silver:authorized</SourceValue>
            </ValueMap>
            <ValueMap>
                <ReturnValue>http://uchicago.edu/duo</ReturnValue>
                <SourceValue>uc:applications:shibboleth:mcb:duo-eligible</SourceValue>
            </ValueMap>
            <ValueMap>
                <ReturnValue>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</ReturnValue>
                <SourceValue>uc:applications:shibboleth:mcb:password</SourceValue>
            </ValueMap>
        </resolver:AttributeDefinition>
  6. Configure in idp.properties the following for AuthN flows:

    Code Block
    languagejava
    # Regular expression matching login flows to enable, e.g. IPAddress|Password
    idp.authn.flows=Password|Duo
    # Regular expression of forced "initial" methods when no session exists,
    # usually in conjunction with the idp.authn.resolveAttribute property below.
    idp.authn.flows.initial = Password
    # Set to an attribute ID to resolve prior to selecting authentication flows;
    # its values are used to filter the flows to allow.
    idp.authn.resolveAttribute = eduPersonAssurance
  7. In order to prevent bypassing of MFA during forceAuthn caused by a principal switch (another user logs in during the force prompt who has MFA where the first user didn't have MFA), you need to further adjust idp.properties and set idp.authn.identitySwitchIsError to true.

  8. Setup your authn-comparison.xml to replicate the MCB functionality of defining for the IdP which contexts will satisfy which other contexts.  In this example Duo and Silver are ok for Password and only duo is equivalent for PasswordProtectedTransport:

    Code Block
    languagexml
     <bean id="shibboleth.BetterClassRefMatchFactory" parent="shibboleth.InexactMatchFactory">
            <property name="matchingRules">
                <map>
                    <entry key="urn:oasis:names:tc:SAML:2.0:ac:classes:Password">
                        <list>
                            <value>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</value>
                            <value>http://uchicago.edu/duo</value>
    			<value>http://id.incommon.org/assurance/silver</value> 
                        </list>
                    </entry>
    		<entry key="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport">
    			<list>
    				<value>http://uchicago.edu/duo</value>
    				<!-- remove silver from PPT equivalence as it causes silvered users who have opted
    				into 2FA forcing to not have 2FA because silver is just as good as Duo for PPT -->
    				<!-- <value>http://id.incommon.org/assurance/silver</value> -->
    			</list>
    		</entry>	
                </map>
            </property>
        </bean>
    
    
  9. In conf/intercept/context-check-intercept-config.xml define further the relationship between PasswordProtectedTransport & Duo/Silver (which are both initially handled by Password flow).  What you're doing here is causing the IdP to ensure that if the SP requests the Silver context that the user was able to meet the context via the eduPersonAssurance lookup.  If that condition cannot be met, then fail.

    Code Block
    languagexml
    <?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">
        <!--
                 Condition to evaluate to interrupt SSO flows to check the state of the transaction before allowing.
        
        Typically the flow itself will be activated based on configuration in relying-party.xml, and this controls
        whether to proceed if the flow is activated. The most common use for this flow is to check the set of
        resolved/filtered attributes and values to see if the user is authorized or provisioned into a service.
        -->
        <bean id="shibboleth.context-check.Condition" parent="shibboleth.Conditions.AND">
            <constructor-arg>
                <list>
                    <bean class="net.shibboleth.idp.profile.logic.AuthnClassPredicate"
                        c:authnClassesToMatch-ref="authnClassesToMatch"
    		    c:authnClassesToForgive-ref="authnClassesToForgive" 	
                        c:predicateToDelegate-ref="attributePredicate" />
                </list>
            </constructor-arg>
        </bean>
        <util:set id="authnClassesToMatch">
          	<value>http://id.incommon.org/assurance/silver</value>
    	<value>http://uchicago.edu/duo</value>
        </util:set>
        <util:set id="authnClassesToForgive">
            <value>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</value>
        </util:set>
    
        <bean id="attributePredicate" class="net.shibboleth.idp.profile.logic.SimpleAttributePredicate">
            <property name="attributeValueMap">
                <map>
                    <entry key="eduPersonAssurance">
                        <list>
                            <value>http://id.incommon.org/assurance/silver</value>
    						<value>http://uchicago.edu/duo</value>
                        </list>
                    </entry>
                </map>
            </property>
        </bean>
    </beans>
    
    


    You should now have an IdP that acts like it did under the v2 MCB assuming you used Password initial authN and user-opt-in to Duo.  

    Warning

    One note about this config.  If an SP explicitly requests PasswordProtectedTransport, and the user is trying to force all authN to require Duo (by not allowing PPT in the eduPersonAssurance attribute) the user will not be prompted for Duo.  The Duo forcing only happens if the AuthnContextClass was either explicitly unspecified or not sent in the AuthnRequest. 

...