Multiple Duo Instances

287 views
Skip to first unread message

brian mancuso

unread,
Feb 6, 2018, 9:25:58 AM2/6/18
to CAS Community
We would like to allow users in a specific ldap group the ability to optionally bypass Duo for a given service if the user is not signed up for a 2fa account. Essentially there would be these two cases for a user: 

- 2fa always required
- 2fa optionally required (but always required if the user has a Duo account)

I have two duo instances defined in the cas.properties file: mfa-duo, mfa-duo-force. The first is in bypass mode while the latter doesn't allow any bypass.

Then my other classes are thus:
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.apereo.cas.custom.config.SelectiveDuoWebflowEventResolverConfiguration


I then put together a custom trigger that will determine if a user is required to use DUO or not:

SelectiveDuoWebflowEventResolver.java
package org.apereo.cas.custom.mfa;

import com.google.common.collect.ImmutableSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.services.MultifactorAuthenticationProvider;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.flow.resolver.impl.AbstractCasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

public class SelectiveDuoWebflowEventResolver extends AbstractCasWebflowEventResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(SelectiveDuoWebflowEventResolver.class);

    public SelectiveDuoWebflowEventResolver(AuthenticationSystemSupport authenticationSystemSupport, CentralAuthenticationService centralAuthenticationService, ServicesManager servicesManager, TicketRegistrySupport ticketRegistrySupport, CookieGenerator warnCookieGenerator, AuthenticationServiceSelectionPlan authenticationSelectionStrategies, MultifactorAuthenticationProviderSelector selector) {
        super(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator, authenticationSelectionStrategies, selector);
    }

    @Override
    public Set<Event> resolveInternal(RequestContext context) {
        final RegisteredService service = WebUtils.getRegisteredService(context);
        final Authentication authentication = WebUtils.getAuthentication(context);
        Set<String> attributeKeys = authentication.getAttributes().keySet();
        for (String s : attributeKeys) {
            System.out.println("s: " + s + " " + authentication.getAttributes().get(s));
        }
        Principal principal = authentication.getPrincipal();
        attributeKeys = principal.getAttributes().keySet();
        for (String s : attributeKeys) {
            System.out.println("p: " + s + " " + principal.getAttributes().get(s));
        }
        if (userRequiresDUO()) {
            LOGGER.warn("Forcing MFA");
            Optional<MultifactorAuthenticationProvider> mfaDuoForced = this.getMultifactorAuthenticationProviderFromApplicationContext("mfa-duo-force");
            MultifactorAuthenticationProvider forcedProvider = mfaDuoForced.get();
            final Map eventAttributes
                    = buildEventAttributeMap(authentication.getPrincipal(),
                            service,
                            forcedProvider);
            final Event event
                    = validateEventIdForMatchingTransitionInContext(forcedProvider.getId(),
                            context, eventAttributes);
            return ImmutableSet.of(event);
        } else {
        LOGGER.warn("Not forcing MFA");
        Optional<MultifactorAuthenticationProvider> mfaDuo = this.getMultifactorAuthenticationProviderFromApplicationContext("mfa-duo");
        MultifactorAuthenticationProvider bypassableProvider = mfaDuo.get();
        final Map eventAttributes
                = buildEventAttributeMap(authentication.getPrincipal(),
                        service,
                        bypassableProvider);
        final Event event
                = validateEventIdForMatchingTransitionInContext(bypassableProvider.getId(),
                        context, eventAttributes);
        return ImmutableSet.of(event);
        }
    }
}



SelectiveDuoWebflowEventResolverConfiguration.java
package org.apereo.cas.custom.config;

import javax.annotation.PostConstruct;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.custom.mfa.SelectiveDuoWebflowEventResolver;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.flow.authentication.RankedMultifactorAuthenticationProviderSelector;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.CookieGenerator;

@Configuration("selectiveDuoWebflowEventResolverConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class SelectiveDuoWebflowEventResolverConfiguration {

    @Autowired
    @Qualifier("initialAuthenticationAttemptWebflowEventResolver")
    private CasDelegatingWebflowEventResolver initialEventResolver;

    @Autowired
    @Qualifier("centralAuthenticationService")
    private CentralAuthenticationService centralAuthenticationService;

    @Autowired
    @Qualifier("defaultAuthenticationSystemSupport")
    private AuthenticationSystemSupport authenticationSystemSupport;

    @Autowired
    @Qualifier("defaultTicketRegistrySupport")
    private TicketRegistrySupport ticketRegistrySupport;

    @Autowired
    @Qualifier("servicesManager")
    private ServicesManager servicesManager;

    @Autowired(required = false)
    @Qualifier("multifactorAuthenticationProviderSelector")
    private final MultifactorAuthenticationProviderSelector multifactorAuthenticationProviderSelector = new RankedMultifactorAuthenticationProviderSelector();

    @Autowired
    @Qualifier("warnCookieGenerator")
    private CookieGenerator warnCookieGenerator;

    @Autowired
    @Qualifier("authenticationServiceSelectionPlan")
    private AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies;

    @RefreshScope
    @Bean
    public CasWebflowEventResolver selectiveDuoWebflowEventResolver() {
        return new SelectiveDuoWebflowEventResolver(authenticationSystemSupport,
                centralAuthenticationService,
                servicesManager, ticketRegistrySupport, warnCookieGenerator,
                authenticationRequestServiceSelectionStrategies,
                multifactorAuthenticationProviderSelector);
    }

    @PostConstruct
    public void initialize() {
        initialEventResolver.addDelegate(selectiveDuoWebflowEventResolver());
    }

}


This is driving me nuts because in the documentation it just states that you are allowed to use multiple DUO instances. But I'm getting an error that transitions aren't defined for the mfa-duo-force instance:

2018-02-01 10:25:29,433 WARN [org.apereo.cas.web.flow.resolver.impl.AbstractCasWebflowEventResolver] - <Transition definition cannot be found for event [mfa-duo-force|mfa-duo]>



If anyone has any information on how I can get this working or if I'm approaching this all wrong, please let me know. Thanks in advance!

Man H

unread,
Feb 6, 2018, 9:59:02 AM2/6/18
to cas-...@apereo.org
Couldn't this be achieved through custom authentication handler?
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscribe@apereo.org.
To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/263f6a6c-9f2b-446f-9707-3c23b96a3f65%40apereo.org.

brian mancuso

unread,
Feb 6, 2018, 10:18:02 AM2/6/18
to CAS Community
I'm open to any solution that simplifies things and meets the needs. When I'd read the documentation, it seemed custom triggers were the way to go here.

To give a little more information, I have students and employees that both need to login via CAS to several systems. For some of those systems, we need to require employees that login to use DUO while students will have the option, but not be required. Other systems won't require DUO for either group unless they're already enrolled.
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+u...@apereo.org.

Man H

unread,
Feb 6, 2018, 10:53:56 AM2/6/18
to cas-...@apereo.org
This triggers (assuming you're on 5.2) are not useful?

# Activate MFA globally based on authentication metadata attributes
# cas.authn.mfa.globalAuthenticationAttributeNameTriggers=memberOf,eduPersonPrimaryAffiliation
# cas.authn.mfa.globalAuthenticationAttributeValueRegex=faculty|staff

# Activate MFA globally based on principal attributes
# cas.authn.mfa.globalPrincipalAttributeNameTriggers=memberOf,eduPersonPrimaryAffiliation

# Specify the regular expression pattern to trigger multifactor when working with a single provider.
# Comment out the setting when working with multiple multifactor providers
# cas.authn.mfa.globalPrincipalAttributeValueRegex=faculty|staff

# Activate MFA globally based on principal attributes and a groovy-based predicate
# cas.authn.mfa.globalPrincipalAttributePredicate=file:/etc/cas/PredicateExample.groovy


To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscribe@apereo.org.
To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/ea1c8c9e-e871-458c-bb74-38e3ed896421%40apereo.org.

Man H

unread,
Feb 6, 2018, 12:56:21 PM2/6/18
to cas-...@apereo.org
So in my opinion  you have a globaltriggerpolicy mfa-duo and eg a groovy trigger for employees.

https://apereo.github.io/cas/5.2.x/installation/Configuring-Multifactor-Authentication-Triggers.html

2018-02-06 12:18 GMT-03:00 brian mancuso <snid...@gmail.com>:
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscribe@apereo.org.
To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/ea1c8c9e-e871-458c-bb74-38e3ed896421%40apereo.org.

Misagh Moayyed

unread,
Feb 6, 2018, 2:14:53 PM2/6/18
to cas-...@apereo.org
Brian, if I have understood things correctly I think you're doing this the hard way: I suspect the sort of thing you're after can be handled with bypass options in CAS where you skip MFA if a particular attribute is found on the authenticated user (ldap group is blah). If a match is found, bypass will kick in and if not, CAS should be able to trigger MFA. There is also the built-in ability to check with Duo directly to see if the user has in fact registered for MFA/Duo and does have an account.

--Misagh


--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+u...@apereo.org.

brian mancuso

unread,
Feb 7, 2018, 1:56:23 PM2/7/18
to CAS Community, mmoa...@unicon.net
Hey Misagh,

Could you point me to something about the built in feature for checking if a user is already registered for MFA/Duo?

Misagh Moayyed

unread,
Feb 7, 2018, 2:10:42 PM2/7/18
to cas-...@apereo.org

brian mancuso

unread,
Feb 8, 2018, 8:55:48 AM2/8/18
to CAS Community, mmoa...@unicon.net
Alright Misagh and Manfredo, I believe you're both putting me on the right track with this. Unfortunately, I haven't used a groovy script before and I'm having trouble getting it to get picked up by CAS. Could either of you help with this example?

/etc/cas/selectiveDuo.groovy:

def String run(final Object... args) {

   def authentication = args[0]

   def principal = args[1]

   def service = args[2]

   def provider = args[3]

   def logger = args[4]

   def httpRequest = args[5]


    logger.info("Evaluating principal attributes ${principal.attributes}")

    def bypass = principal.attributes['uid']
    if (bypass.contains("testuid")) {

       logger.info("Skipping bypass for principal ${principal.id}
                return false

   }

    return true

}

Is this really what the groovy file should look like or am I missing imports and package info at the top? I never get any info logged, so I'm pretty sure this script never gets run.

/etc/cas/config/cas.properties:

cas.authn.mfa.duo[0].rank=0

cas.authn.mfa.duo[0].duoApiHost=REMOVED

cas.authn.mfa.duo[0].duoIntegrationKey=REMOVED

cas.authn.mfa.duo[0].duoSecretKey=REMOVED

cas.authn.mfa.duo[0].duoApplicationKey=REMOVED

cas.authn.mfa.duo[0].id=mfa-duo

cas.authn.mfa.globalProviderId=mfa-duo

cas.authn.mfa.globalPrincipalAttributePredicate=file:///etc/cas/selectiveDuo.groovy




Man H

unread,
Feb 8, 2018, 9:28:29 AM2/8/18
to cas-...@apereo.org
In version 5.2 this should be 
cas.authn.mfa.providerSelectorGroovyScript=file:/etc/cas/wathever.groovy
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscribe@apereo.org.
To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/9ade320d-4c96-4c23-b22b-a830387cf692%40apereo.org.

brian mancuso

unread,
Feb 8, 2018, 9:36:49 AM2/8/18
to CAS Community
Hey Manfredo,

I'm actually trying to go with the bypass vs the provider selector: Shown Here. I'm hoping to simplify the environment to only one Duo instance with the use of an LDAP attribute that will be utilized in the groovy script once I get the script to run.

But, it seems that there are typos and other missing details from the samples. For example, the first one returns a boolean while the second one returns a String. Both are meant to be the same thing. The first also has an import for java.util.* while the second either ignores it or omits it. 
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+u...@apereo.org.

Man H

unread,
Feb 8, 2018, 9:38:01 AM2/8/18
to cas-...@apereo.org
Start a new thread with full information such as version pom properties startup log with debug set etc ...
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cas-user+unsubscribe@apereo.org.
To view this discussion on the web visit https://groups.google.com/a/apereo.org/d/msgid/cas-user/9ade320d-4c96-4c23-b22b-a830387cf692%40apereo.org.
Reply all
Reply to author
Forward
0 new messages