Advice on migrating SPNEGO to MFA-flow
Philip Brusten
philip.brusten at kuleuven.be
Tue Jan 10 11:16:09 EST 2017
Hi Scott,
sorry for the delay...
On 15/12/2016 16:52, Cantor, Scott wrote:
>> For normal authentication requests (no special requirements from SP or
>> custom relying-party config) we want to authenticate our users by:
>> - their previous session if available and still active for achieving SSO
>> - SPNEGO authentication if the client resides in a subnet so it can
>> request a Kerberos ST. Therefore we set an activationCondition based on
>> several subnets on the authn/SPNEGO flow. Since not every user-agent is
>> automatically able to use SPNEGO we rely on the cookie to "opt-in" and
>> to actually run SPNEGO. (from a user's perspective this provides SSO as
>> well)
> And so the initial choice is always "password" unless the user opts-in, correct?
Correct
>
>> - password authentication if none of the above succeeded
>>
>> Our IAM-system allows users to register a 2nd factor. Users can also
>> indicate whether they want to use this 2nd factor for every login or
>> only when the application requests for it. The former sets the attribute
>> eduPersonAssurance to a certain value, for the latter eduPersonAssurance
>> is empty.
> And the second factor doesn't stand alone, so it has to be combined with...either password or SPNEGO? Just password?
The way we implemented it, we require the user to be know, either by
password or SPNEGO (1st factor)
As a side note: our strong authn solution
(https://www.n-auth.com/technology/) could be used as a true two factor
solution, but that's not how we implemented it for now.
>> If a SP requests for the authnContextClass
>> 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', but
>> the user already authenticated using its first and 2nd factor, he must
>> not re-authenticate because he already authenticated using a higher
>> level of trust.
> That works if your earlier method was password or password plus something else, but if you did SPNEGO, that technically isn't PasswordProtectedTransport. Once you start playing games like that, the IdP is going to deliberately make your life painful because it's not designed to lie. And once you manipulate the configuration to lie, that makes other lies automatic.
>
> Of course, this is why using contexts that refer to specific technologies is a problem, and why requesting weaker methods is a bad idea.
>
> But, if you want to configure SPNEGO as a flow to support that Principal type, it shouldn't do too much damage. Not every "lie" is the same and some won't cause as many weird side effects.
Ok, I'll have to look into that.
>> I was not using the ExtendedFlows feature of the Password flow to run
>> SPNEGO as a subflow. I now configured SPNEGO as an extended flow for
>> Password.
> I didn't recall whether that went hand in hand with that SWITCH built, it may have. I will grant that the extended flows stuff is awful, but that's because it was built as a stopgap until the MFA design was done. It's not as bad as that initial-authn disaster, but it's not great.
>
> I suspect that you can implement whatever logic you're using the extended flow hook to do in the MFA flow, and if you can, that would probably be better. The MFA flow can do things with cookies just as easily as anything else can. Limiting the different types of mechanics used is better than trying to combine a lot of features and make it all work together.
>
> The big advantage is all the logic around principal types. With the MFA flow, it's all much cleaner: you tell the system exactly which principals each flow supports, by themselves, and then the MFA flow can easily coordinate which ones to run for different situations.
>
> With the extended flows thing, I think you have to hack in a lot of extra weirdness and lie about which principals are supported to get things to work. It's just not good.
I will not use the extended flow feature, to not further complicate things.
This is what I got so far (not everything works... questions below)
<util:map id="shibboleth.authn.MFA.TransitionMap">
<entry key="">
<bean parent="shibboleth.authn.MFA.Transition"
p:nextFlowStrategy-ref="selectFirstFactor" />
</entry>
<entry key="authn/SPNEGO">
<bean parent="shibboleth.authn.MFA.Transition"
p:nextFlowStrategy-ref="checkSecondFactor" />
</entry>
<entry key="authn/Password">
<bean parent="shibboleth.authn.MFA.Transition"
p:nextFlowStrategy-ref="checkSecondFactor" />
</entry>
<!-- An implicit final rule will return whatever the final flow
returns. -->
</util:map>
<import resource="../../system/flows/authn/spnego-authn-beans.xml" />
<util:map id="customObjectsToSelectFirstFactor">
<entry key="SPNEGOAutoLoginManager"
value-ref="SPNEGOAutoLoginManager" />
<entry key="SPNEGOActicationCondition"
value-ref="shibboleth.SPNEGO.ActivationCondition" />
</util:map>
<!-- See which first factor we can use (SPNEGO or Password) -->
<bean id="selectFirstFactor"
parent="shibboleth.ContextFunctions.Scripted"
factory-method="inlineScript"
p:customObject-ref="customObjectsToSelectFirstFactor">
<constructor-arg>
<value>
<![CDATA[
nextFlow = 'authn/Password';
logger =
Java.type("org.slf4j.LoggerFactory").getLogger("selectFirstFactor");
logger.debug('Starting selectFirstFactor');
authCtx =
input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
mfaCtx =
authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
//check to see if user opt-in for SPNEGO using cookie
SPNEGOAutoLoginManager =
custom.get("SPNEGOAutoLoginManager");
isSPNEGOOptIn = SPNEGOAutoLoginManager.isEnabled();
logger.debug('Check if SPNEGO opt-in was done through
cookie: ' + isSPNEGOOptIn );
//check to see if SPNEGO can be activated
SPNEGOActicationCondition =
custom.get("SPNEGOActicationCondition");
isSPNEGOActivated = SPNEGOActicationCondition.apply(input);
logger.debug('Check if SPNEGO can be activated: ' +
isSPNEGOActivated );
if (SPNEGOAutoLoginManager.isEnabled() &&
SPNEGOActicationCondition.apply(input)) {
logger.debug('Opt-in for SPNEGO and
activationcondition is set');
nextFlow = 'authn/SPNEGO';
}
nextFlow;
]]>
</value>
</constructor-arg>
</bean>
<!-- Example script to see if second factor is required. -->
<bean id="checkSecondFactor"
parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript"
p:customObject-ref="shibboleth.AttributeResolverService">
<constructor-arg>
<value>
<![CDATA[
nextFlow = 'authn/multifactor';
logger =
Java.type("org.slf4j.LoggerFactory").getLogger("checkSecondFactor");
logger.debug('Starting checkSecondFactor');
// Go straight to second factor if we have to, or set
up for an attribute lookup first.
authCtx =
input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext");
mfaCtx =
authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext");
c14nCtx =
input.getSubcontext("net.shibboleth.idp.authn.context.SubjectCanonicalizationContext");
//check is the current MFA-conftext is already
acceptable, if so check if the user did an opt-in, otherwise finish
(with value 'null')
if (mfaCtx.isAcceptable()) {
logger.debug('MFA context is already acceptable but
now we will check if there is an opt-in set for multifactor');
// Attribute check is required to decide if first
factor alone is enough.
resCtx = input.getSubcontext(
"net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext",
true);
username = null;
logger.debug('Try to find username from
SubjectCanonicalizationContext or SessionContext...');
c14nCtx =
input.getSubcontext("net.shibboleth.idp.authn.context.SubjectCanonicalizationContext");
sessionContext =
input.getSubcontext("net.shibboleth.idp.session.context.SessionContext");
if (c14nCtx != null && c14nCtx.getPrincipalName()
!= null) {
username = c14nCtx.getPrincipalName();
logger.debug('Found username from
SubjectCanonicalizationContext: ' + username);
} else if (sessionContext != null &&
sessionContext.getIdPSession() != null) {
username =
sessionContext.getIdPSession().getPrincipalName();
logger.debug('Found username from
SessionContext: ' + username);
}
resCtx.setPrincipal(username);
logger.debug('resolving attrs for ' + username);
resCtx.getRequestedIdPAttributeNames().add("eduPersonAssurance");
resCtx.resolveAttributes(custom);
// Check for an attribute that authorizes use of
first factor.
attribute =
resCtx.getResolvedIdPAttributes().get("eduPersonAssurance");
valueType =
Java.type("net.shibboleth.idp.attribute.StringAttributeValue");
// set the nextFlow to null and exit MFA-flow when:
// a: There are no values for eduPersonAssurance
(attribute==null) -> not true in case of opt-in
// b: eduPersonAssurance is set, but does not
contain the opt-in value -> not true in case of opt-in
if (attribute == null || (attribute != null && !
attribute.getValues().contains(new
valueType("https://example.com/auth/multifactor")))) {
nextFlow = null;
}
input.removeSubcontext(resCtx); // cleanup
}
nextFlow; // pass control to second factor or end
with the first
]]>
</value>
</constructor-arg>
</bean>
Most use-cases are working. SPNEGO/Password is called when it should. I
will certainly have to do some further tests to ensure everything is
working (especially when SPNEGO is selected and then fails).
But I already have a question as SPNEGO + multifactor flow fails because
of the subject c14n when the MFA-flow finishes.
SPNEGO produces a subject like 'username at domain.com' which has to be
canonicalized to 'username'. I have 2 c14n active: c14n/attribute &
c14n/simple.
When I only use one factor the c14n works, but if I add the 2nd factor,
c14n fails.
I think c14n/simple won't run because it finds multiple subjects, after
MFA-flow merged the results:
2017-01-10 16:59:18,989 - INFO
[net.shibboleth.idp.authn.impl.AttributeSourcedSubjectCanonicalization:189]
- 10.34.165.120|Profile Action AttributeSourcedSubjectCanonicalization:
Attribute sources [uid] did not produce a usable identifier
2017-01-10 16:59:18,989 - INFO
[net.shibboleth.idp.authn.impl.SelectSubjectCanonicalizationFlow:62] -
10.34.165.120|Profile Action SelectSubjectCanonicalizationFlow: Moving
incomplete flow c14n/attribute to intermediate set, reselecting a
different one
2017-01-10 16:59:18,990 - DEBUG
[net.shibboleth.idp.authn.impl.SelectSubjectCanonicalizationFlow:100] -
10.34.165.120|Profile Action SelectSubjectCanonicalizationFlow: Checking
canonicalization flow c14n/simple for applicability...
2017-01-10 16:59:18,990 - DEBUG
[net.shibboleth.idp.authn.impl.SelectSubjectCanonicalizationFlow:106] -
10.34.165.120|Profile Action SelectSubjectCanonicalizationFlow:
Canonicalization flow c14n/simple was not applicable to this request
2017-01-10 16:59:18,990 - ERROR
[net.shibboleth.idp.authn.impl.SelectSubjectCanonicalizationFlow:78] -
10.34.165.120|Profile Action SelectSubjectCanonicalizationFlow: No
potential flows left to choose from, canonicalization will fail
Do I need to write my own merge strategy for this?
Why doesn't the MFA-flow user the principalnames that already have been
canonicalized?
Thx,
Philip
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://shibboleth.net/pipermail/users/attachments/20170110/6add015b/attachment-0001.html>
More information about the users
mailing list