HADOOP-13105. Support timeouts in LDAP queries in LdapGroupsMapping. Contributed by Mingliang Liu.
(cherry picked from commitd82bc85018
) (cherry picked from commit5e6ee5aafb
) Conflicts: hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestLdapGroupsMapping.java
This commit is contained in:
parent
6963d50e2f
commit
e54f073cfe
|
@ -170,6 +170,13 @@ public class LdapGroupsMapping
|
||||||
LDAP_CONFIG_PREFIX + ".directory.search.timeout";
|
LDAP_CONFIG_PREFIX + ".directory.search.timeout";
|
||||||
public static final int DIRECTORY_SEARCH_TIMEOUT_DEFAULT = 10000; // 10s
|
public static final int DIRECTORY_SEARCH_TIMEOUT_DEFAULT = 10000; // 10s
|
||||||
|
|
||||||
|
public static final String CONNECTION_TIMEOUT =
|
||||||
|
LDAP_CONFIG_PREFIX + ".connection.timeout.ms";
|
||||||
|
public static final int CONNECTION_TIMEOUT_DEFAULT = 60 * 1000; // 60 seconds
|
||||||
|
public static final String READ_TIMEOUT =
|
||||||
|
LDAP_CONFIG_PREFIX + ".read.timeout.ms";
|
||||||
|
public static final int READ_TIMEOUT_DEFAULT = 60 * 1000; // 60 seconds
|
||||||
|
|
||||||
private static final Log LOG = LogFactory.getLog(LdapGroupsMapping.class);
|
private static final Log LOG = LogFactory.getLog(LdapGroupsMapping.class);
|
||||||
|
|
||||||
private static final SearchControls SEARCH_CONTROLS = new SearchControls();
|
private static final SearchControls SEARCH_CONTROLS = new SearchControls();
|
||||||
|
@ -306,6 +313,11 @@ public class LdapGroupsMapping
|
||||||
env.put(Context.SECURITY_PRINCIPAL, bindUser);
|
env.put(Context.SECURITY_PRINCIPAL, bindUser);
|
||||||
env.put(Context.SECURITY_CREDENTIALS, bindPassword);
|
env.put(Context.SECURITY_CREDENTIALS, bindPassword);
|
||||||
|
|
||||||
|
env.put("com.sun.jndi.ldap.connect.timeout", conf.get(CONNECTION_TIMEOUT,
|
||||||
|
String.valueOf(CONNECTION_TIMEOUT_DEFAULT)));
|
||||||
|
env.put("com.sun.jndi.ldap.read.timeout", conf.get(READ_TIMEOUT,
|
||||||
|
String.valueOf(READ_TIMEOUT_DEFAULT)));
|
||||||
|
|
||||||
ctx = new InitialDirContext(env);
|
ctx = new InitialDirContext(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,6 +174,30 @@
|
||||||
</description>
|
</description>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hadoop.security.group.mapping.ldap.connection.timeout.ms</name>
|
||||||
|
<value>60000</value>
|
||||||
|
<description>
|
||||||
|
This property is the connection timeout (in milliseconds) for LDAP
|
||||||
|
operations. If the LDAP provider doesn't establish a connection within the
|
||||||
|
specified period, it will abort the connect attempt. Non-positive value
|
||||||
|
means no LDAP connection timeout is specified in which case it waits for the
|
||||||
|
connection to establish until the underlying network times out.
|
||||||
|
</description>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>hadoop.security.group.mapping.ldap.read.timeout.ms</name>
|
||||||
|
<value>60000</value>
|
||||||
|
<description>
|
||||||
|
This property is the read timeout (in milliseconds) for LDAP
|
||||||
|
operations. If the LDAP provider doesn't get a LDAP response within the
|
||||||
|
specified period, it will abort the read attempt. Non-positive value
|
||||||
|
means no read timeout is specified in which case it waits for the response
|
||||||
|
infinitely.
|
||||||
|
</description>
|
||||||
|
</property>
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
<name>hadoop.security.group.mapping.ldap.url</name>
|
<name>hadoop.security.group.mapping.ldap.url</name>
|
||||||
<value></value>
|
<value></value>
|
||||||
|
|
|
@ -17,16 +17,24 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.security;
|
package org.apache.hadoop.security;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.security.LdapGroupsMapping.CONNECTION_TIMEOUT;
|
||||||
|
import static org.apache.hadoop.security.LdapGroupsMapping.READ_TIMEOUT;
|
||||||
|
import static org.apache.hadoop.test.GenericTestUtils.assertExceptionContains;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import javax.naming.CommunicationException;
|
import javax.naming.CommunicationException;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
@ -35,15 +43,37 @@ import javax.naming.directory.SearchResult;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.Path;
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.io.IOUtils;
|
||||||
import org.apache.hadoop.security.alias.CredentialProvider;
|
import org.apache.hadoop.security.alias.CredentialProvider;
|
||||||
import org.apache.hadoop.security.alias.CredentialProviderFactory;
|
import org.apache.hadoop.security.alias.CredentialProviderFactory;
|
||||||
import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
|
import org.apache.hadoop.security.alias.JavaKeyStoreProvider;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
|
public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(
|
||||||
|
TestLdapGroupsMapping.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To construct a LDAP InitialDirContext object, it will firstly initiate a
|
||||||
|
* protocol session to server for authentication. After a session is
|
||||||
|
* established, a method of authentication is negotiated between the server
|
||||||
|
* and the client. When the client is authenticated, the LDAP server will send
|
||||||
|
* a bind response, whose message contents are bytes as the
|
||||||
|
* {@link #AUTHENTICATE_SUCCESS_MSG}. After receiving this bind response
|
||||||
|
* message, the LDAP context is considered connected to the server and thus
|
||||||
|
* can issue query requests for determining group membership.
|
||||||
|
*/
|
||||||
|
private static final byte[] AUTHENTICATE_SUCCESS_MSG =
|
||||||
|
{48, 12, 2, 1, 1, 97, 7, 10, 1, 0, 4, 0, 4, 0};
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupMocks() throws NamingException {
|
public void setupMocks() throws NamingException {
|
||||||
SearchResult mockUserResult = mock(SearchResult.class);
|
SearchResult mockUserResult = mock(SearchResult.class);
|
||||||
|
@ -174,4 +204,114 @@ public class TestLdapGroupsMapping extends TestLdapGroupsMappingBase {
|
||||||
// extract password
|
// extract password
|
||||||
Assert.assertEquals("", mapping.getPassword(conf,"invalid-alias", ""));
|
Assert.assertEquals("", mapping.getPassword(conf,"invalid-alias", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that if the {@link LdapGroupsMapping#CONNECTION_TIMEOUT} is set in the
|
||||||
|
* configuration, the LdapGroupsMapping connection will timeout by this value
|
||||||
|
* if it does not get a LDAP response from the server.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test (timeout = 30000)
|
||||||
|
public void testLdapConnectionTimeout()
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
final int connectionTimeoutMs = 3 * 1000; // 3s
|
||||||
|
try (ServerSocket serverSock = new ServerSocket(0)) {
|
||||||
|
final CountDownLatch finLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
// Below we create a LDAP server which will accept a client request;
|
||||||
|
// but it will never reply to the bind (connect) request.
|
||||||
|
// Client of this LDAP server is expected to get a connection timeout.
|
||||||
|
final Thread ldapServer = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
try (Socket ignored = serverSock.accept()) {
|
||||||
|
finLatch.await();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ldapServer.start();
|
||||||
|
|
||||||
|
final LdapGroupsMapping mapping = new LdapGroupsMapping();
|
||||||
|
final Configuration conf = new Configuration();
|
||||||
|
conf.set(LdapGroupsMapping.LDAP_URL_KEY,
|
||||||
|
"ldap://localhost:" + serverSock.getLocalPort());
|
||||||
|
conf.setInt(CONNECTION_TIMEOUT, connectionTimeoutMs);
|
||||||
|
mapping.setConf(conf);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mapping.doGetGroups("hadoop");
|
||||||
|
fail("The LDAP query should have timed out!");
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
LOG.debug("Got the exception while LDAP querying: ", ne);
|
||||||
|
assertExceptionContains("LDAP response read timed out, timeout used:" +
|
||||||
|
connectionTimeoutMs + "ms", ne);
|
||||||
|
assertFalse(ne.getMessage().contains("remaining name"));
|
||||||
|
} finally {
|
||||||
|
finLatch.countDown();
|
||||||
|
}
|
||||||
|
ldapServer.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that if the {@link LdapGroupsMapping#READ_TIMEOUT} is set in the
|
||||||
|
* configuration, the LdapGroupsMapping query will timeout by this value if
|
||||||
|
* it does not get a LDAP response from the server.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
@Test(timeout = 30000)
|
||||||
|
public void testLdapReadTimeout() throws IOException, InterruptedException {
|
||||||
|
final int readTimeoutMs = 4 * 1000; // 4s
|
||||||
|
try (ServerSocket serverSock = new ServerSocket(0)) {
|
||||||
|
final CountDownLatch finLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
// Below we create a LDAP server which will accept a client request,
|
||||||
|
// authenticate it successfully; but it will never reply to the following
|
||||||
|
// query request.
|
||||||
|
// Client of this LDAP server is expected to get a read timeout.
|
||||||
|
final Thread ldapServer = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
try (Socket clientSock = serverSock.accept()) {
|
||||||
|
IOUtils.skipFully(clientSock.getInputStream(), 1);
|
||||||
|
clientSock.getOutputStream().write(AUTHENTICATE_SUCCESS_MSG);
|
||||||
|
finLatch.await();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ldapServer.start();
|
||||||
|
|
||||||
|
final LdapGroupsMapping mapping = new LdapGroupsMapping();
|
||||||
|
final Configuration conf = new Configuration();
|
||||||
|
conf.set(LdapGroupsMapping.LDAP_URL_KEY,
|
||||||
|
"ldap://localhost:" + serverSock.getLocalPort());
|
||||||
|
conf.setInt(READ_TIMEOUT, readTimeoutMs);
|
||||||
|
mapping.setConf(conf);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mapping.doGetGroups("hadoop");
|
||||||
|
fail("The LDAP query should have timed out!");
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
LOG.debug("Got the exception while LDAP querying: ", ne);
|
||||||
|
assertExceptionContains("LDAP response read timed out, timeout used:" +
|
||||||
|
readTimeoutMs + "ms", ne);
|
||||||
|
assertExceptionContains("remaining name", ne);
|
||||||
|
} finally {
|
||||||
|
finLatch.countDown();
|
||||||
|
}
|
||||||
|
ldapServer.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue