Sandor Molnar created KNOX-3558:
-----------------------------------

             Summary: LDAP proxy role memberOf values are dropped by the 
KnoxIDF LdapUserParamsProvider, so /token fails on a role listing call
                 Key: KNOX-3558
                 URL: https://issues.apache.org/jira/browse/KNOX-3558
             Project: Apache Knox
          Issue Type: Bug
          Components: JWT, KnoxIDF
            Reporter: Sandor Molnar
            Assignee: Sandor Molnar
             Fix For: 3.1.0


h3. Problem
The KnoxIDF token endpoint, with the {{roles}} scope, returns 500 ("Failed to 
fetch user parameters for <user>"). This happens when 
{{LdapUserParamsProvider}} points at the Knox LDAP proxy, the REST roles lookup 
is enabled, and the user has no backend LDAP groups.

# {{LdapUserParamsProvider.getParamsFor}} reads the user entry, asking for 
{{memberOf}}.
#* {{LDAPRolesLookupInterceptor.handleSearch}} gets the user's roles from 
{{POST .../roles/lookup}} (HTTP 200, 5 roles) and writes them into {{memberOf}}.
#* The user has no backend groups, so there is no template DN, and 
{{addRoleAttribute}} writes each role as a bare {{cn=<role>}} with no parent DN.
# {{rolesFromMemberOf}} only accepts direct {{cn=}} children of 
{{ou=groups,<baseDn>}}, so it drops every role.
# The provider then falls back to {{fetchRoles}}: a one-level search of 
{{ou=groups,<baseDn>}} with {{(objectClass=groupOfNames)}}, filtering 
{{member}} on the client side.
#* {{isAllGroupsSearch}} treats this as "list all roles" and calls 
{{listRoles}}, i.e. {{GET .../roles?offset=0}}. That pages through every role 
in the system to enrich one user.
#* The roles backend refuses the listing (HTTP 403). The interceptor throws 
"Failed to retrieve roles from roles lookup service.", the LDAP search fails, 
and {{/token}} returns 500.

{noformat}
RestApiLdapRolesLookup - Sending role lookup request to 
http://awc-auth/api/v1/auth/roles/lookup: {"user_id":"admin","groups":[]}
RestApiLdapRolesLookup - Received role lookup response ...: HTTP 200, body: 
{"user_id":"admin","roles":[{"scope":"global","name":"temporary-local-admin"}, 
...]}
RestApiLdapRolesReverseLookup - Sending role list request to 
http://awc-auth/api/v1/auth/roles?offset=0
RestApiLdapRolesReverseLookup - Role list request ... failed with HTTP 403
Caused by: java.lang.RuntimeException: Failed to fetch user parameters for admin
    at 
org.apache.knox.gateway.service.knoxidf.userparams.LdapUserParamsProvider.getParamsFor(LdapUserParamsProvider.java:170)
{noformat}

When the user does have backend groups, each role DN is the first backend 
group's DN with its {{cn}} renamed, e.g. {{cn=<role>,ou=<backend OU>,...}}. 
That DN doesn't match the role entries the proxy publishes for an all-groups 
search, or the DN {{ReverseRoleLookupMappingVisitor}} expects in {{memberOf=}} 
filters. If it isn't directly under {{ou=groups,<baseDn>}}, the provider drops 
it too.

h3. Proposed fix
*LDAP proxy ({{LDAPRolesLookupInterceptor}})*
* Always write each role in {{memberOf}} as {{cn=<role>,ou=groups,<baseDn>}} 
(i.e. {{createGroupDnFromName(role)}}), instead of the bare {{cn=<role>}} or a 
renamed backend group DN.
* This is the same DN that {{handleAllGroupsSearch}} uses for role entries and 
that the reverse lookup expects. A user's {{memberOf}} then names entries the 
proxy actually serves, and the value can be fed back into a {{memberOf=}} 
filter.
* Not configurable. All three are derived from {{ou=groups,<baseDn>}}, so a 
separate setting for {{memberOf}} would only let them disagree. If a different 
groups base is ever needed, make that base itself configurable.
* The role is still the {{cn}}, so clients that take the group name from 
{{memberOf}}'s {{cn}} (e.g. Hadoop {{LdapGroupsMapping}}) are not affected.

*KnoxIDF ({{LdapUserParamsProvider}})*
* New topology param {{user.params.provider.ldap.groupSearchBase}}, defaulting 
to {{ou=groups,<baseDn>}}, which is the DN the proxy now writes.
** Direct {{cn=}} children of it in {{memberOf}} are taken as roles. This lets 
directories whose groups live elsewhere (e.g. {{ou=Groups}} under another OU) 
work without changing {{baseDn}}.
** The {{fetchRoles}} fallback searches this base too, instead of the hardcoded 
{{"ou=groups," + baseDn}}.
* {{fetchRoles}} searches only the user's own groups with 
{{(&(objectClass=groupOfNames)(member=\{0\}))}}, passing the user DN as a 
filter argument so JNDI escapes it. It no longer lists every group and filters 
on the client side.
** On the proxy this is not an all-groups search, so it no longer turns into 
{{listRoles}}. A user with no roles gets an empty {{roles}} claim instead of a 
failed {{/token}}.
** On a plain LDAP server it returns the same groups without reading all of 
them.

h3. Tests
* {{LDAPRolesLookupInterceptorTest}}:
** a user without groups gets {{memberOf: cn=<role>,ou=groups,<baseDn>}};
** a user with groups gets the same form, not a renamed backend group DN.
* {{LdapUserParamsProviderTest}}:
** the default {{groupSearchBase}} accepts the proxy's {{memberOf}} values 
without a fallback search;
** a custom {{groupSearchBase}} accepts its own children and rejects 
{{ou=groups,<baseDn>}};
** the fallback uses the per-user {{member}} filter on {{groupSearchBase}}.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to