Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Note

This IFRAME-based integration with Duo Security is now deprecated at the request of the company, which is requiring that customers begin to migrate to their new integration strategy based on OpenID Connect and a full-frame redirect (effectively a proxied authentication via OIDC to Duo Security's servers).

The replacement feature is available as a plugin to V4.1+ of the IdP software and will not be shipped with it.

This original integration will be removed at a future time not yet determined, as this will depend on Duo Security's timeline for retiring their support. We have no plans to remove the feature prior to Duo's retirement of the option. (The warning in the log does say "next major version" but that's an artifact of the standard method we invoke to log deprecations.)is scheduled be removed in V5 in light of Duo Security’s statement that the old method will be unavailable as of March 2024. The timing of V5, combined with the expected support for V4 lasting through the end of 2024, makes the inclusion of this feature in V5 unnecessary.

The authn/Duo login flow is a native implementation of the Duo Security product's DuoWeb authentication interface that leverages an embedded IFRAME. Duo is a popular commercial solution for adding additional authentication factors to existing credentials. It is designed to be used in conjunction with an existing factor, usually a password. It is therefore implemented in a manner that assumes an earlier authentication step has already been completed, and is designed to be used in conjunction with the MFA login flow as part of a combined workflow.

...

Expand
Code Block
languagexml
<bean id="DefaultDuo" classparent="net.shibboleth.idp.authn.duoDuo.BasicDuoIntegrationDuoIntegration"
    p:APIHost="%{idp.duo.apiHost:none}"
    p:applicationKey="%{idp.duo.applicationKey:none}"
    p:integrationKey="%{idp.duo.integrationKey:none}"
    p:secretKey="%{idp.duo.secretKey:none}" />

<bean id="SpecialDuo" classparent="net.shibboleth.idp.authn.duoDuo.BasicDuoIntegrationDuoIntegration"
    p:APIHost="%{idp.specialduo.apiHost:none}"
    p:applicationKey="%{idp.specialduo.applicationKey:none}"
    p:integrationKey="%{idp.specialduo.integrationKey:none}"
    p:secretKey="%{idp.specialduo.secretKey:none}" />

<util:map id="DuoIntegrationMap">
	<entry key="default" value-ref="DefaultDuo" />
	<entry key="https://special1.example.org/shibboleth" value-ref="SpecialDuo" />
	<entry key="https://special2.example.org/shibboleth" value-ref="SpecialDuo" />
</util:map>

<bean id="shibboleth.authn.Duo.DuoIntegrationStrategy" parent="shibboleth.ContextFunctions.Scripted"
		factory-method="inlineScript"
        p:customObject-ref="DuoIntegrationMap">
	<constructor-arg>
		<value>
		<![CDATA[
		duo = null;
		rpCtx = input.getSubcontext("net.shibboleth.idp.profile.context.RelyingPartyContext");
		if (rpCtx) {
			duo = custom.get(rpCtx.getRelyingPartyId());
		}
		if (duo == null) {
			duo = custom.get("default");
		}
		duo;
		]]>
		</value>
	</constructor-arg>
</bean>

...

Expand
Code Block
languagexml
<!-- Turn off default behavior in favor of integration-specific principals below. -->
<util:constant id="shibboleth.authn.Duo.addDefaultPrincipals" static-field="java.lang.Boolean.FALSE" />

<bean id="DefaultDuo" classparent="net.shibboleth.idp.authn.duoDuo.BasicDuoIntegrationDuoIntegration"
		p:APIHost="%{idp.duo.apiHost:none}"
		p:applicationKey="%{idp.duo.applicationKey:none}"
		p:integrationKey="%{idp.duo.integrationKey:none}"
		p:secretKey="%{idp.duo.secretKey:none}">
	<property name="supportedPrincipals">
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
				c:classRef="http://example.org/ac/classes/mfa/default" />
			<bean parent="shibboleth.SAML1AuthenticationMethod"
				c:method="http://example.org/ac/classes/mfa/default" />
		</list>
	</property>
</bean>

<bean id="SpecialDuo" classparent="net.shibboleth.idp.authn.duoDuo.BasicDuoIntegrationDuoIntegration"
		p:APIHost="%{idp.specialduo.apiHost:none}"
		p:applicationKey="%{idp.specialduo.applicationKey:none}"
		p:integrationKey="%{idp.specialduo.integrationKey:none}"
		p:secretKey="%{idp.specialduo.secretKey:none}">
	<property name="supportedPrincipals">
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
				c:classRef="http://example.org/ac/classes/mfa/special" />
			<bean parent="shibboleth.SAML1AuthenticationMethod"
				c:method="http://example.org/ac/classes/mfa/special" />
		</list>
	</property>
</bean>

<util:list id="DuoIntegrationList">
	<ref bean="SpecialDuo" />
	<ref bean="DefaultDuo" />
</util:list>

<bean id="shibboleth.authn.Duo.DuoIntegrationStrategy" parent="shibboleth.ContextFunctions.Scripted"
		factory-method="inlineScript"
        p:customObject-ref="DuoIntegrationList">
	<constructor-arg>
		<value>
		<![CDATA[
		duo = null;
		authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
		iter = custom.iterator();
		while (duo == null && iter.hasNext()) {
			duo = iter.next();
			if (!authCtx.isAcceptable(duo)) {
				duo = null;
			}
		}
		duo;
		]]>
		</value>
	</constructor-arg>
</bean>

...

Expand
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">

    <!-- Require an explicit CA root on Duo AuthAPI calls. -->

    <bean id="shibboleth.authn.Duo.NonBrowser.HttpClientSecurityParameters"
            class="org.opensaml.security.httpclient.HttpClientSecurityParameters">
        <property name="tLSTrustEngine">
            <bean parent="shibboleth.StaticPKIXTrustEngine"
                p:trustedNames="*.duosecurity.com"
                p:checkNames="true">
				<property name="certificates">
					<list>
						<bean class="org.springframework.core.io.FileSystemResource"
							c:_0="%{idp.home}/credentials/duo-ca.pem"/>
					</list>
				</property>
			</bean>
		</property>
    </bean>
    
    <bean id="shibboleth.authn.Duo.NonBrowser.HttpClient"
        
parent="shibboleth.HttpClientFactory"
        p:connectionTimeout="%{idp.httpclient.connectionTimeout:PT1M}"
        p:connectionRequestTimeout="%{idp.httpclient.connectionRequestTimeout:PT1M}"
        p:socketTimeout="%{idp.httpclient.socketTimeout:PT1M}" 
        p:maxConnectionsTotal="%{idp.httpclient.maxConnectionsTotal:100}"
        p:maxConnectionsPerRoute="%{idp.httpclient.maxConnectionsPerRoute:100}"
        p:tLSSocketFactory-ref="shibboleth.SecurityEnhancedTLSSocketFactory" />

</beans>

...

Expand
titleBeans

The beans defined, or expected to be defined, in authn/duo-authn-config.xml follow:

Bean ID / Type

Default

Description

shibboleth.authn.Duo.DuoIntegration

DuoIntegration

Derived from settings in duo.properties

Defines a single/static DuoWeb ntegration with Duo, you can override this bean to supply a non-property-configured alternative or inherit from it to create additional ones

shibboleth.authn.Duo.NonBrowser.DuoIntegration

DuoIntegration

Derived from settings in duo.properties

Defines a single/static AuthAPI integration with Duo, you can override this bean to supply a non-property-configured alternative or inherit from it to create additional ones

shibboleth.authn.Duo.DuoIntegrationStrategy

Function<ProfileRequestContext,DuoIntegration>

Optional bean to supply the DuoWeb integration settings dynamically

shibboleth.authn.Duo.NonBrowser.DuoIntegrationStrategy

Function<ProfileRequestContext,DuoIntegration>

Optional bean to supply the Duo AuthAPI integration settings dynamically

shibboleth.authn.Duo.UsernameLookupStrategy

Function<ProfileRequestContext,String>

CanonicalUsernameLookupStrategy

Optional bean to supply username

shibboleth.authn.Duo.resultCachingPredicate

Predicate<ProfileRequestContext>

An optional bean that can be defined to control whether to preserve the authentication result in an IdP session

shibboleth.authn.Duo.addDefaultPrincipals

Boolean

true

Whether to add the content of the supportedPrincipals property of the underlying flow descriptor to the resulting Subject

shibboleth.authn.Duo.NonBrowser.HttpClient

HttpClient

Internal/default HttpClient instance

Overrides the HttpClient implementation and settings to use for the AuthAPI (see HttpClientConfiguration)

shibboleth.authn.Duo.NonBrowser.HttpClientSecurityParameters

HttpClientSecurityParameters

Custom security settings for the AuthAPI calls (see HttpClientConfiguration)

...

Expand
titleFlow Descriptor XML (V4.1+)

To replace the internally defined flow descriptor bean, the following XML is required:

Code Block
<util:list id="shibboleth.AvailableAuthenticationFlows">
 
    <bean p:id="authn/Duo" parent="shibboleth.AuthenticationFlow"
            p:order="%{idp.authn.Duo.order:1000}"
            p:nonBrowserSupported="%{idp.authn.Duo.nonBrowserSupported:false}"
            p:passiveAuthenticationSupported="%{idp.authn.Duo.passiveAuthenticationSupported:false}"
            p:forcedAuthenticationSupported="%{idp.authn.Duo.forcedAuthenticationSupported:true}"
            p:proxyRestrictionsEnforced="%{idp.authn.Duo.proxyRestrictionsEnforced:%{idp.authn.enforceProxyRestrictions:true}}"
            p:proxyScopingEnforced="%{idp.authn.Duo.proxyScopingEnforced:false}"
            p:discoveryRequired="%{idp.authn.Duo.discoveryRequired:false}"
            p:lifetime="%{idp.authn.Duo.lifetime:%{idp.authn.defaultLifetime:PT1H}}"
            p:inactivityTimeout="%{idp.authn.Duo.inactivityTimeout:%{idp.authn.defaultTimeout:PT30M}}"
            p:reuseCondition-ref="#{'%{idp.authn.Duo.reuseCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:activationCondition-ref="#{'%{idp.authn.Duo.activationCondition:shibboleth.Conditions.TRUE}'.trim()}"
            p:subjectDecorator-ref="#{getObject('%{idp.authn.Duo.subjectDecorator:}'.trim())}">
        <property name="supportedPrincipalsByString">
            <bean parent="shibboleth.CommaDelimStringArray"
                c:_0="#{'%{idp.authn.Duo.supportedPrincipals:}'.trim()}" />
        </property>
    </bean>
 
</util:list>

In older versions and upgraded systems, this list is defined in conf/authn/general-authn.xml. In V4.1+, no default version of the list is provided and it may simply be placed in conf/global.xml if needed.

...