Bladeren bron

HADOOP-14465. LdapGroupsMapping - support user and group search base. Contributed by Shwetha G S and Mingliang Liu

Mingliang Liu 8 jaren geleden
bovenliggende
commit
a2121cb0d9

+ 34 - 7
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/LdapGroupsMapping.java

@@ -130,6 +130,19 @@ public class LdapGroupsMapping
   public static final String BASE_DN_KEY = LDAP_CONFIG_PREFIX + ".base";
   public static final String BASE_DN_DEFAULT = "";
 
+  /*
+   * Base DN used in user search.
+   */
+  public static final String USER_BASE_DN_KEY =
+          LDAP_CONFIG_PREFIX + ".userbase";
+
+  /*
+   * Base DN used in group search.
+   */
+  public static final String GROUP_BASE_DN_KEY =
+          LDAP_CONFIG_PREFIX + ".groupbase";
+
+
   /*
    * Any additional filters to apply when searching for users
    */
@@ -200,7 +213,7 @@ public class LdapGroupsMapping
 
   private static final Log LOG = LogFactory.getLog(LdapGroupsMapping.class);
 
-  private static final SearchControls SEARCH_CONTROLS = new SearchControls();
+  static final SearchControls SEARCH_CONTROLS = new SearchControls();
   static {
     SEARCH_CONTROLS.setSearchScope(SearchControls.SUBTREE_SCOPE);
   }
@@ -214,7 +227,8 @@ public class LdapGroupsMapping
   private String keystorePass;
   private String bindUser;
   private String bindPassword;
-  private String baseDN;
+  private String userbaseDN;
+  private String groupbaseDN;
   private String groupSearchFilter;
   private String userSearchFilter;
   private String memberOfAttr;
@@ -315,7 +329,7 @@ public class LdapGroupsMapping
       uidNumber = uidAttribute.get().toString();
     }
     if (uidNumber != null && gidNumber != null) {
-      return c.search(baseDN,
+      return c.search(groupbaseDN,
               "(&"+ groupSearchFilter + "(|(" + posixGidAttr + "={0})" +
                   "(" + groupMemberAttr + "={1})))",
               new Object[] {gidNumber, uidNumber},
@@ -350,7 +364,7 @@ public class LdapGroupsMapping
     } else {
       String userDn = result.getNameInNamespace();
       groupResults =
-          c.search(baseDN,
+          c.search(groupbaseDN,
               "(&" + groupSearchFilter + "(" + groupMemberAttr + "={0}))",
               new Object[]{userDn},
               SEARCH_CONTROLS);
@@ -391,7 +405,7 @@ public class LdapGroupsMapping
     DirContext c = getDirContext();
 
     // Search for the user. We'll only ever need to look at the first result
-    NamingEnumeration<SearchResult> results = c.search(baseDN,
+    NamingEnumeration<SearchResult> results = c.search(userbaseDN,
         userSearchFilter, new Object[]{user}, SEARCH_CONTROLS);
     // return empty list if the user can not be found.
     if (!results.hasMoreElements()) {
@@ -489,7 +503,7 @@ public class LdapGroupsMapping
     filter.append("))");
     LOG.debug("Ldap group query string: " + filter.toString());
     NamingEnumeration<SearchResult> groupResults =
-        context.search(baseDN,
+        context.search(groupbaseDN,
            filter.toString(),
            SEARCH_CONTROLS);
     while (groupResults.hasMoreElements()) {
@@ -575,7 +589,20 @@ public class LdapGroupsMapping
           conf.get(BIND_PASSWORD_FILE_KEY, BIND_PASSWORD_FILE_DEFAULT));
     }
     
-    baseDN = conf.get(BASE_DN_KEY, BASE_DN_DEFAULT);
+    String baseDN = conf.getTrimmed(BASE_DN_KEY, BASE_DN_DEFAULT);
+
+    //User search base which defaults to base dn.
+    userbaseDN = conf.getTrimmed(USER_BASE_DN_KEY, baseDN);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Usersearch baseDN: " + userbaseDN);
+    }
+
+    //Group search base which defaults to base dn.
+    groupbaseDN = conf.getTrimmed(GROUP_BASE_DN_KEY, baseDN);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Groupsearch baseDN: " + userbaseDN);
+    }
+
     groupSearchFilter =
         conf.get(GROUP_SEARCH_FILTER_KEY, GROUP_SEARCH_FILTER_DEFAULT);
     userSearchFilter =

+ 20 - 0
hadoop-common-project/hadoop-common/src/main/resources/core-default.xml

@@ -346,6 +346,26 @@
   </description>
 </property>
 
+<property>
+  <name>hadoop.security.group.mapping.ldap.userbase</name>
+  <value></value>
+  <description>
+    The search base for the LDAP connection for user search query. This is a
+    distinguished name, and its the root of the LDAP directory for users.
+    If not set, hadoop.security.group.mapping.ldap.base is used.
+  </description>
+</property>
+
+<property>
+  <name>hadoop.security.group.mapping.ldap.groupbase</name>
+  <value></value>
+  <description>
+    The search base for the LDAP connection for group search . This is a
+    distinguished name, and its the root of the LDAP directory for groups.
+    If not set, hadoop.security.group.mapping.ldap.base is used.
+  </description>
+</property>
+
 <property>
   <name>hadoop.security.group.mapping.ldap.search.filter.user</name>
   <value>(&amp;(objectClass=user)(sAMAccountName={0}))</value>

+ 1 - 0
hadoop-common-project/hadoop-common/src/site/markdown/GroupsMapping.md

@@ -76,6 +76,7 @@ This provider supports LDAP with simple password authentication using JNDI API.
 `hadoop.security.group.mapping.ldap.url` must be set. This refers to the URL of the LDAP server for resolving user groups.
 
 `hadoop.security.group.mapping.ldap.base` configures the search base for the LDAP connection. This is a distinguished name, and will typically be the root of the LDAP directory.
+Get groups for a given username first looks up the user and then looks up the groups for the user result. If the directory setup has different user and group search bases, use `hadoop.security.group.mapping.ldap.userbase` and `hadoop.security.group.mapping.ldap.groupbase` configs.
 
 If the LDAP server does not support anonymous binds,
 set the distinguished name of the user to bind in `hadoop.security.group.mapping.ldap.bind.user`.

+ 63 - 1
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestLdapGroupsMapping.java

@@ -80,10 +80,12 @@ public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
   private static final byte[] AUTHENTICATE_SUCCESS_MSG =
       {48, 12, 2, 1, 1, 97, 7, 10, 1, 0, 4, 0, 4, 0};
 
+  private final String userDN = "CN=some_user,DC=test,DC=com";
+
   @Before
   public void setupMocks() throws NamingException {
     when(getUserSearchResult().getNameInNamespace()).
-        thenReturn("CN=some_user,DC=test,DC=com");
+        thenReturn(userDN);
   }
   
   @Test
@@ -96,6 +98,66 @@ public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
     doTestGetGroups(Arrays.asList(getTestGroups()), 2);
   }
 
+  @Test
+  public void testGetGroupsWithDifferentBaseDNs() throws Exception {
+    Configuration conf = new Configuration();
+    // Set this, so we don't throw an exception
+    conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test");
+    String userBaseDN = "ou=Users,dc=xxx,dc=com ";
+    String groupBaseDN = " ou=Groups,dc=xxx,dc=com";
+    conf.set(LdapGroupsMapping.USER_BASE_DN_KEY, userBaseDN);
+    conf.set(LdapGroupsMapping.GROUP_BASE_DN_KEY, groupBaseDN);
+
+    doTestGetGroupsWithBaseDN(conf, userBaseDN.trim(), groupBaseDN.trim());
+  }
+
+  @Test
+  public void testGetGroupsWithDefaultBaseDN() throws Exception {
+    Configuration conf = new Configuration();
+    // Set this, so we don't throw an exception
+    conf.set(LdapGroupsMapping.LDAP_URL_KEY, "ldap://test");
+    String baseDN = " dc=xxx,dc=com ";
+    conf.set(LdapGroupsMapping.BASE_DN_KEY, baseDN);
+    doTestGetGroupsWithBaseDN(conf, baseDN.trim(), baseDN.trim());
+  }
+
+  /**
+   * Helper method to do the LDAP getGroups operation using given user base DN
+   * and group base DN.
+   * @param conf The created configuration
+   * @param userBaseDN user base DN
+   * @param groupBaseDN group base DN
+   * @throws NamingException if error happens when getting groups
+   */
+  private void doTestGetGroupsWithBaseDN(Configuration conf, String userBaseDN,
+      String groupBaseDN) throws NamingException {
+    final LdapGroupsMapping groupsMapping = getGroupsMapping();
+    groupsMapping.setConf(conf);
+
+    final String userName = "some_user";
+
+    // The search functionality of the mock context is reused, so we will
+    // return the user NamingEnumeration first, and then the group
+    when(getContext().search(anyString(), anyString(), any(Object[].class),
+        any(SearchControls.class)))
+        .thenReturn(getUserNames(), getGroupNames());
+
+    List<String> groups = groupsMapping.getGroups(userName);
+    Assert.assertEquals(Arrays.asList(getTestGroups()), groups);
+
+    // We should have searched for the username and groups with default base dn
+    verify(getContext(), times(1)).search(userBaseDN,
+        LdapGroupsMapping.USER_SEARCH_FILTER_DEFAULT,
+        new Object[]{userName},
+        LdapGroupsMapping.SEARCH_CONTROLS);
+
+    verify(getContext(), times(1)).search(groupBaseDN,
+        "(&" + LdapGroupsMapping.GROUP_SEARCH_FILTER_DEFAULT + "(" +
+            LdapGroupsMapping.GROUP_MEMBERSHIP_ATTR_DEFAULT + "={0}))",
+        new Object[]{userDN},
+        LdapGroupsMapping.SEARCH_CONTROLS);
+  }
+
   @Test
   public void testGetGroupsWithHierarchy() throws IOException, NamingException {
     // The search functionality of the mock context is reused, so we will