Greetings,
Background: I have been assigned the task of evaluating the suitability of WSO2
Identity Server for use by some existing products, as a replacement for our own
custom security back-ends. The motivation for this effort has been parallel
development of two pieces of new software by different teams which each chose a
different security route – one has already been built on WSO2 but is using only
the built-in stores and has only a simple user model – users either have admin
privilege or they do not. This product is at the demo stage but not yet
available to customers. The other, slightly older software chose to fork an
existing security back-end that I originally wrote for an even older, legacy
product. This particular product is currently deployed with customers and needs
very fine-grained security, since it controls access to scarce and sensitive
test devices.
These two teams now have to merge their products onto a common platform within
about the next six months, and are debating which route to go for security –
Team A worries WSO2 doesn’t fit their needs based on seeing only the limited
security granularity in the demo product from Team B, and Team B claims WSO2
will meet all of Team A’s needs if configured properly. The manager over both
of those teams requested my help in researching the decision – I have no
personal involvement with either project. That manager wanted me to weigh in
simply because I’ve a long history of either building or fixing the security
implementations behind several different products spanning many technologies.
My direct manager agreed to loan me for this evaluation, but set me a strict
limit of 12 hours effort. I've reached that limit. I do, however, have someone
else I can hand further work and investigation off to if I have learned enough.
Problem: I have downloaded WSO2 Identity Server 5.4.1 and configured it to use
ReadOnlyLDAPUserStoreManager, pointing this at an OpenLDAP server I have
configured to model the LDAP repository of one of our most important customers.
I maintain a user store to model each of our major customers that don’t just
use our own, built-in proprietary user store. The WSO2 configuration was quite
easy and “mostly” works, except I am not able to enumerate groups. I’ve tracked
this down to an idiosyncrasy of this customer’s LDAP data combined with what I
consider to be a bug in the WSO2 ReadOnlyLDAPUserStoreManager implementation.
This customer assigns two common name values to each of their LDAP groups – one
of these is unique within the search base, but the other is not. They’re
essentially “tagging” groups as being one of two classes – either admins or
plain users. This must be done for some other vendor’s application – none of
ours look at these at all. So, for example, a group might have both
CN=northeast-managers *and* CN=category-sysadmin, where “northeast-managers” is
unique and CN=northeast-managers is part of the DN for the group.
Our own existing LDAP back-end honors a setting very similar to WSO2’s
GroupDNPattern. If present, when enumerating the list of attribute values
returned for the naming attribute (equivalent to WSO2’s GroupNameAttribute), we
substitute the group name value into the pattern(s) and throw out any that
don’t result in a match for the DN of the group. In this way, we pick the
“correct” CN value, since LDAP returns these things in some sort of random
order. WSO2-IS doesn’t do this, though it appears to employ a similar technique
in other places. Thus, I consider this a bug/oversight. Consider the following
code:
https://github.com/wso2/carbon-kernel/blob/4.4.x/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/ReadOnlyLDAPUserStoreManager.java#L1401
On that line of code, WSO2-IS is fetching only a *single* attribute value for
the “roleNameProperty”, when there may be multiple. In the customer's existing
LDAP, more often than not, their category- groups are being returned as this
single value, instead of the one that actually is unique and matches the DN. I
believe that WSO2 should be calling .getAll() here rather than .get(), and then
should iterate over these attribute values. For each value, it should attempt
to substitute that potential role name into the GroupDNPattern(s) (if the
property has been defined) and then ensure that the result matches the DN of
the SearchResult, (sr). When a match is found, add the role name, but
otherwise, discard it.
I’m basically out of time for my evaluation at this point. I’d like to go ahead
and recommend WSO2 – I have no particular affinity for our own proprietary
security code (even though I wrote this particular part of it) and think that
an actively-maintained code base is likely to be the safer bet in the long run.
However, this particular issue would stymie deployment at the very first
customer it would go into – and we’re in no position to demand that they
simplify their