An ADFS Claims Rules Adventure

[Editor’s Note: This is a guest post from Steve Halligan, a Senior Premier Field Engineer.]

Notes from the Field

Customers can come up with some fairly complex requirements for access control. It can be a challenge to accommodate these requirements in an Office 365 world. The ADFS claims rule system in ADFS 2.0 UR1 provides some powerful options to implement these controls—and some limitations.

The requirements:

No one shall access email via Outlook when off the corporate network

Members of a specific security group may not use ActiveSync

Members of a specific security group may not access OWA off the corporate network

All OWA users must log in via a forms based login

It is important to note that the rule processing system always processes all rules. It is NOT a first match system. Because of this, the first rule is most always an “allow everything” rule followed by additional rules that block some access. Schematically, it could look like this:

Allow everyone

Block members of the group “Bigwigs” from access to OWA

Allow the CEO access to OWA

When the CEO attempts to log in to OWA: Rule #1 allows him, Rule #2 blocks him, and then Rule #3 allows him. It is the state of the last matching rule that determine his final outcome. If the CIO were to attempt to log in, he would only match rules 1 and 2 and therefore would be blocked.

Requirement #1: No one shall access email via Outlook when off the corporate network

This is a fairly common requirement in a lot of enterprise Exchange environments. Security folks get a bit freaked out by the thought that a user could set up Outlook on a home machine. Meeting this challenge is pretty straightforward with ADFS 2.0 claims rules.

First, let’s review a bit how ADFS claims work in an Office 365 deployment. There are two flavors of ADFS claims requests: Active and Passive. When a claims request is created and submitted by the service (O365) it is an “active” request. This seems kind of counter-intuitive since you (the client) don’t have to do anything for these requests. The active/passive nature of the request refers to the participation of the _service_, not the client. Examples of O365 services that use active claims requests are Outlook 2007/2010 (RPC + HTTPS, EWS), Outlook 2011 for Mac (EWS), ActiveSync, Autodiscover and Lync Online.

A passive claims request is when the service sends you off to get the claim yourself. The main examples of this in O365 are OWA and Sharepoint Online. When you try to log in to OWA in O365 (and you are using federated identity) your browser is redirected to your ADFS endpoint. There you provide your credentials (either via a web form, basic authentication pop-up or integrated auth.), get your token and then return to OWA with your token in hand.

Back to the issue at hand: Blocking Outlook when client is not connected to the corporate network. To translate that into ADFS speak—We need to block active ADFS claims for the RPC+HTTPS and EWS services if the IP address doesn’t match a known set of corporate addresses.

Logically, the rule will look like this:

If { {ClientApplication is RPC Or ClientApplication is EWS} AND ClaimType is Active AND ClientIPAddress is not in <corporate ip address list>} THEN DENY THE CLAIM

The ‘Type’ x-ms-proxy exists. This simply means that the claim came through an ADFS Proxy server (or other compatible proxy). Note: we are not checking the ‘value’ for this type, just that the type exists.

The value for the type x-ms-forwarded-client-ip has a value that DOES NOT MATCH the regular expression “<public NAT addresses>”. That brings up two important questions:

Where does that “x-ms-forwarded-client-ip” come from and what values should I expect to see there?

What does the format of the regular expression look like?

According to TechNet:

“This AD FS claim represents a “best attempt” at ascertaining the IP address of the user (for example, the Outlook client) making the request. This claim can contain multiple IP addresses, including the address of every proxy that forwarded the request. This claim is populated from an HTTP header that is currently only set by Exchange Online, which populates the header when passing the authentication request to AD FS.”

So, the value is going to be the border IP address that Exchange Online (EXO) sees for the client. That would be either the border firewall doing NAT/PAT or the border Proxy server. Exchange Online add this IP to the ADFS claim request. Perfect for our Outlook scenario here: Outlook attempt to connect to EXO, EXO builds up a claims request that includes the client IP and heads out to the ADFS endpoint to submit the request.

The second question is a bit easier (or perhaps a bit harder—regular expressions can get complicated) due to the fact that the regular expression format follows the general rules for regular expressions. The Internet is full of regular expression examples to filter IP addresses. For example, let’s say that your network has one block of addresses in use in a NAT pool: 192.168.4.0-192.168.4.255. You also have one satellite office with a single public IP address: 10.3.4.5. An expression you may use could be:

If any one of the elements of the rule evaluate to false, the entire rule is skipped. So, if the client IS coming from one of the addresses that match the regular expression, they do not match this rule.

Requirement #2: Members of a specific security group may not use ActiveSync

The previous example illustrated how to allow or block users based upon where they are. This is a simple example of how to block users based upon who they are.

The new element here from the previous example is the “groupsid” type. Yeah, you need to dive into AD and hunt down the SID of the group in question. As is hinted by the “=~” operator, you could create a regular expression that would match more than one group. You could also use the “==” operator and the “|” to do a multiple “or” match.

That one was easy—sets us up well for the next. Which gets a bit…complicated.

Requirement #3: Members of a specific group may only use OWA on the corporate network

We built a rule above that did something very similar for Outlook, couldn’t we just add on or slightly alter that rule? Nope, we can’t. OWA login uses a passive ADFS claim, so the behavior is different. With Outlook, or other active claims, all requests come from O365 and land on the external ADFS proxy. To determine if a user is internal or external, we have to examine the “x-ms-forwarded-client-ip” value. With a passive claim request, like OWA, the client’s browser will be connecting directly to the ADFS endpoint (sts.contoso.com for example). So, we can control who gets in based upon where they are asking. If they ask the external proxies (and they are in the specified group) we say “no”. If they ask the internal ADFS servers we say “yes”.

I hope the blaring red font illustrates the point that this will not work. Why not? Scroll up a bit to the section on blocking Outlook based on IP address. Notice what fills in that x-ms-forwarded-client-ip? EXO. In this example we are dealing with _passive_ ADFS claims. EXO is not creating this claim—the user is hitting the ADFS login page directly. If you turn on this rule, everyone in the specified group will be blocked no matter where they are coming from. The x-ms-forwarded-client-ip type does not exist at all, so that line will evaluate to true. In order to make it false (and thereby stopping the deny rule from firing on someone) the element would need to exist AND the value need to match the regular expression.

If we can’t use the client’s source IP as a filter, how can we solve this problem?

We have been checking for the existence of the x-ms-proxy element, but we haven’t looked into its value. The value identifies the name of the proxy server that the request passed through. What if we could tell if a user was internal or external based upon which proxy server they came through?

With this change, internal OWA users will land on internal ADFS Proxy servers and external OWA users will land on external ADFS Proxy servers. That will allow us to add a rule like this:

Line 1: User is coming through an ADFS Proxy and that proxy has a name that matches ADFSP##

Line 2: User is in the specified group

Line 3: User is hitting the passive endpoint

Line 4: Deny the claim

If a user in the specified group presents a claim to ADFS from outside the network, all elements of this rule will be true and the claim will be denied. If the same user is inside the network and is using one of the internal proxies, line 1 will be false (the proxy name will not match ADFSP##) and the claim will be allowed.

For illustration purposes, we can express the same thing in a slightly different way: