Complicating my MFA implementation
Michael A Grady
mgrady at unicon.net
Mon Apr 25 21:32:40 EDT 2016
> 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.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 842 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <http://shibboleth.net/pipermail/users/attachments/20160425/8e38d202/attachment.sig>
More information about the users
mailing list