diff --git common/src/java/org/apache/hadoop/hive/conf/HiveConf.java common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index a3b03ca..2156ff1 100644 --- common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -3499,6 +3499,16 @@ private static void populateLlapDaemonVarsSet(Set llapDaemonVarsSetLocal "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 service/pom.xml service/pom.xml index eca6f3b..30b7398 100644 --- service/pom.xml +++ service/pom.xml @@ -36,6 +36,17 @@ org.apache.hive + hive-common + ${project.version} + + + org.eclipse.jetty.aggregate + jetty-all + + + + + org.apache.hive hive-exec ${project.version} diff --git service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java index 73bbb6b..0120513 100644 --- service/src/java/org/apache/hive/service/auth/LdapAuthenticationProviderImpl.java +++ 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 LdapAuthenticationProviderImpl(HiveConf conf) { @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 service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java index 43453fa..0396b74 100644 --- service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java +++ service/src/test/org/apache/hive/service/auth/TestLdapAuthenticationProviderImpl.java @@ -22,6 +22,7 @@ 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 void testAuthenticateWhenUserMembershipKeyFilter2x2PatternsPasses() throw 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 service/src/test/resources/creds/test.jceks service/src/test/resources/creds/test.jceks new file mode 100755 index 0000000000000000000000000000000000000000..8d58c414192d9a4a6d1f48ce876057db7a1210f4 GIT binary patch literal 534 zcmX?i?%X*B1_mY|W&~np2K9{0vQ)j|)S|M~A|t)T(vpnSypqi1#FEVXJiVNh!~(sf z%)FF>a-jN*o$JJ7*1Tn4EiPg(PR`HOD=y8`OD?J`D9P6=D9SI(Oi3-$3rx#zfQ?O`LImG{?Pof4PD#r`12UaX@bCD`J>%chg_ zgPM)cean7ax$pVHml2X57x_NFyDU(0LgdMn1{GV9_J7xY-x1gT{BeO-fSO4q!}a4U zUloVX-lHOOc2(ze@e2vpT)Np!OBlogoLs{*OEP?2Oda!5LW(jAa#CGfgK