Skip to content

Commit 62efb63

Browse files
committed
HADOOP-16245. Restrict the effect of LdapGroupsMapping SSL configurations to avoid interfering with other SSL connections. Contributed by Erik Krogen.
1 parent ecc8acf commit 62efb63

File tree

1 file changed

+153
-13
lines changed

1 file changed

+153
-13
lines changed

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

Lines changed: 153 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717
*/
1818
package org.apache.hadoop.security;
1919

20+
import java.io.FileInputStream;
2021
import java.io.IOException;
22+
import java.io.InputStream;
2123
import java.io.InputStreamReader;
2224
import java.io.Reader;
25+
import java.net.InetAddress;
26+
import java.net.Socket;
2327
import java.nio.charset.StandardCharsets;
2428
import java.nio.file.Files;
2529
import java.nio.file.Paths;
30+
import java.security.GeneralSecurityException;
31+
import java.security.KeyStore;
2632
import java.util.ArrayList;
2733
import java.util.Collections;
2834
import java.util.Hashtable;
@@ -44,6 +50,13 @@
4450
import javax.naming.ldap.LdapName;
4551
import javax.naming.ldap.Rdn;
4652
import javax.naming.spi.InitialContextFactory;
53+
import javax.net.SocketFactory;
54+
import javax.net.ssl.KeyManager;
55+
import javax.net.ssl.KeyManagerFactory;
56+
import javax.net.ssl.SSLContext;
57+
import javax.net.ssl.SSLSocketFactory;
58+
import javax.net.ssl.TrustManager;
59+
import javax.net.ssl.TrustManagerFactory;
4760

4861
import com.google.common.collect.Iterators;
4962
import org.apache.hadoop.classification.InterfaceAudience;
@@ -273,6 +286,13 @@ public class LdapGroupsMapping
273286
public static final String LDAP_CTX_FACTORY_CLASS_DEFAULT =
274287
"com.sun.jndi.ldap.LdapCtxFactory";
275288

289+
/**
290+
* The env key used for specifying a custom socket factory to be used for
291+
* creating connections to the LDAP server. This is not a Hadoop conf key.
292+
*/
293+
private static final String LDAP_SOCKET_FACTORY_ENV_KEY =
294+
"java.naming.ldap.factory.socket";
295+
276296
private static final Logger LOG =
277297
LoggerFactory.getLogger(LdapGroupsMapping.class);
278298

@@ -640,19 +660,13 @@ private DirContext getDirContext() throws NamingException {
640660
// Set up SSL security, if necessary
641661
if (useSsl) {
642662
env.put(Context.SECURITY_PROTOCOL, "ssl");
643-
if (!keystore.isEmpty()) {
644-
System.setProperty("javax.net.ssl.keyStore", keystore);
645-
}
646-
if (!keystorePass.isEmpty()) {
647-
System.setProperty("javax.net.ssl.keyStorePassword", keystorePass);
648-
}
649-
if (!truststore.isEmpty()) {
650-
System.setProperty("javax.net.ssl.trustStore", truststore);
651-
}
652-
if (!truststorePass.isEmpty()) {
653-
System.setProperty("javax.net.ssl.trustStorePassword",
654-
truststorePass);
655-
}
663+
// It is necessary to use a custom socket factory rather than setting
664+
// system properties to configure these options to avoid interfering
665+
// with other SSL factories throughout the system
666+
LdapSslSocketFactory.setConfigurations(keystore, keystorePass,
667+
truststore, truststorePass);
668+
env.put("java.naming.ldap.factory.socket",
669+
LdapSslSocketFactory.class.getName());
656670
}
657671

658672
env.put(Context.SECURITY_PRINCIPAL, currentBindUser.username);
@@ -929,4 +943,130 @@ public String toString() {
929943
return this.username;
930944
}
931945
}
946+
947+
/**
948+
* An private internal socket factory used to create SSL sockets with custom
949+
* configuration. There is no way to pass a specific instance of a factory to
950+
* the Java naming services, and the instantiated socket factory is not
951+
* passed any contextual information, so all information must be encapsulated
952+
* directly in the class. Static fields are used here to achieve this. This is
953+
* safe since the only usage of {@link LdapGroupsMapping} is within
954+
* {@link Groups}, which is a singleton (see the GROUPS field).
955+
* <p>
956+
* This has nearly the same behavior as an {@link SSLSocketFactory}. The only
957+
* additional logic is to configure the key store and trust store.
958+
* <p>
959+
* This is public only to be accessible by the Java naming services.
960+
*/
961+
@InterfaceAudience.Private
962+
public static class LdapSslSocketFactory extends SocketFactory {
963+
964+
/** Cached value lazy-loaded by {@link #getDefault()}. */
965+
private static LdapSslSocketFactory defaultSslFactory;
966+
967+
private static String keyStoreLocation;
968+
private static String keyStorePassword;
969+
private static String trustStoreLocation;
970+
private static String trustStorePassword;
971+
972+
private final SSLSocketFactory socketFactory;
973+
974+
LdapSslSocketFactory(SSLSocketFactory wrappedSocketFactory) {
975+
this.socketFactory = wrappedSocketFactory;
976+
}
977+
978+
public static synchronized SocketFactory getDefault() {
979+
if (defaultSslFactory == null) {
980+
try {
981+
SSLContext context = SSLContext.getInstance("TLS");
982+
context.init(createKeyManagers(), createTrustManagers(), null);
983+
defaultSslFactory =
984+
new LdapSslSocketFactory(context.getSocketFactory());
985+
LOG.info("Successfully instantiated LdapSslSocketFactory with "
986+
+ "keyStoreLocation = {} and trustStoreLocation = {}",
987+
keyStoreLocation, trustStoreLocation);
988+
} catch (IOException | GeneralSecurityException e) {
989+
throw new RuntimeException("Unable to create SSLSocketFactory", e);
990+
}
991+
}
992+
return defaultSslFactory;
993+
}
994+
995+
static synchronized void setConfigurations(String newKeyStoreLocation,
996+
String newKeyStorePassword, String newTrustStoreLocation,
997+
String newTrustStorePassword) {
998+
LdapSslSocketFactory.keyStoreLocation = newKeyStoreLocation;
999+
LdapSslSocketFactory.keyStorePassword = newKeyStorePassword;
1000+
LdapSslSocketFactory.trustStoreLocation = newTrustStoreLocation;
1001+
LdapSslSocketFactory.trustStorePassword = newTrustStorePassword;
1002+
}
1003+
1004+
private static KeyManager[] createKeyManagers()
1005+
throws IOException, GeneralSecurityException {
1006+
if (keyStoreLocation.isEmpty()) {
1007+
return null;
1008+
}
1009+
KeyManagerFactory keyMgrFactory = KeyManagerFactory
1010+
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
1011+
keyMgrFactory.init(createKeyStore(keyStoreLocation, keyStorePassword),
1012+
getPasswordCharArray(keyStorePassword));
1013+
return keyMgrFactory.getKeyManagers();
1014+
}
1015+
1016+
private static TrustManager[] createTrustManagers()
1017+
throws IOException, GeneralSecurityException {
1018+
if (trustStoreLocation.isEmpty()) {
1019+
return null;
1020+
}
1021+
TrustManagerFactory trustMgrFactory = TrustManagerFactory
1022+
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1023+
trustMgrFactory.init(
1024+
createKeyStore(trustStoreLocation, trustStorePassword));
1025+
return trustMgrFactory.getTrustManagers();
1026+
}
1027+
1028+
private static KeyStore createKeyStore(String location, String password)
1029+
throws IOException, GeneralSecurityException {
1030+
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
1031+
try (InputStream keyStoreInput = new FileInputStream(location)) {
1032+
keyStore.load(keyStoreInput, getPasswordCharArray(password));
1033+
}
1034+
return keyStore;
1035+
}
1036+
1037+
private static char[] getPasswordCharArray(String password) {
1038+
if (password == null || password.isEmpty()) {
1039+
return null;
1040+
}
1041+
return password.toCharArray();
1042+
}
1043+
1044+
@Override
1045+
public Socket createSocket() throws IOException {
1046+
return socketFactory.createSocket();
1047+
}
1048+
1049+
@Override
1050+
public Socket createSocket(String host, int port) throws IOException {
1051+
return socketFactory.createSocket(host, port);
1052+
}
1053+
1054+
@Override
1055+
public Socket createSocket(String host, int port, InetAddress localHost,
1056+
int localPort) throws IOException {
1057+
return socketFactory.createSocket(host, port, localHost, localPort);
1058+
}
1059+
1060+
@Override
1061+
public Socket createSocket(InetAddress host, int port) throws IOException {
1062+
return socketFactory.createSocket(host, port);
1063+
}
1064+
1065+
@Override
1066+
public Socket createSocket(InetAddress address, int port,
1067+
InetAddress localAddress, int localPort) throws IOException {
1068+
return socketFactory.createSocket(address, port, localAddress, localPort);
1069+
}
1070+
}
1071+
9321072
}

0 commit comments

Comments
 (0)