This is an automated email from the ASF dual-hosted git repository. sankarh pushed a commit to branch branch-3 in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/branch-3 by this push: new edd0d46408f HIVE-27614 : Backport of HIVE-21009: Adding ability for user to set bind user (#4594) edd0d46408f is described below commit edd0d46408f7f9226e2fad2bc55f4d3b435d69c6 Author: Aman Raj <104416558+amanraj2...@users.noreply.github.com> AuthorDate: Mon Aug 21 10:42:47 2023 +0530 HIVE-27614 : Backport of HIVE-21009: Adding ability for user to set bind user (#4594) Signed-off-by: Sankar Hariappan <sank...@apache.org> Closes (#4594) --- .../java/org/apache/hadoop/hive/conf/HiveConf.java | 10 ++ service/pom.xml | 11 ++ .../auth/LdapAuthenticationProviderImpl.java | 32 +++++- .../auth/TestLdapAuthenticationProviderImpl.java | 113 +++++++++++++++++++++ service/src/test/resources/creds/test.jceks | Bin 0 -> 534 bytes 5 files changed, 164 insertions(+), 2 deletions(-) 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 f9a47324473..33796a24d19 100644 --- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -3386,6 +3386,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_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" + + "This should be the full domain name of the user, and should have search access across all " + + "users in the LDAP tree.\n" + + "If not specified, then the user being authenticated will be used as the bind user.\n" + + "For example: CN=bindUser,CN=Users,DC=subdomain,DC=domain,DC=com"), + HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD("hive.server2.authentication.ldap.bindpw", null, + "The password for the bind user, to be used to search for the full name of the user being authenticated.\n" + + "If the username is specified, this parameter must also be specified."), HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS("hive.server2.custom.authentication.class", null, "Custom authentication class. Used when property\n" + "'hive.server2.authentication' is set to 'CUSTOM'. Provided class\n" + diff --git a/service/pom.xml b/service/pom.xml index e44d1244e52..7f93efe0c04 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -34,6 +34,17 @@ <dependencies> <!-- dependencies are always listed in sorted order by groupId, artifectId --> <!-- intra-project --> + <dependency> + <groupId>org.apache.hive</groupId> + <artifactId>hive-common</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>org.eclipse.jetty.aggregate</groupId> + <artifactId>jetty-all</artifactId> + </exclusion> + </exclusions> + </dependency> <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-exec</artifactId> 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 73bbb6bdf8a..0120513b515 100644 --- a/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java +++ b/service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java @@ -18,9 +18,10 @@ package org.apache.hive.service.auth; import javax.security.sasl.AuthenticationException; - +import javax.naming.NamingException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import java.io.IOException; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; @@ -68,9 +69,36 @@ public class LdapAuthenticationProviderImpl implements PasswdAuthenticationProvi @Override public void Authenticate(String user, String password) throws AuthenticationException { DirSearch search = null; + String bindUser = this.conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER); + String bindPassword = null; + try { + char[] rawPassword = this.conf.getPassword(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD.toString()); + if (rawPassword != null) { + bindPassword = new String(rawPassword); + } + } catch (IOException e) { + bindPassword = null; + } + boolean usedBind = bindUser != null && bindPassword != null; + if (!usedBind) { + // If no bind user or bind password was specified, + // we assume the user we are authenticating has the ability to search + // the LDAP tree, so we use it as the "binding" account. + // This is the way it worked before bind users were allowed in the LDAP authenticator, + // so we keep existing systems working. + bindUser = user; + bindPassword = password; + } try { - search = createDirSearch(user, password); + search = createDirSearch(bindUser, bindPassword); applyFilter(search, user); + if (usedBind) { + // If we used the bind user, then we need to authenticate again, + // this time using the full user name we got during the bind process. + createDirSearch(search.findUserDn(user), password); + } + } catch (NamingException e) { + throw new AuthenticationException("Unable to find the user in the LDAP tree. " + e.getMessage()); } finally { ServiceUtils.cleanup(LOG, search); } diff --git a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java index 43453fa53f5..0396b749127 100644 --- a/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java +++ b/service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java @@ -22,6 +22,7 @@ import java.util.Arrays; import javax.naming.NamingException; import javax.security.sasl.AuthenticationException; import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hive.service.auth.ldap.DirSearch; import org.apache.hive.service.auth.ldap.DirSearchFactory; import org.apache.hive.service.auth.ldap.LdapSearchFactory; @@ -324,6 +325,118 @@ public class TestLdapAuthenticationProviderImpl { verify(search, atLeastOnce()).close(); } + @Test + public void testAuthenticateWithBindInCredentialFilePasses() throws AuthenticationException, NamingException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String bindPass = "testPassword"; + String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String authUser = "user1"; + String authPass = "Blah"; + String tmpDir = System.getProperty("build.dir"); + String credentialsPath = "jceks://file" + tmpDir + "/test-classes/creds/test.jceks"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, credentialsPath); + + System.out.println(tmpDir); + + when(search.findUserDn(eq(authUser))).thenReturn(authFullUser); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + + verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(bindUser), eq(bindPass)); + verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(authFullUser), eq(authPass)); + verify(search, times(1)).findUserDn(eq(authUser)); + } + + @Test + public void testAuthenticateWithBindInMissingCredentialFilePasses() throws AuthenticationException, NamingException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String authUser = "user1"; + String authPass = "Blah"; + String tmpDir = System.getProperty("build.dir"); + String credentialsPath = "jceks://file" + tmpDir + "/test-classes/creds/nonExistent.jceks"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, credentialsPath); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + + verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(authUser), eq(authPass)); + } + + @Test + public void testAuthenticateWithBindUserPasses() throws AuthenticationException, NamingException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String bindPass = "Blah"; + String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String authUser = "user1"; + String authPass = "Blah2"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, bindPass); + + when(search.findUserDn(eq(authUser))).thenReturn(authFullUser); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + + verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(bindUser), eq(bindPass)); + verify(factory, times(1)).getInstance(isA(HiveConf.class), eq(authFullUser), eq(authPass)); + verify(search, times(1)).findUserDn(eq(authUser)); + } + + @Test + public void testAuthenticateWithBindUserFailsOnAuthentication() throws AuthenticationException, NamingException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String bindPass = "Blah"; + String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String authUser = "user1"; + String authPass = "Blah2"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, bindPass); + + thrown.expect(AuthenticationException.class); + when(factory.getInstance(any(HiveConf.class), eq(authFullUser), eq(authPass))). + thenThrow(AuthenticationException.class); + when(search.findUserDn(eq(authUser))).thenReturn(authFullUser); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + } + + @Test + public void testAuthenticateWithBindUserFailsOnGettingDn() throws AuthenticationException, NamingException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String bindPass = "Blah"; + String authFullUser = "cn=user1,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String authUser = "user1"; + String authPass = "Blah2"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, bindPass); + + thrown.expect(AuthenticationException.class); + when(search.findUserDn(eq(authUser))).thenThrow(NamingException.class); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + } + + @Test + public void testAuthenticateWithBindUserFailsOnBinding() throws AuthenticationException { + String bindUser = "cn=BindUser,ou=Users,ou=branch1,dc=mycorp,dc=com"; + String bindPass = "Blah"; + String authUser = "user1"; + String authPass = "Blah2"; + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_USER, bindUser); + conf.setVar(HiveConf.ConfVars.HIVE_SERVER2_PLAIN_LDAP_BIND_PASSWORD, bindPass); + + thrown.expect(AuthenticationException.class); + when(factory.getInstance(any(HiveConf.class), eq(bindUser), eq(bindPass))).thenThrow(AuthenticationException.class); + + auth = new LdapAuthenticationProviderImpl(conf, factory); + auth.Authenticate(authUser, authPass); + } + private void expectAuthenticationExceptionForInvalidPassword() { thrown.expect(AuthenticationException.class); thrown.expectMessage("a null or blank password has been provided"); diff --git a/service/src/test/resources/creds/test.jceks b/service/src/test/resources/creds/test.jceks new file mode 100755 index 00000000000..8d58c414192 Binary files /dev/null and b/service/src/test/resources/creds/test.jceks differ