Many companies have business relationships with SaaS partners that use SAML for authentication. ADFS works very well for many as a SAML WS-* federation infrastructure, although we have had some hiccups and incompatibilities along the way. One thing that comes up every now and then is applying business rules to the federation trust with a partner. Microsoft has done a very good job of explaining how to implement certain business rules for Office365 in some of their official blog posts by PFE’s. But what I have not seen is some of that practical help applied to non Microsoft services that we rely on.
The rest of this article will address how we implemented a couple of these.
2 simple rules we are asked about are:
We want our users to be able to only use this SaaS service when they are on our internal network or VPN. This may provide the business/security/compliance stakeholder a bit of extra confidence that the SaaS service is being used properly.
We want only the users in this Active Directory group to be able to access this SaaS service. This could be a licensing requirement or a data security issue.
These business rules are typically requested for a specific SaaS application, so they are implemented as claims rules on the specific relying party trust for that SaaS application. It could also be applied in a service provider relationship but you would have to re-examine the logic. So just open the ADFS management tool, in a dev environment please!. Then go to Relying party trusts and select the trust you want to test with. Then click Edit Claims Rules.
The first business rule makes an assumption that should hold true in an ADFS farm. It assumes that if you have an ms-proxy value as an existing claim, then you authenticated via the proxy. If you authenticated via the proxy then you are on the external network. If you do not have an ms-proxy value, then you authenticated via the Federation Servers and you are therefore on the internal network. Now that assumption may not hold true in all infrastructures, but it should in most, so examine it very carefully. It is the basis of the security decisions we make with this rule.
This rule is to either permit or deny users to authenticate so it is classified as a Issuance Authorization Rule. Leave the permit all users rule alone and create this as a second rule, of the custom type. Here is the syntax:
exists([Type == “http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy”%5D) => issue(Type = “http://schemas.microsoft.com/authorization/claims/deny”, Value = “true”);
Regarding the 2nd rule, I have a few issues with it. 1st, in a large Active Directory domain, token bloat is a common issue. I generally try to discourage the use of AD groups for application authorization for many reasons that ultimately lead back to token bloat. But in some cases we have to allow it. 2nd, many SaaS providers ask you to groups over as a claim. And it is relatively easy to do so. But this is a bit of a security hazard. If your company like mine, names their groups rather plainly to be user friendly, then you are sending over a lot more information than you intended. Consider, should you be informing your SaaS provider of all the clients you service, or who in your organization is an executive or an administrator? If you send over group claims as unfiltered you will be. So I strongly recommend you do not do that.
Here is the recommended method in 2 steps:
The first step is to make all of the groups available to the claims engine to process. Since the output of these rules is a value to send as a claim you will create these as Issuance Transform Rules. They are custom rules so you should select that option to create them.
Please note: This means all groups the user is a direct member of. Nested groups will not be included in this claim.
You must make sure this rule processes 1st or the 2nd rule will have nothing to work with. Here is the custom rule syntax.
The 2nd rule then processes all the group claims you made available and searches for a match to a string, if it matches then it will send it along. Anything that does not match will not be sent, maintaining a good deal of privacy.
c:[Type == “http://schemas.xmlsoap.org/claims/Group”, Value =~ “(?i)SaaSappName“] => issue(claim = c);
Another option that should perform a bit better is to find the actual sid of the group you want to allow. This needs only 1 rule instead of 2.
NOT exists([Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid”, Value =~ “Group SID value of AD group that should be allowed”])=> issue(Type = “http://schemas.microsoft.com/authorization/claims/deny”, Value = “true”);