You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hive.apache.org by gs...@apache.org on 2023/05/12 17:14:40 UTC
[hive] branch master updated: HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)
This is an automated email from the ASF dual-hosted git repository.
gsaihemanth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new dd3fd775406 HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)
dd3fd775406 is described below
commit dd3fd775406c2d621c42ef1adce2fa0f3b9acd39
Author: Naveen Gangam <ng...@cloudera.com>
AuthorDate: Fri May 12 13:14:27 2023 -0400
HIVE-27311: Support generic search bind filter auth with LDAP (#4284) (Naveen Gangam, Reviewed by Henri Biestro, Sai Hemanth G, Zhihua Deng)
---
.../java/org/apache/hadoop/hive/conf/HiveConf.java | 10 ++
.../auth/LdapAuthenticationProviderImpl.java | 4 +-
.../apache/hive/service/auth/ldap/DirSearch.java | 21 ++++
.../apache/hive/service/auth/ldap/LdapSearch.java | 32 ++++++
.../hive/service/auth/ldap/QueryFactory.java | 32 ++++++
.../auth/ldap/UserGroupSearchFilterFactory.java | 105 ++++++++++++++++++++
.../auth/TestLdapAtnProviderWithMiniDS.java | 62 ++++++++++++
.../auth/ldap/LdapAuthenticationTestCase.java | 15 +++
.../MetaStoreLdapAuthenticationProviderImpl.java | 2 +
.../hadoop/hive/metastore/conf/MetastoreConf.java | 13 +++
.../hadoop/hive/metastore/ldap/DirSearch.java | 21 ++++
.../hadoop/hive/metastore/ldap/LdapSearch.java | 31 ++++++
.../hadoop/hive/metastore/ldap/QueryFactory.java | 32 ++++++
.../ldap/UserGroupSearchFilterFactory.java | 108 +++++++++++++++++++++
.../metastore/TestLdapAtnProviderWithMiniDS.java | 63 ++++++++++++
.../metastore/ldap/LdapAuthenticationTestCase.java | 15 +++
16 files changed, 565 insertions(+), 1 deletion(-)
diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
index a1bae8b0d34..676b2fe417e 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java
@@ -4252,6 +4252,16 @@ public class HiveConf extends Configuration {
"For example: (&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*)) \n" +
"(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" +
"(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))"),
+ HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER("hive.server2.authentication.ldap.userSearchFilter", null,
+ "User search filter to be used with baseDN to search for users\n" +
+ "For example: (&(uid={0})(objectClass=person))"),
+ HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN("hive.server2.authentication.ldap.groupBaseDN", null,
+ "BaseDN for Group Search. This is used in conjunction with hive.server2.authentication.ldap.baseDN\n" +
+ "and \n" +
+ "request, succeeds if the group is part of the resultset."),
+ HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER("hive.server2.authentication.ldap.groupSearchFilter", null,
+ "Group search filter to be used with baseDN, userSearchFilter, groupBaseDN to search for users in groups\n" +
+ "For example: (&(|(memberUid={0})(memberUid={1}))(objectClass=posixGroup))\n"),
HIVE_SERVER2_PLAIN_LDAP_BIND_USER("hive.server2.authentication.ldap.binddn", null,
"The user with which to bind to the LDAP server, and search for the full domain name " +
"of the user being authenticated.\n" +
diff --git a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
index 0f318fb5d85..31351b1da4a 100644
--- a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
+++ b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java
@@ -29,14 +29,15 @@ import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hive.service.ServiceUtils;
import org.apache.hive.service.auth.ldap.ChainFilterFactory;
import org.apache.hive.service.auth.ldap.CustomQueryFilterFactory;
-import org.apache.hive.service.auth.ldap.LdapSearchFactory;
import org.apache.hive.service.auth.ldap.Filter;
import org.apache.hive.service.auth.ldap.DirSearch;
import org.apache.hive.service.auth.ldap.DirSearchFactory;
import org.apache.hive.service.auth.ldap.FilterFactory;
import org.apache.hive.service.auth.ldap.GroupFilterFactory;
+import org.apache.hive.service.auth.ldap.LdapSearchFactory;
import org.apache.hive.service.auth.ldap.LdapUtils;
import org.apache.hive.service.auth.ldap.UserFilterFactory;
+import org.apache.hive.service.auth.ldap.UserGroupSearchFilterFactory;
import org.apache.hive.service.auth.ldap.UserSearchFilterFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,6 +47,7 @@ public class LdapAuthenticationProviderImpl implements PasswdAuthenticationProvi
private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProviderImpl.class);
private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
+ new UserGroupSearchFilterFactory(),
new CustomQueryFilterFactory(),
new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
new GroupFilterFactory())
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java b/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
index da8cc66b7e6..3a79b473808 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/DirSearch.java
@@ -34,6 +34,16 @@ public interface DirSearch extends Closeable {
*/
String findUserDn(String user) throws NamingException;
+ /**
+ * Finds user's distinguished name.
+ * @param user username
+ * @param userSearchFilter Generic LDAP Search filter for ex: (&(uid={0})(objectClass=person))
+ * @param baseDn LDAP BaseDN for user searches for ex: dc=apache,dc=org
+ * @return DN for the specific user if exists, null otherwise
+ * @throws NamingException
+ */
+ String findUserDn(String user, String userSearchFilter, String baseDn) throws NamingException;
+
/**
* Finds group's distinguished name.
* @param group group name or unique identifier
@@ -66,4 +76,15 @@ public interface DirSearch extends Closeable {
* @throws NamingException
*/
List<String> executeCustomQuery(String query) throws NamingException;
+
+ /**
+ * Executes an arbitrary query.
+ * @param user user RDN or username. This will be substituted for {0} in group search
+ * @param userDn userDn DN for the username. This will be substituted for {1} in group search
+ * @param filter filter is the group filter query ex: (&(memberUid={0})(&(CN=group1)(objectClass=posixGroup)))
+ * @param groupBaseDn BaseDN for group searches. ex: "ou=groups,dc=apache,dc=org"
+ * @return list of names that match the group filter aka groups that the user belongs to, if any.
+ * @throws NamingException
+ */
+ List<String> executeUserAndGroupFilterQuery(String user, String userDn, String filter, String groupBaseDn) throws NamingException;
}
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
index 9fe4a7cd51d..2b5ca940cfc 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/LdapSearch.java
@@ -24,6 +24,7 @@ import java.util.List;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.hadoop.hive.conf.HiveConf;
import org.slf4j.Logger;
@@ -104,6 +105,28 @@ public final class LdapSearch implements DirSearch {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String findUserDn(String user, final String userSearchFilter, final String baseDn) throws NamingException {
+ List<String> allLdapNames;
+ String userRdn = user;
+ if (LdapUtils.isDn(user)) {
+ userRdn = LdapUtils.extractUserName(user);
+ }
+ Query query = queries.findUserDnBySearch(userRdn, userSearchFilter);
+ allLdapNames = execute(Collections.singletonList(baseDn), query).getAllLdapNames();
+
+ if (allLdapNames.size() == 1) {
+ return allLdapNames.get(0);
+ } else {
+ LOG.info("Expected exactly one userSearchFilter result for the userSearchFilter: {}, but got {}. Returning null",
+ userSearchFilter, allLdapNames.size());
+ return null;
+ }
+ }
+
private List<String> findDnByPattern(List<String> patterns, String name) throws NamingException {
for (String pattern : patterns) {
String baseDnFromPattern = LdapUtils.extractBaseDn(pattern);
@@ -152,6 +175,15 @@ public final class LdapSearch implements DirSearch {
.getAllLdapNamesAndAttributes();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<String> executeUserAndGroupFilterQuery(String user, String userDn, String groupSearchFilter, String groupBaseDn) throws NamingException {
+ Query query = queries.findDnByUserAndGroupSearch(user, userDn, groupSearchFilter);
+ return execute(Collections.singletonList(groupBaseDn), query).getAllLdapNames();
+ }
+
private SearchResultHandler execute(Collection<String> baseDns, Query query) {
List<NamingEnumeration<SearchResult>> searchResults = new ArrayList<>();
LOG.debug("Executing a query: '{}' with base DNs {}.", query.getFilter(), baseDns);
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
index e06f1129da0..6bcbf92fe57 100644
--- a/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
+++ b/service/src/java/org/apache/hive/service/auth/ldap/QueryFactory.java
@@ -108,6 +108,38 @@ final class QueryFactory {
.build();
}
+ /**
+ * Returns a query for finding user DN based on user RDN.
+ * @param user user RDN that will replace {0} in the search filter
+ * @param userSearchFilter search query that user filter conditions.
+ * @return an instance of {@link Query}
+ */
+ public Query findUserDnBySearch(String user, String userSearchFilter) {
+ if (userSearchFilter != null) {
+ userSearchFilter = userSearchFilter.replaceAll("\\{0\\}", user);
+ }
+ return Query.builder()
+ .filter(userSearchFilter)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding matches based on user and group filter query.
+ * @param user user RDN to replace {0}
+ * @param userDn user DN to replace {1}}
+ * @param groupSearchFilter search query that includes any group filter conditions.
+ * @return an instance of {@link Query}
+ */
+ public Query findDnByUserAndGroupSearch(String user, String userDn, String groupSearchFilter) {
+ if (groupSearchFilter != null) {
+ groupSearchFilter = groupSearchFilter.replaceAll("\\{0\\}", userDn);
+ groupSearchFilter = groupSearchFilter.replaceAll("\\{1\\}", user);
+ }
+ return Query.builder()
+ .filter(groupSearchFilter)
+ .build();
+ }
+
/**
* Returns a query for finding groups to which the user belongs.
* @param userName username
diff --git a/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java b/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java
new file mode 100644
index 00000000000..30a9bc64874
--- /dev/null
+++ b/service/src/java/org/apache/hive/service/auth/ldap/UserGroupSearchFilterFactory.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hive.service.auth.ldap;
+
+import com.google.common.base.Strings;
+
+import java.util.List;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for a {@link Filter} based on user and group search filters.
+ * <br>
+ * The produced filter object filters out all users that are not found in the search result
+ * of the query provided in Hive configuration.
+ * Atleast one search criteria is REQUIRED.
+ * Configuration could have Usersearch filter or Groupsearch filter or both.
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_BASEDN
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER
+ * @see HiveConf.ConfVars#HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN
+ */
+public class UserGroupSearchFilterFactory implements FilterFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Filter getInstance(HiveConf conf) {
+ String userSearchFilter = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER.varname);
+ String userSearchBaseDN = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BASEDN.varname);
+ String groupSearchFilter = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER.varname);
+ String groupSearchBaseDN = conf.get(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN.varname);
+
+ // Both UserSearch and GroupSearch cannot be null or empty.
+ if (Strings.isNullOrEmpty(userSearchFilter) &&
+ (Strings.isNullOrEmpty(groupSearchFilter) && Strings.isNullOrEmpty(groupSearchBaseDN))) {
+ return null;
+ }
+ return new UserGroupSearchFilter(userSearchFilter, userSearchBaseDN, groupSearchFilter, groupSearchBaseDN);
+ }
+
+ private static final class UserGroupSearchFilter implements Filter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UserGroupSearchFilter.class);
+
+ private final String userSearchFilter;
+ private final String userBaseDN;
+ private final String groupSearchFilter;
+ private final String groupBaseDN;
+
+ UserGroupSearchFilter(String userSearchFilter, String userBaseDN, String groupSearchFilter, String groupBaseDN) {
+ this.userSearchFilter = userSearchFilter;
+ this.userBaseDN = userBaseDN;
+ this.groupSearchFilter = groupSearchFilter;
+ this.groupBaseDN = groupBaseDN;
+ }
+
+ @Override public void apply(DirSearch client, String user) throws AuthenticationException {
+ String userDn = null;
+ List<String> resultList;
+ try {
+ if (!Strings.isNullOrEmpty(userSearchFilter) && !Strings.isNullOrEmpty(userBaseDN)) {
+ userDn = client.findUserDn(user, userSearchFilter, userBaseDN);
+
+ // This should not be null because we were allowed to bind with this username
+ // safe check in case we were able to bind anonymously.
+ if (userDn == null) {
+ throw new AuthenticationException("Authentication failed: User search found no matching user");
+ }
+ }
+
+ if (!Strings.isNullOrEmpty(groupSearchFilter) && !Strings.isNullOrEmpty(groupBaseDN)) {
+ resultList = client.executeUserAndGroupFilterQuery(user, userDn, groupSearchFilter, groupBaseDN);
+ if (resultList != null && resultList.size() > 0) {
+ return;
+ }
+ } else if (userDn != null) { // succeed based on user search filter only
+ return;
+ }
+ throw new AuthenticationException("Authentication failed: User search does not satisfy filter condition");
+ } catch (NamingException e) {
+ throw new AuthenticationException("LDAP Authentication failed for user", e);
+ }
+ }
+ }
+}
diff --git a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
index edf39128728..d72cd5604d7 100644
--- a/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
+++ b/service/src/test/org/apache/hive/service/auth/TestLdapAtnProviderWithMiniDS.java
@@ -682,4 +682,66 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn());
}
+
+ @Test
+ public void testUserSearchFilterPositive() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("uid")
+ .build();
+
+ testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+ testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+
+ testCase = defaultBuilder()
+ .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("sAMAccountName")
+ .build();
+
+ testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId());
+ testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn());
+ }
+
+ @Test
+ public void testUserSearchFilterNegative() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+ .userSearchFilter("(&(sAMAccountName={0})(objectClass=person)")
+ .guidKey("uid")
+ .build();
+
+ testCase.assertAuthenticateFails(USER1.credentialsWithId());
+ testCase.assertAuthenticateFails(USER3.credentialsWithId());
+ }
+
+ @Test
+ public void testUserAndGroupSearchFilterPositive() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("uid")
+ .groupBaseDN("ou=Groups,dc=example,dc=com")
+ .groupSearchFilter("(&(|(member={0})(member={1}))(&(|(cn=group1)(cn=group2))(objectClass=groupOfNames)))")
+ .build();
+
+ testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+ testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+ }
+
+ @Test
+ public void testUserAndGroupSearchFilterNegative() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person)")
+ .guidKey("uid")
+ .groupBaseDN("ou=Groups,dc=example,dc=com")
+ .groupSearchFilter("(&(|(member={0})(member={1}))(&(cn=group1)(objectClass=groupOfNames)))")
+ .build();
+
+ testCase.assertAuthenticateFails(USER2.credentialsWithId());
+ testCase.assertAuthenticateFails(USER3.credentialsWithId());
+ testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
+ }
}
diff --git a/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java b/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
index 9693e77edc4..40b99f47b90 100644
--- a/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
+++ b/service/src/test/org/apache/hive/service/auth/ldap/LdapAuthenticationTestCase.java
@@ -123,6 +123,21 @@ public final class LdapAuthenticationTestCase {
userMembershipKey);
}
+ public Builder userSearchFilter(String userSearchFilter) {
+ return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_USERSEARCHFILTER,
+ userSearchFilter);
+ }
+
+ public Builder groupSearchFilter(String groupSearchFilter) {
+ return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPSEARCHFILTER,
+ groupSearchFilter);
+ }
+
+ public Builder groupBaseDN(String groupBaseDN) {
+ return setVarOnce(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_GROUPBASEDN,
+ groupBaseDN);
+ }
+
private Builder setVarOnce(HiveConf.ConfVars confVar, String value) {
Preconditions.checkState(!overrides.containsKey(confVar),
"Property %s has been set already", confVar);
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
index 51f0d1a8b3a..ba433e250f7 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreLdapAuthenticationProviderImpl.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.hive.metastore.ldap.FilterFactory;
import org.apache.hadoop.hive.metastore.ldap.GroupFilterFactory;
import org.apache.hadoop.hive.metastore.ldap.LdapUtils;
import org.apache.hadoop.hive.metastore.ldap.UserFilterFactory;
+import org.apache.hadoop.hive.metastore.ldap.UserGroupSearchFilterFactory;
import org.apache.hadoop.hive.metastore.ldap.UserSearchFilterFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,6 +52,7 @@ public class MetaStoreLdapAuthenticationProviderImpl implements MetaStorePasswdA
LoggerFactory.getLogger(MetaStoreLdapAuthenticationProviderImpl.class);
private static final List<FilterFactory> FILTER_FACTORIES = ImmutableList.<FilterFactory>of(
+ new UserGroupSearchFilterFactory(),
new CustomQueryFilterFactory(),
new ChainFilterFactory(new UserSearchFilterFactory(), new UserFilterFactory(),
new GroupFilterFactory())
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
index a61df6c2672..29c4b5774be 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
@@ -985,6 +985,19 @@ public class MetastoreConf {
"For example: (&(objectClass=group)(objectClass=top)(instanceType=4)(cn=Domain*)) \n" +
"(&(objectClass=person)(|(sAMAccountName=admin)(|(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=com)" +
"(memberOf=CN=Administrators,CN=Builtin,DC=domain,DC=com))))"),
+ METASTORE_PLAIN_LDAP_USERSEARCHFILTER("metastore.authentication.ldap.userSearchFilter",
+ "hive.metastore.authentication.ldap.userSearchFilter", "",
+ "User search filter to be used with baseDN to search for users\n" +
+ "For example: (&(uid={0})(objectClass=person))"),
+ METASTORE_PLAIN_LDAP_GROUPBASEDN("metastore.authentication.ldap.groupBaseDN",
+ "hive.metastore.authentication.ldap.groupBaseDN", "",
+ "BaseDN for Group Search. This is used in conjunction with metastore.authentication.ldap.baseDN\n" +
+ "and \n" +
+ "request, succeeds if the group is part of the resultset."),
+ METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER("metastore.authentication.ldap.groupSearchFilter",
+ "hive.metastore.authentication.ldap.groupSearchFilter", "",
+ "Group search filter to be used with baseDN, userSearchFilter, groupBaseDN to search for users in groups\n" +
+ "For example: (&(|(memberUid={0})(memberUid={1}))(objectClass=posixGroup))\n"),
METASTORE_PLAIN_LDAP_BIND_USER("metastore.authentication.ldap.binddn",
"hive.metastore.authentication.ldap.binddn", "",
"The user with which to bind to the LDAP server, and search for the full domain name " +
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
index 2b8238bc77e..0b8efe3bdc4 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/DirSearch.java
@@ -34,6 +34,16 @@ public interface DirSearch extends Closeable {
*/
String findUserDn(String user) throws NamingException;
+ /**
+ * Finds user's distinguished name.
+ * @param user username
+ * @param userSearchFilter Generic LDAP Search filter for ex: (&(uid={0})(objectClass=person))
+ * @param baseDn LDAP BaseDN for user searches for ex: dc=apache,dc=org
+ * @return DN for the specific user if exists, null otherwise
+ * @throws NamingException
+ */
+ String findUserDn(String user, String userSearchFilter, String baseDn) throws NamingException;
+
/**
* Finds group's distinguished name.
* @param group group name or unique identifier
@@ -66,4 +76,15 @@ public interface DirSearch extends Closeable {
* @throws NamingException
*/
List<String> executeCustomQuery(String query) throws NamingException;
+
+ /**
+ * Executes an arbitrary query.
+ * @param user user RDN or username. This will be substituted for {0} in group search
+ * @param userDn userDn DN for the username. This will be substituted for {1} in group search
+ * @param filter filter is the group filter query ex: (&(memberUid={0})(&(CN=group1)(objectClass=posixGroup)))
+ * @param groupBaseDn BaseDN for group searches. ex: "ou=groups,dc=apache,dc=org"
+ * @return list of names that match the group filter aka groups that the user belongs to, if any.
+ * @throws NamingException
+ */
+ List<String> executeUserAndGroupFilterQuery(String user, String userDn, String filter, String groupBaseDn) throws NamingException;
}
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
index b82036cd8ae..775127457ef 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/LdapSearch.java
@@ -106,6 +106,28 @@ public final class LdapSearch implements DirSearch {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String findUserDn(String user, final String userSearchFilter, final String baseDn) throws NamingException {
+ List<String> allLdapNames;
+ String userRdn = user;
+ if (LdapUtils.isDn(user)) {
+ userRdn = LdapUtils.extractUserName(user);
+ }
+ Query query = queries.findUserDnBySearch(userRdn, userSearchFilter);
+ allLdapNames = execute(Collections.singletonList(baseDn), query).getAllLdapNames();
+
+ if (allLdapNames.size() == 1) {
+ return allLdapNames.get(0);
+ } else {
+ LOG.info("Expected exactly one userSearchFilter result for the userSearchFilter: {}, but got {}. Returning null",
+ userSearchFilter, allLdapNames.size());
+ return null;
+ }
+ }
+
private List<String> findDnByPattern(List<String> patterns, String name) throws NamingException {
for (String pattern : patterns) {
String baseDnFromPattern = LdapUtils.extractBaseDn(pattern);
@@ -154,6 +176,15 @@ public final class LdapSearch implements DirSearch {
.getAllLdapNamesAndAttributes();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<String> executeUserAndGroupFilterQuery(String user, String userDn, String groupSearchFilter, String groupBaseDn) throws NamingException {
+ Query query = queries.findDnByUserAndGroupSearch(user, userDn, groupSearchFilter);
+ return execute(Collections.singletonList(groupBaseDn), query).getAllLdapNames();
+ }
+
private SearchResultHandler execute(Collection<String> baseDns, Query query) {
List<NamingEnumeration<SearchResult>> searchResults = new ArrayList<>();
LOG.debug("Executing a query: '{}' with base DNs {}.", query.getFilter(), baseDns);
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
index 7ba2e4c281e..74b3457d256 100644
--- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/QueryFactory.java
@@ -110,6 +110,38 @@ final class QueryFactory {
.build();
}
+ /**
+ * Returns a query for finding user DN based on user RDN.
+ * @param user user RDN that will replace {0} in the search filter
+ * @param userSearchFilter search query that user filter conditions.
+ * @return an instance of {@link Query}
+ */
+ public Query findUserDnBySearch(String user, String userSearchFilter) {
+ if (userSearchFilter != null) {
+ userSearchFilter = userSearchFilter.replaceAll("\\{0\\}", user);
+ }
+ return Query.builder()
+ .filter(userSearchFilter)
+ .build();
+ }
+
+ /**
+ * Returns a query for finding matches based on user and group filter query.
+ * @param user user RDN to replace {0}
+ * @param userDn user DN to replace {1}}
+ * @param groupSearchFilter search query that includes any group filter conditions.
+ * @return an instance of {@link Query}
+ */
+ public Query findDnByUserAndGroupSearch(String user, String userDn, String groupSearchFilter) {
+ if (groupSearchFilter != null) {
+ groupSearchFilter = groupSearchFilter.replaceAll("\\{0\\}", userDn);
+ groupSearchFilter = groupSearchFilter.replaceAll("\\{1\\}", user);
+ }
+ return Query.builder()
+ .filter(groupSearchFilter)
+ .build();
+ }
+
/**
* Returns a query for finding groups to which the user belongs.
* @param userName username
diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java
new file mode 100644
index 00000000000..ee12a57ed5d
--- /dev/null
+++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ldap/UserGroupSearchFilterFactory.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hive.metastore.ldap;
+
+import com.google.common.base.Strings;
+
+import java.util.List;
+import javax.naming.NamingException;
+import javax.security.sasl.AuthenticationException;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for a {@link Filter} based on user and group search filters.
+ * <br>
+ * The produced filter object filters out all users that are not found in the search result
+ * of the query provided in Metastore configuration.
+ * Atleast one search criteria is REQUIRED.
+ * Configuration could have Usersearch filter or Groupsearch filter or both.
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_USERSEARCHFILTER
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_BASEDN
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER
+ * @see MetastoreConf.ConfVars#METASTORE_PLAIN_LDAP_GROUPBASEDN
+ */
+public class UserGroupSearchFilterFactory implements FilterFactory {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Filter getInstance(Configuration conf) {
+ String userSearchFilter = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_USERSEARCHFILTER.getVarname());
+ String userSearchBaseDN = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_BASEDN.getVarname());
+ String groupSearchFilter = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER.getVarname());
+ String groupSearchBaseDN = conf.get(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPBASEDN.getVarname());
+
+ // Both UserSearch and GroupSearch cannot be null or empty.
+ if (Strings.isNullOrEmpty(userSearchFilter) &&
+ (Strings.isNullOrEmpty(groupSearchFilter) && Strings.isNullOrEmpty(groupSearchBaseDN))) {
+ return null;
+ }
+ return new UserGroupSearchFilter(userSearchFilter, userSearchBaseDN, groupSearchFilter, groupSearchBaseDN);
+ }
+
+ private static final class UserGroupSearchFilter implements Filter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UserGroupSearchFilter.class);
+
+ private final String userSearchFilter;
+ private final String userBaseDN;
+ private final String groupSearchFilter;
+ private final String groupBaseDN;
+
+ UserGroupSearchFilter(String userSearchFilter, String userBaseDN, String groupSearchFilter, String groupBaseDN) {
+ this.userSearchFilter = userSearchFilter;
+ this.userBaseDN = userBaseDN;
+ this.groupSearchFilter = groupSearchFilter;
+ this.groupBaseDN = groupBaseDN;
+ }
+
+ @Override public void apply(DirSearch client, String user) throws AuthenticationException {
+ String userDn = null;
+ List<String> resultList;
+ try {
+ if (!Strings.isNullOrEmpty(userSearchFilter) && !Strings.isNullOrEmpty(userBaseDN)) {
+ userDn = client.findUserDn(user, userSearchFilter, userBaseDN);
+
+ // This should not be null because we were allowed to bind with this username
+ // safe check in case we were able to bind anonymously.
+ if (userDn == null) {
+ throw new AuthenticationException("Authentication failed: User search found no matching user");
+ }
+ }
+
+ if (!Strings.isNullOrEmpty(groupSearchFilter) && !Strings.isNullOrEmpty(groupBaseDN)) {
+ resultList = client.executeUserAndGroupFilterQuery(user, userDn, groupSearchFilter, groupBaseDN);
+ if (resultList != null && resultList.size() > 0) {
+ return;
+ }
+ } else if (userDn != null) { // succeed based on user search filter only
+ return;
+ }
+ throw new AuthenticationException("Authentication failed: User search does not satisfy filter condition");
+ } catch (NamingException e) {
+ throw new AuthenticationException("LDAP Authentication failed for user", e);
+ }
+ }
+ }
+}
+
diff --git a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
index 075e895e23e..816ec4a3717 100644
--- a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
+++ b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestLdapAtnProviderWithMiniDS.java
@@ -689,4 +689,67 @@ public class TestLdapAtnProviderWithMiniDS extends AbstractLdapTestUnit {
testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
testCase.assertAuthenticateFails(MANAGER_1.credentialsWithDn());
}
+
+ @Test
+ public void testUserSearchFilterPositive() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("uid")
+ .build();
+
+ testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+ testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+
+ testCase = defaultBuilder()
+ .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("sAMAccountName")
+ .build();
+
+ testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithId());
+ testCase.assertAuthenticatePasses(ENGINEER_1.credentialsWithDn());
+ }
+
+ @Test
+ public void testUserSearchFilterNegative() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=Engineering,dc=ad,dc=example,dc=com")
+ .userSearchFilter("(&(sAMAccountName={0})(objectClass=person)")
+ .guidKey("uid")
+ .build();
+
+ testCase.assertAuthenticateFails(USER1.credentialsWithId());
+ testCase.assertAuthenticateFails(USER3.credentialsWithId());
+ }
+
+ @Test
+ public void testUserAndGroupSearchFilterPositive() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person))")
+ .guidKey("uid")
+ .groupBaseDN("ou=Groups,dc=example,dc=com")
+ .groupSearchFilter("(&(|(member={0})(member={1}))(&(|(cn=group1)(cn=group2))(objectClass=groupOfNames)))")
+ .build();
+
+ testCase.assertAuthenticatePasses(USER1.credentialsWithId());
+ testCase.assertAuthenticatePasses(USER2.credentialsWithId());
+ }
+
+ @Test
+ public void testUserAndGroupSearchFilterNegative() throws Exception {
+ testCase = defaultBuilder()
+ .baseDN("ou=People,dc=example,dc=com")
+ .userSearchFilter("(&(|(uid={0})(sAMAccountName={0}))(objectClass=person)")
+ .guidKey("uid")
+ .groupBaseDN("ou=Groups,dc=example,dc=com")
+ .groupSearchFilter("(&(|(member={0})(member={1}))(&(cn=group1)(objectClass=groupOfNames)))")
+ .build();
+
+ testCase.assertAuthenticateFails(USER2.credentialsWithId());
+ testCase.assertAuthenticateFails(USER3.credentialsWithId());
+ testCase.assertAuthenticateFails(ENGINEER_1.credentialsWithId());
+ }
+
}
diff --git a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
index 1a404a83401..4429840eae6 100644
--- a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
+++ b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/ldap/LdapAuthenticationTestCase.java
@@ -125,6 +125,21 @@ public final class LdapAuthenticationTestCase {
userMembershipKey);
}
+ public Builder userSearchFilter(String userSearchFilter) {
+ return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_USERSEARCHFILTER,
+ userSearchFilter);
+ }
+
+ public Builder groupSearchFilter(String groupSearchFilter) {
+ return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPSEARCHFILTER,
+ groupSearchFilter);
+ }
+
+ public Builder groupBaseDN(String groupBaseDN) {
+ return setVarOnce(MetastoreConf.ConfVars.METASTORE_PLAIN_LDAP_GROUPBASEDN,
+ groupBaseDN);
+ }
+
private Builder setVarOnce(MetastoreConf.ConfVars confVar, String value) {
Preconditions.checkState(!overrides.containsKey(confVar),
"Property %s has been set already", confVar);