Java EE authorization - JACC revisited part I

A while ago we took a look at container authorization in Java EE, which we saw was taken care of by a specification called JACC.

We saw that JACC offered a clear standardized hook into what's often seen as a completely opaque and container specific process, but that it also had a number of disadvantages. Furthermore we provided a partial (non-working) implementation of a JACC provider to illustrate the idea.

In this part of the article we'll revisit JACC by taking a closer look at some of the mentioned disadvantages and dive a little deeper in the concept of role mapping. In part II we'll be looking at the first element of a more complete implementation of the JACC provider that was shown before.

To refresh our memory, the following were the disadvantages that we previously discovered:

  • Arcane & verbose API
  • No portable way to see what the groups/roles are in a collection of Principals
  • No portable way to use the container's role to group mapper
  • No default implementation of a JACC provider active or even available
  • Mixing Java SE and EE permissions (which protect against totally different things) when security manager is used
  • JACC provider has to be installed for the entire AS; can not be registered from or for a single application

As it later on appeared though, there's a little more to say about a few of these items.

Role mapping

While it's indeed the case that there's no portable way to get to either the groups or the container's role to group mapper, it appeared there was something called the primary use case for which JACC was originally conceived.

For this primary use case the idea was that a custom JACC provider would be coupled with a (custom) authentication module that only provided a caller principal (which contains the user name). That JACC provider would then contact an (external) authorization system to fetch authorization data based on this single caller principal. This authorization data can then be a collection of roles or anything that the JACC provider can either locally map to roles, or something to which it can map the permissions that a PolicyConfiguration initially collects. For this use case it's indeed not necessary to have portable access to groups or a role to groups mapper.

Building on this primary use case, it also appears that JASPIC auth modules in fact do have a means to put a specific implementation of a caller principal into the subject. JASPIC being JASPIC with its bare minimum of TCK tests this of course didn't work on all containers and there's still a gap present where the container is allowed to "map" that principal (whatever this means), but the basic idea is there. A JACC provider that knows about the auth module being used can then unambiguously pick out the caller principal from the set of principals in a subject. All of this would be so much simpler though if the caller principal was simply standardized in the first place, but alas.

To illustrate the basic process for a custom JACC provider according to this primary use case:

Auth module  ——provides——►  Caller Principal (name = "someuser")

JACC provider  ——contacts—with—"someuser"——►  Authorization System

Authorization System  ——returns——►  roles ["admin", "architect"] 

JACC provider  ——indexes—with—"admin"——►  rolesToPermissions
JACC provider  ——indexes with—"architect"——►  rolesToPermissions

As can be seen above there is no need for role mapping in this primary use case.

For the default implementation of a proprietary JACC provider that ships with a Java EE container the basic process is a little bit different as shown next:

role to group mapping in place
RoleGroups
"admin"["admin-group"]
"architect"["architect-group"]
"expert"["expert-group"]
JACC provider ——calls—with—["admin", "architect", "expert"] ——► Role Mapper Role mapper ——returns——► ["admin-group", "architect-group", "expert-group"] Auth module ——provides——► Caller Principal (name = "someuser") Auth module ——provides——► Group Principal (name = "admin-group", name = "architect-group") JACC provider maps "admin-group" to "admin" JACC provider maps "architect-group "to "architect" JACC provider ——indexes—with—"admin"——► rolesToPermissions JACC provider ——indexes—with—"architect"——► rolesToPermissions

In the second use case the role mapper and possibly knowledge of which principals represent groups is needed, but since this JACC provider is the one that ships with a Java EE container it's arguably "allowed" to use proprietary techniques.

Do note that the mapping technique shown maps a subject's groups to roles, and uses that to check permissions. While this may conceptually be the most straightforward approach, it's not the only way.

Groups to permission mapping

An alternative approach is to remap the roles-to-permission collection to a groups-to-permission collection using the information from the role mapper. This is what both GlassFish and WebLogic implicitly do when they write out their granted.policy file.

The following is an illustration of this process. Suppose we have a role to permissions map as shown in the following table:

Role-to-permissions
RolePermission
"admin"[WebResourcePermission ("/protected/*" GET)]

This means a user that's in the logical application role "admin" is allowed to do a GET request for resources in the /protected folder. Now suppose the role mapper gave us the following role to group mapping:

Role-to-groups
RoleGroups
"admin"["admin-group", "adm"]

This means the logical application role "admin" is mapped to the groups "admin-group" and "adm". What we can now do is first reverse the last mapping into a group-to-roles map as shown in the following table:

Group-to-roles
GroupRoles
"admin-group"["admin"]
"adm"["admin"]

Subsequently we can then iterate over this new map and look up the permissions associated with each role in the existing role to permissions map to create our target group to permissions map. This is shown in the table below:

Group-to-permissions
GroupPermissions
"admin-group"[WebResourcePermission ("/protected/*" GET)]
"adm"[WebResourcePermission ("/protected/*" GET)]

Finally, consider a current subject with principals as shown in the next table:

Subject's principals
TypeName
com.somevendor.CallerPrincipalImpl"someuser"
com.somevendor.GroupPrincipalImpl"admin-group"
com.somevendor.GroupPrincipalImpl"architect-group"

Given the above shown group to permissions map and subject's principals, a JACC provider can now iterate over the group principals that belong to this subject and via the map check each such group against the permissions for that group. Note that the JACC provider does have to know that com.somevendor.GroupPrincipalImpl is the principal type that represents groups.

Principal to permission mapping

Yet another alternative approach is to remap the roles-to-permission collection to a principals-to-permission collection, again using the information from the role mapper. This is what both Geronimo and GlassFish' optional SimplePolicyProvider do.

Principal to permission mapping basically works like group to permission mapping, except that the JACC provider doesn't need to have knowledge of the principals involved. For the JACC provider those principals are pretty much opaque then, and it doesn't matter if they represent groups, callers, or something else entirely. All the JACC provider does is compare (using equals() or implies()) principals in the map against those in the subject.

The following code fragment taken from Geronimo 3.0.1 demonstrates the mapping algorithm:

for (Map.Entry<Principal, Set<String>> principalEntry : principalRoleMapping.entrySet()) {
    Principal principal = principalEntry.getKey();
    Permissions principalPermissions = principalPermissionsMap.get(principal);

    if (principalPermissions == null) {
        principalPermissions = new Permissions();
        principalPermissionsMap.put(principal, principalPermissions);
    }

    Set<String> roleSet = principalEntry.getValue();
    for (String role : roleSet) {
        Permissions permissions = rolePermissionsMap.get(role);
        if (permissions == null) continue;
        for (Enumeration<Permission> rolePermissions = permissions.elements(); rolePermissions.hasMoreElements();) {
            principalPermissions.add(rolePermissions.nextElement());
        }
    }

}

In the code fragment above rolePermissions is the map the provider created before the mapping, principalRoleMapping is the mapping from the role mapper and principalPermissions is the final map that's used for access decisions.

Default JACC provider

Several full Java EE implementations do not ship with an activated JACC provider, which makes it extremely troublesome for portable Java EE applications to just make use of JACC for things like asking if a user will be allowed to access say a URL.

As it appears, Java EE implementations are actually required to ship with an activated JACC provider and are even required to use it for access decisions. Clearly there's no TCK test for this, so just as we saw with JASPIC, vendors take different approaches in absence of such test. In the end it doesn't matter so much what the spec says, as it's the TCK that has the final word on compatibility certification. In this case, the TCK clearly says it's NOT required, while as mentioned the spec says it is. Why both JASPIC and JACC have historically tested so little is still not entirely clear, but I have it on good authority (no pun ;)) that the situation is going to be improved.

So while this is theoretically not a spec issue, it is still very much a practical issue. I looked at 6 Java EE implementations and found the following:

JACC default providers
ServerJACC provider presentJACC provider activatedVendor discourages to activate JACC
JBoss EAP 6.3VVX
GlassFish 4.1VVX
Geronimo 3.0.1VVX
WebLogic 12.1.3VXV
JEUS 8 previewVXV
WebSphere 8.5XX- (no provider present so nothing to discourage)

As can be seen only half of the servers investigated have JACC actually enabled. WebLogic 12.1.3 and JEUS 8 preview both do ship with a JACC policy provider, but it has to be enabled explicitly. Both WebLogic and JEUS 8 in their documentation somewhat advice against using JACC. TMaxSoft warns in its JEUS 7 security manual (there's not one for JEUS 8 yet) that the default JACC provider that will be activated is mainly for testing and doesn't advise to use it for real production usage.

WebSphere does not even ship with any default JACC policy provider, at least not that I could find. There's only a Tivoli Access Manager client, for which you have to install a separate external authorization server.

I haven't yet investigated Interstage AS, Cosminexus and WebOTX, but I hope to be able to look at them at a later stage.

Conclusion

Given the historical background of JACC it's a little bit more understandable why access to the role mapper was never standardized. Still, it is something that's needed for other use cases than the historical primary use case, so after all this time is still something that would be welcome to have. Another huge disadvantage of JACC, the fact that it's simply not always there in Java EE, appeared to be yet another case of incomplete TCK coverage.

Continue reading at part II.

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

What’s new in Jakarta Security 3?

Jakarta EE Survey 2022