|
17 | 17 | */ |
18 | 18 | package org.apache.hadoop.security; |
19 | 19 |
|
| 20 | +import java.io.FileInputStream; |
20 | 21 | import java.io.IOException; |
| 22 | +import java.io.InputStream; |
21 | 23 | import java.io.InputStreamReader; |
22 | 24 | import java.io.Reader; |
| 25 | +import java.net.InetAddress; |
| 26 | +import java.net.Socket; |
23 | 27 | import java.nio.charset.StandardCharsets; |
24 | 28 | import java.nio.file.Files; |
25 | 29 | import java.nio.file.Paths; |
| 30 | +import java.security.GeneralSecurityException; |
| 31 | +import java.security.KeyStore; |
26 | 32 | import java.util.ArrayList; |
27 | 33 | import java.util.Collections; |
28 | 34 | import java.util.Hashtable; |
|
44 | 50 | import javax.naming.ldap.LdapName; |
45 | 51 | import javax.naming.ldap.Rdn; |
46 | 52 | 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; |
47 | 60 |
|
48 | 61 | import com.google.common.collect.Iterators; |
49 | 62 | import org.apache.hadoop.classification.InterfaceAudience; |
@@ -273,6 +286,13 @@ public class LdapGroupsMapping |
273 | 286 | public static final String LDAP_CTX_FACTORY_CLASS_DEFAULT = |
274 | 287 | "com.sun.jndi.ldap.LdapCtxFactory"; |
275 | 288 |
|
| 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 | + |
276 | 296 | private static final Logger LOG = |
277 | 297 | LoggerFactory.getLogger(LdapGroupsMapping.class); |
278 | 298 |
|
@@ -640,19 +660,13 @@ private DirContext getDirContext() throws NamingException { |
640 | 660 | // Set up SSL security, if necessary |
641 | 661 | if (useSsl) { |
642 | 662 | 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()); |
656 | 670 | } |
657 | 671 |
|
658 | 672 | env.put(Context.SECURITY_PRINCIPAL, currentBindUser.username); |
@@ -929,4 +943,130 @@ public String toString() { |
929 | 943 | return this.username; |
930 | 944 | } |
931 | 945 | } |
| 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 | + |
932 | 1072 | } |
0 commit comments