Complicating my MFA implementation

Wessel, Keith kwessel at illinois.edu
Mon Apr 25 21:47:46 EDT 2016


Very interesting, Mike. Thanks for sharing. I assume the rest of your configuration (outside the attribute resolver config) handles step-up authentication for a non-opt-in user who logged into a regular service at the start of their session then visited a service requiring the 2nd factor. Is that correct?

What context gets returned for an opt-in user who logs into any old SP, meaning one that doesn't require MFA? Does the password context get returned if that's what they request? Or do you always return the Duo context if Duo was completed, regardless of request?

Finally, this setup assumes that the list of SPs requiring Duo is controlled by the IDP. Is there any way to make it leverage the requested context instead, allowing MFA to be requested by the SP in the SAML request instead of making it all depend on IDP config and grou configuration?

Keith


-----Original Message-----
From: users [mailto:users-bounces at shibboleth.net] On Behalf Of Michael A Grady
Sent: Monday, April 25, 2016 8:33 PM
To: Shib Users <users at shibboleth.net>
Subject: Re: Complicating my MFA implementation


> On Apr 25, 2016, at 3:12 PM, Wessel, Keith <kwessel at illinois.edu> wrote:
> 
> The config I'm referring to is in step 8 of this page:
> 
> https://wiki.shibboleth.net/confluence/pages/viewpage.action?pageId=20807829
> 
> With that in mind, is that a valid use of the intercept? Or is that a bad idea?
> 
> And is there any way, if it's not a bad idea, to craft it into what I was talking about with opt-in? If not, I'll look into the custom subflow.
> 
> Thanks,
> Keith
> 

That is somewhat specific to U Chicago, where they have folks that qualify for Silver based on password alone. Nothing to do with whether Duo was triggered.

You can get quite complicated with your logic for calculating eduPersonAssurance. Not that many would want to get as complicated as the below, but I include it as an example of what you could do if you so chose to do it. You can consider the SP entityID, the person, and the group memberships amongst other things.

One place we were working with was considering managing groups based on the SP entityID, where the users who had to do MFA for that SP would be identified in some field in that group. That's likely more manageable than below, if you are going to have a limited set of users required to do Duo for a given SP.

So here is an example of "beyond complex" logic, one way of implementing the use cases (below) that I was asked to satisfy within the resolver.  (You could just put all the logic into the script, and loop thru the memberOf values in the script. Might want to do that if you had lots of groups, and you were looking for groups that matched the "current SP entityID".)

You'll see this gets pretty complex. Partly because I'm allowing the possibility that the sets of SPs for each of the cases could be different sets of SPs, and partly because I'm allowing for multiple opt-in type groups, one that is opt-in to all, one that is opt-in to Case 4, and one that is Opt-out of Case 3 (i/.e. Case 5). If you don't have that, you can simplify significantly from below.

Note I handle the group memberships (isMember) in the "dependencyOnly" Mapped attribute 'groupAssuranceSignals'. Then I use those to initialize a hash in the script, and reference those values at the needed places. And the initialization of each set of SPs happens where it is needed, and done in a way I thought would be easy to maintain -- as a "hash". So you'd need to plug in the right group names, the right SP entityIDs, etc.

As info, here are the use cases you presented to be "solved". But I allowed for opt-in to potentially be more than one group, and the sets of SPs to be different. If you don't want that, some stuff could be removed, or less "sets of SP entityIDs" initialized.


1.	Require Duo login for opt-in users [i] logging into the IdP (i.e., any SP).
2.	Require Duo login for all users logging into specific SPs.
3.	Require Duo login only for a certain user population [ii] logging into specific SPs.
4.	Require Duo login only for opt-in users [i] when logging into specific SPs.
5.	Opt-out [i] certain users from Use Case #3.

---

	<resolver:AttributeDefinition xsi:type="ad:Mapped" id="groupAssuranceSignals" sourceAttributeID="memberOf" dependencyOnly="true">
		<resolver:Dependency ref="myLDAP"/>
		<ad:ValueMap>
			<ad:ReturnValue>use case 1 users opt-in to all SPs</ad:ReturnValue>
			<ad:SourceValue ignoreCase="true">CN=Duo Opt-in,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue>
		</ad:ValueMap>
		<ad:ValueMap>
			<ad:ReturnValue>use case 4 users for specific SPs</ad:ReturnValue>
			<ad:SourceValue ignoreCase="true">CN=Duo For Use Case 4,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue>
		</ad:ValueMap>
		<ad:ValueMap>
			<ad:ReturnValue>exempt users from use case 3</ad:ReturnValue>
			<ad:SourceValue ignoreCase="true">CN=Duo Exempt For Use Case 3,OU=Groups,OU=CAMPUS,DC=campustest,DC=net</ad:SourceValue>
		</ad:ValueMap>
	</resolver:AttributeDefinition>

	<resolver:AttributeDefinition id="eduPersonAssurance" xsi:type="ad:Script">
	    <resolver:Dependency ref="myLDAP" />
	    <resolver:Dependency ref="groupAssuranceSignals" />
	    <resolver:Dependency ref="eduPersonAffiliation" />
		<resolver:AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:1.3.6.1.4.1.1466.115.121.1.15" friendlyName="eduPersonAssurance" encodeType="false" />

	    <ad:Script><![CDATA[
		logger = Java.type("org.slf4j.LoggerFactory").getLogger("net.shibboleth.idp.attribute.resolver.buildEduPersonAssurance");
		var duoAuthnContext = 'http://www.duosecurity.com/';  // set the context value we are using to indicate Duo
		var spEntityId = resolutionContext.getAttributeRecipientID(); // This SP's entityID
		var contextSet = false;
		eduPersonAssurance.getValues().clear(); //remove any current values, shouldn't be any

		// Set up a "hash" based on any applicable Group memberships this person has
		var groupSignals = new Object();
		if (typeof groupAssuranceSignals != "undefined" && groupAssuranceSignals.getValues().size() > 0) {
			logger.debug("Found some groupAssuranceSignals");
			for ( i = 0; i < groupAssuranceSignals.getValues().size(); i++ ) {
				value = groupAssuranceSignals.getValues().get(i);
				groupSignals[ value ] = 1; // create a hash with all the 'group signals' based on isMember
				logger.debug("Group signal set: " + value);
			}
		}

		// Case 1: Require Duo login for opt-in users logging into the IdP (i.e., any SP).
		if ( groupSignals['use case 1 users opt-in to all SPs'] ) {
			eduPersonAssurance.getValues().add(duoAuthnContext);
			contextSet = true;
			logger.debug("Case 1 applied");
		}

		// Case 2: Require Duo login for all users logging into specific SPs.
		if ( !contextSet ) { // Case 2: see if it applies
			// Initialize Case 2 SPs that require Duo for all users.
			// You only need to do that if the SP's requested authn context needs overriding,
			// otherwise you can depend on that being enforced by requested authn context.
			var SPsAllMustDoDuoFor = new Object();
					SPsAllMustDoDuoFor['SP 1 entityID'] = 1;
					SPsAllMustDoDuoFor['SP 2 entityID'] = 1;

			if (SPsAllMustDoDuoFor[spEntityId]) { // check the current SP against Case 2 SPs
				eduPersonAssurance.getValues().add(duoAuthnContext); // Yes, case 2
				contextSet = true;
				logger.debug("Case 2 applied");
			}
		}

		// Case 4: Require Duo login only for opt-in users when logging into specific SPs.
		if ( !contextSet ) { // Case 4: see if it applies
			// Initialize Case 4 SPs that require Duo for opt-in users
			var SPsForCase4 = new Object();
					SPsForCase4['SP 1 entityID'] = 1;
					SPsForCase4['SP 2 entityID'] = 1;
			// check the current SP against Case 4 SPs and User for Case 4 opt-in
			if (SPsForCase4[spEntityId] && groupSignals['use case 4 users for specific SPs'] ) {
				eduPersonAssurance.getValues().add(duoAuthnContext); // Yes, case 4
				contextSet = true;
				logger.debug("Case 4 applied");
			}
		}

		// Case 3: Require Duo login only for a certain user population logging into specific SPs
		// Case 5: Opt-out certain users from Use Case #3
		if ( !contextSet ) { // Left with Cases 3 & 5 to check
			if (groupSignals['exempt users from use case 3']) { // Case 5 overrides Case 3
					// Case 5 applies, these users are exempt from Case 3, allow them PPT
					eduPersonAssurance.getValues().add('urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport');
					contextSet = true;
					logger.debug("Case 5 applied");
			} else { // Case 3: see if it applies
					// Initialize Case 3 SPs that require Duo for all users with certain affiliation
					var SPsForCase3 = new Object();
							SPsForCase3['SP 1 entityID'] = 1;
							SPsForCase3['SP 2 entityID'] = 1;

				    // See if case 3 applies to this SP, and if so, if user
				 	// has eduPersonAffiliation requiring Duo.
					if (SPsForCase3[spEntityId] && typeof eduPersonAffiliation != "undefined" && eduPersonAffiliation.getValues().size() > 0) {
						for ( i = 0; i < eduPersonAffiliation.getValues().size(); i++ ) {
							value = eduPersonAffiliation.getValues().get(i);
							if (value == "employee") {
								eduPersonAssurance.getValues().add(duoAuthnContext);
								contextSet = true;
								logger.debug("Case 3 applied");
							}
						}
					}
			}
		}

		// if no eduPersonAssurance value has been set yet, set it to PPT
		if (eduPersonAssurance.getValues().size() == 0) { // Or you could just check for !contextSet
			eduPersonAssurance.getValues().add('urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport');
			logger.debug("No Cases applied, setting default value");
		}

		logger.debug("eduPersonAssurance final value: " + eduPersonAssurance.getValues().get(0));
		]]></ad:Script>

	</resolver:AttributeDefinition>


--
Michael A. Grady
IAM Architect, Unicon, Inc.



More information about the users mailing list