Wednesday, February 17, 2016

Migrating OAM Policies to OpenAM

The policy enforcement models differ significantly between Oracle Access Manager 10g and OpenAM. To be clear, I'm referring only to using reverse proxy web gates (OAM's term) and agents (OpenAM's term) that protect access via policies to back-end application servers. In this post I'll start outlining how to migrate policies from OAM 10g to OpenAM. And for the record, i'm going to admit to some conceptual mistakes in this post and some may say, "Hey, why didn't you read the documentation?" And the answer is, I did.

Finding a Policy for a Request

Lets first discuss how a policy or policies are found to determine access for a given inbound request.

The OAM 10g Model

OAM uses policy domains that are containers of policies and have associated host identifier objects that can also have aliases and can include a port. And policy domains have URL prefixes that are the base for the relative URLs in the policies that are contained in the policy domain object. And policies themselves have separate fields for relative URL, query string, and query parameter of which all, some, or just one can be specified. And there is a rich set of meta character patterns as well. Furthermore, a policy has an authentication scheme of which we only used two types; one that required authentication and one that didn't, an anonymous one which was the root of my first big mistake in the plan for migrating our policies. All of these policy aspects combine to define the URL space that a policy domain object and its policies protect and the authentication state that is required.

When finding a policy for a given request URL, OAM first looks for policy domains that match the host and then looks for the most specific of these policy domains by taking the one having the longest prefix that matches the request's URL At that point only URLs of policies within that policy domain container will be consulted for one that matches. And OAM allows you to order those policies. And the first policy to match the request's URL is the only policy that will then be used to either allow or deny access.

With that background lets now look at the corresponding parts of OpenAM's model and how my understanding of OAM's model initially tripped me up on my first approach to migrating our policies  into OpenAM. Hopefully that can help someone avoid the same mistakes.

The OpenAM 12.X Model 

Again to be clear, I am discussing only OpenAM version 12.X and believe that there are no changes to the policy model in version 13.X save for moving from multiple resource URLs per policy to a single resource URL per policy. So for migration we chose to have a single resource URL per policy in OpenAM so that we would have no issues when updating to 13.

OpenAM uses application objects as containers of policies. These have base URLs to which policy resource URLs must be relative. And where OAM has three fields that combine for the policy's URL space OpenAM only provides the resource URL. And OAM implicitly applies policies to requests with and without queries subject to any query string and query parameters if specified while OpenAM must have two separate policies for the exact same URL if we want both the query-less request and the same request having query strings to make it through. With this requirement alone we anticipate that our number of policies will double at a minimum. But I'll outline some mitigation mechanisms in a follow-up post.

Oh, and by the way, if you are using the very cool rest endpoints in OpenAM to manage your policies you'll find that resource URLs are not relative. They are absolute. So the "relative" nature may solely be a UI enforced feature. Have I tried setting a policy's resource URL to something that isn't relative to its containing application object? I have not. Our tool for migrating just honors that contract.

And finally, before discussing finding a policy in OpenAM for a given request URL I need to explain a minor aspect of OpenAM's Web Agents. They can only point to a single OpenAM application object. Since we have a single cluster of reverse proxy agents that share identical configuration via an agent group this means that all of our policies must be contained in a single OpenAM application object. That is very important.

Finding a policy for a given request URL first looks for all policies in the application object pointed to by the web agent which happens to be all of them in our case so no narrowing there. I'm hoping we can change that at some point in a straightforward, backwards compatible way as I'll explain later. Next, it finds all policies whose resource URLs match the request's URL. All of them. Not just the first since OpenAM doesn't support ordering of policies. And access is only allowed if all policies allow the request. But it turns out that statement isn't accurate enough and this is where I made a second huge mistake.

Policy Matching

In OAM the identified policy either allows access or denies access based upon its authorization expression that I won't go into. Actually, the result of evaluating the expression can be either allow, deny, or inconclusive and can each be handled differently. But for us, inconclusive meant deny and we treated it as such.

Things are different in OpenAM. I assumed that once the set of policies were identified that matched the request's URL that access was allowed or denied based upon two additional aspects of OpenAM policies that seemed to be correlated to the authorization expression concept in OAM; namely subjects and conditions.

Subjects that are supported natively include Authenticated Users, Never Match (what good is that???), users and groups, jwt claims, and logical combinators NOT, AND, and OR. And that brings me to my first big mistake. Authenticated Users mapped nicely to OAM's login scheme mechanism. But where was anonymous access. Then I found in one piece of documentation that a NOT wrapping a Never Match would cause the policy to always match which I assumed was my elusive anonymous access. But it wasn't.

In OpenAM's policy engine there is no concept of an unauthenticated user. You only get to policy evaluation if you are already authenticated even if it is with what is known as the "anonymous" module that doesn't prompt for any user credentials. The key here is OpenAM's powerful concept of authentication levels and how you map them to mean different things such as 0 is an authenticated but anonymous user while 10 is a user who authenticated with a username and password and 20 is a user who used a 2nd factor mechanism.

That is why the unenforced url list is part of agent configuration and not policy configuration. If a request's URL matches on the unenforced url list then the agent lets it through. If it isn't, the agent redirects to the login page and only after you've authenticated and been redirected back to the original request URL will the agent then confer with the policy engine to find matching policies to apply to the request.

Now for conditions. Conditions add further requirements beyond the subject. A policy doesn't have to have any conditions in which case only the subject is considered. And this brings me to that second mistake. I assumed that if the subject or condition, if included, didn't apply to the current user that the policy denied access. And remember that if one policy denies access then the request is denied.

And this is totally wrong and why this section is titled Policy Matching. The subject and condition aspects are not used to deny and allow access. They are part of the matching to winnow down further the list of policies that apply to the request. If the user doesn't meet the requirements of the subject and condition, if included, that policy is simply dropped from the candidate list of policies to be applied to the request. And that brings us to one final aspect of policies.

Policy Actions

OAM provided a list of Http methods with check boxes that indicated if a policy applied to GET, POST, PUT, etc. OpenAM provides a similar list called actions also with a checkbox for each. But it also provides a pair of radio buttons with one for Allow and one for Deny. Only when the policies have been further winnowed down via subjects and conditions does OpenAM then attempt to allow or deny a request. And it does so by taking the http method of the request and looking to see which of the remaining policies in the list included that action by checking that action's check box. For each of those it then sees if that policy has Allow or Deny selected. If any have Deny selected then the request is denied. If all have Allow selected then the request is allowed.

How am I certain about all of this? I crafted a number of test scenarios with related policies, subjects, conditions, and related users with specific characteristics that would meet or fail those conditions and tested each with the policy evaluation rest endpoint to obtain the conclusive results. I'll cover these in my next post.


No comments:

Post a Comment