...
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.
...
Multiple Duo Integrations
Expand |
---|
Code Block |
---|
| <bean id="DefaultDuo" | classnet.idp.duoBasicDuoIntegrationDuoIntegration"
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" | classnet..idpduoBasicDuoIntegrationDuoIntegration"
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> |
|
Integration-Specific Principal Sets
...
Multiple Integrations with Distinct Principal Sets
Expand |
---|
Code Block |
---|
| <!-- 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" | classnet..idpduoBasicDuoIntegrationDuoIntegration"
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" | classnet..idpduoBasicDuoIntegrationDuoIntegration"
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> |
|
Advanced HttpClient Scenarios
...
Assuming you want to do this, the following example to place in conf/authn/duo-authn-config.xml will ensure that the root CA used by Duo is a specific CA. It assumes you've pulled down and stored the CA certificate in a file, and it also assumes the subject name Duo uses continues to be the same wildcard, which seems likely. But if they change their CA, as they probably will someday, you won't know and it will break, so if that bothers you, you should speak to Duo about it.
duo-authn-config.xml
Expand |
---|
Code Block |
---|
| <?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> |
|
Reference
Localtabgroup |
---|
Localtab live |
---|
active | true Expand |
---|
|
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) |
localtab-live |
Expand |
---|
|
The properties defined in conf/authn/duo.properties follow: Name | Default | Function |
---|
idp.duo.apiHost | | DuoWeb API hostname assigned to the integration | idp.duo.applicationKey | | A secret supplied by you and not shared with Duo; see https://duo.com/docs/duoweb-v2, "Generate an akey". | idp.duo.integrationKey | | DuoWeb integration key (supplied by Duo as Client ID) | idp.duo.secretKey | | DuoWeb secret key (supplied by Duo as Client secret) | idp.duo.nonbrowser.apiHost | ${idp.duo.apiHost} | Duo AuthAPI hostname assigned to the integration | idp.duo.nonbrowser.applicationKey |
| a secret supplied by you and not shared with Duo; see https://duo.com/docs/duoweb-v2, "Generate an akey". | idp.duo.nonbrowser.integrationKey | | Duo AuthAPI integration key (supplied by Duo as Client ID) | idp.duo.nonbrowser.secretKey | | Duo AuthAPI secret key (supplied by Duo as Client secret) | idp.duo.nonbrowser.header.factor | X-Shibboleth-Duo-Factor | Name of HTTP request header for Duo AuthAPI factor | idp.duo.nonbrowser.header.device | X-Shibboleth-Duo-Device | Name of HTTP request header for Duo AuthAPI device ID or name | idp.duo.nonbrowser.header.passcode | X-Shibboleth-Duo-Passcode | Name of HTTP request header for Duo AuthAPI passcode | idp.duo.nonbrowser.auto | true | Allow the factor to be defaulted in as "auto" if no headers are received | idp.duo.nonbrowser.clientAddressTrusted | true | Pass client address to Duo in API calls to support logging, push display, and network-based Duo policies |
localtab-live |
Expand |
---|
|
The Duo-specific properties defined in conf/authn/duo.properties follow: Name | Default | Description |
---|
idp.duo.apiHost | | DuoWeb API hostname assigned to the integration | idp.duo.applicationKey | | A secret supplied by you and not shared with Duo; see https://duo.com/docs/duoweb-v2, "Generate an akey". | idp.duo.integrationKey | | DuoWeb integration key (supplied by Duo as Client ID) | idp.duo.secretKey | | DuoWeb secret key (supplied by Duo as Client secret) | idp.duo.nonbrowser.apiHost | ${idp.duo.apiHost} | Duo AuthAPI hostname assigned to the integration | idp.duo.nonbrowser.integrationKey | | Duo AuthAPI integration key (supplied by Duo as Client ID) | idp.duo.nonbrowser.secretKey | | Duo AuthAPI secret key (supplied by Duo as Client secret) | idp.duo.nonbrowser.header.factor | X-Shibboleth-Duo-Factor | Name of HTTP request header for Duo AuthAPI factor | idp.duo.nonbrowser.header.device | X-Shibboleth-Duo-Device | Name of HTTP request header for Duo AuthAPI device ID or name | idp.duo.nonbrowser.header.passcode | X-Shibboleth-Duo-Passcode | Name of HTTP request header for Duo AuthAPI passcode | idp.duo.nonbrowser.auto | true | Allow the factor to be defaulted in as "auto" if no headers are received | idp.duo.nonbrowser.clientAddressTrusted | true | Pass client address to Duo in API calls to support logging, push display, and network-based Duo policies |
The general properties configuring this flow via authn/authn.properties are: Name | Default | Description |
---|
idp.authn.Duo.order | 1000 | Flow priority relative to other enabled login flows (lower is "higher" in priority) | idp.authn.Duo.nonBrowserSupported | false | Whether the flow should handle non-browser request profiles (e.g., ECP) | idp.authn.Duo.passiveAuthenticationSupported | false | Whether the flow allows for passive authentication | idp.authn.Duo.forcedAuthenticationSupported | true | Whether the flow supports forced authentication | idp.authn.Duo.proxyRestrictionsEnforced | %{idp.authn.enforceProxyRestrictions:true} | Whether the flow enforces upstream IdP-imposed restrictions on proxying | idp.authn.Duo.proxyScopingEnforced | false | Whether the flow considers itself to be proxying, and therefore enforces SP-signaled restrictions on proxying | idp.authn.Duo.discoveryRequired | false | Whether to invoke IdP-discovery prior to running flow | idp.authn.Duo.lifetime | %{idp.authn.defaultLifetime:PT1H} | Lifetime of results produced by this flow | idp.authn.Duo.inactivityTimeout | %{idp.authn.defaultTimeout:PT30M} | Inactivity timeout of results produced by this flow | idp.authn.Duo.reuseCondition | shibboleth.Conditions.TRUE | Bean ID of Predicate<ProfileRequestContext> controlling result reuse for SSO | idp.authn.Duo.activationCondition | shibboleth.Conditions.TRUE | Bean ID of Predicate<ProfileRequestContext> determining whether flow is usable for request | idp.authn.Duo.subjectDecorator | | Bean ID of BiConsumer<ProfileRequestContext,Subject> for subject customization | idp.authn.Duo.supportedPrincipals | (see below) | Comma-delimited list of protocol-specific Principal strings associated with flow | idp.authn.Duo.addDefaultPrincipals | true | Whether to auto-attach the preceding set of Principal objects to each Subject produced by this flow |
As a non-password based flow, the supportedPrincipals property defaults to the following XML: Code Block |
---|
| <list>
<bean parent="shibboleth.SAML2AuthnContextClassRef"
c:classRef="http://example.org/ac/classes/mfa" />
<bean parent="shibboleth.SAML1AuthenticationMethod"
c:method="http://example.org/ac/classes/mfa" />
</list> |
In property form, this is expressed as (note the trailing commas): Code Block |
---|
idp.authn.Duo.supportedPrincipals = \
saml2/http://example.org/ac/classes/mfa, \
saml1/http://example.org/ac/classes/mfa |
However, this default is (obviously) intended purely as an illustrative example of how to define your own values, as there are no standard ones to use. Some suggestions are mentioned earlier under General Configuration. localtab-live |
Expand |
---|
title | Flow 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. |
...