From c5ef0767864b90ee9a013a60405c08ce6192d2bc Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 28 Nov 2016 16:38:33 -0500 Subject: [PATCH] NIFI-2325: - Adding support for LDAPS. This closes #1275. Signed-off-by: Andy LoPresto --- .../main/asciidoc/administration-guide.adoc | 18 +-- .../conf/login-identity-providers.xml | 18 +-- .../nifi/ldap/LdapAuthenticationStrategy.java | 1 + .../org/apache/nifi/ldap/LdapProvider.java | 121 +++++++++++------- .../apache/nifi/ldap/LdapsSocketFactory.java | 106 +++++++++++++++ 5 files changed, 200 insertions(+), 64 deletions(-) create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapsSocketFactory.java diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 773cb649e4..ccbd47138d 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -307,17 +307,17 @@ nifi.security.user.login.identity.provider=ldap-provider [options="header,footer"] |================================================================================================================================================== | Property Name | Description -|`Authentication Strategy` | How the connection to the LDAP server is authenticated. Possible values are ANONYMOUS, SIMPLE, or START_TLS. +|`Authentication Strategy` | How the connection to the LDAP server is authenticated. Possible values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS. |`Manager DN` | The DN of the manager that is used to bind to the LDAP server to search for users. |`Manager Password` | The password of the manager that is used to bind to the LDAP server to search for users. -|`TLS - Keystore` | Path to the Keystore that is used when connecting to LDAP using START_TLS. -|`TLS - Keystore Password` | Password for the Keystore that is used when connecting to LDAP using START_TLS. -|`TLS - Keystore Type` | Type of the Keystore that is used when connecting to LDAP using START_TLS (i.e. JKS or PKCS12). -|`TLS - Truststore` | Path to the Truststore that is used when connecting to LDAP using START_TLS. -|`TLS - Truststore Password` | Password for the Truststore that is used when connecting to LDAP using START_TLS. -|`TLS - Truststore Type` | Type of the Truststore that is used when connecting to LDAP using START_TLS (i.e. JKS or PKCS12). -|`TLS - Client Auth` | Client authentication policy when connecting to LDAP using START_TLS. Possible values are REQUIRED, WANT, NONE. -|`TLS - Protocol` | Protocol to use when connecting to LDAP using START_TLS. (i.e. TLS, TLSv1.1, TLSv1.2, etc). +|`TLS - Keystore` | Path to the Keystore that is used when connecting to LDAP using LDAPS or START_TLS. +|`TLS - Keystore Password` | Password for the Keystore that is used when connecting to LDAP using LDAPS or START_TLS. +|`TLS - Keystore Type` | Type of the Keystore that is used when connecting to LDAP using LDAPS or START_TLS (i.e. JKS or PKCS12). +|`TLS - Truststore` | Path to the Truststore that is used when connecting to LDAP using LDAPS or START_TLS. +|`TLS - Truststore Password` | Password for the Truststore that is used when connecting to LDAP using LDAPS or START_TLS. +|`TLS - Truststore Type` | Type of the Truststore that is used when connecting to LDAP using LDAPS or START_TLS (i.e. JKS or PKCS12). +|`TLS - Client Auth` | Client authentication policy when connecting to LDAP using LDAPS or START_TLS. Possible values are REQUIRED, WANT, NONE. +|`TLS - Protocol` | Protocol to use when connecting to LDAP using LDAPS or START_TLS. (i.e. TLS, TLSv1.1, TLSv1.2, etc). |`TLS - Shutdown Gracefully` | Specifies whether the TLS should be shut down gracefully before the target context is closed. Defaults to false. |`Referral Strategy` | Strategy for handling referrals. Possible values are FOLLOW, IGNORE, THROW. |`Connect Timeout` | Duration of connect timeout. (i.e. 10 secs). diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml index 828868ac8e..fbfcfb4c56 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml @@ -23,25 +23,25 @@ Identity Provider for users logging in with username/password against an LDAP server. 'Authentication Strategy' - How the connection to the LDAP server is authenticated. Possible - values are ANONYMOUS, SIMPLE, or START_TLS. + values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS. 'Manager DN' - The DN of the manager that is used to bind to the LDAP server to search for users. 'Manager Password' - The password of the manager that is used to bind to the LDAP server to search for users. - 'TLS - Keystore' - Path to the Keystore that is used when connecting to LDAP using START_TLS. + 'TLS - Keystore' - Path to the Keystore that is used when connecting to LDAP using LDAPS or START_TLS. 'TLS - Keystore Password' - Password for the Keystore that is used when connecting to LDAP - using START_TLS. + using LDAPS or START_TLS. 'TLS - Keystore Type' - Type of the Keystore that is used when connecting to LDAP using - START_TLS (i.e. JKS or PKCS12). - 'TLS - Truststore' - Path to the Truststore that is used when connecting to LDAP using START_TLS. + LDAPS or START_TLS (i.e. JKS or PKCS12). + 'TLS - Truststore' - Path to the Truststore that is used when connecting to LDAP using LDAPS or START_TLS. 'TLS - Truststore Password' - Password for the Truststore that is used when connecting to - LDAP using START_TLS. + LDAP using LDAPS or START_TLS. 'TLS - Truststore Type' - Type of the Truststore that is used when connecting to LDAP using - START_TLS (i.e. JKS or PKCS12). - 'TLS - Client Auth' - Client authentication policy when connecting to LDAP using START_TLS. + LDAPS or START_TLS (i.e. JKS or PKCS12). + 'TLS - Client Auth' - Client authentication policy when connecting to LDAP using LDAPS or START_TLS. Possible values are REQUIRED, WANT, NONE. - 'TLS - Protocol' - Protocol to use when connecting to LDAP using START_TLS. (i.e. TLS, + 'TLS - Protocol' - Protocol to use when connecting to LDAP using LDAPS or START_TLS. (i.e. TLS, TLSv1.1, TLSv1.2, etc). 'TLS - Shutdown Gracefully' - Specifies whether the TLS should be shut down gracefully before the target context is closed. Defaults to false. diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java index 7124ce162c..fc64c4077d 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java @@ -23,5 +23,6 @@ public enum LdapAuthenticationStrategy { ANONYMOUS, SIMPLE, + LDAPS, START_TLS } diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java index 43387830f3..851cf0d1b3 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java @@ -47,6 +47,7 @@ import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.security.ldap.search.LdapUserSearch; import org.springframework.security.ldap.userdetails.LdapUserDetails; +import javax.naming.Context; import javax.net.ssl.SSLContext; import java.io.IOException; import java.security.KeyManagementException; @@ -96,11 +97,6 @@ public class LdapProvider implements LoginIdentityProvider { setTimeout(configurationContext, baseEnvironment, "Connect Timeout", "com.sun.jndi.ldap.connect.timeout"); setTimeout(configurationContext, baseEnvironment, "Read Timeout", "com.sun.jndi.ldap.read.timeout"); - // set the base environment is necessary - if (!baseEnvironment.isEmpty()) { - context.setBaseEnvironmentProperties(baseEnvironment); - } - // authentication strategy final String rawAuthenticationStrategy = configurationContext.getProperty("Authentication Strategy"); final LdapAuthenticationStrategy authenticationStrategy; @@ -126,6 +122,20 @@ public class LdapProvider implements LoginIdentityProvider { case SIMPLE: context.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy()); break; + case LDAPS: + context.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy()); + + // indicate a secure connection + baseEnvironment.put(Context.SECURITY_PROTOCOL, "ssl"); + + // get the configured ssl context + final SSLContext ldapsSslContext = getConfiguredSslContext(configurationContext); + if (ldapsSslContext != null) { + // initialize the ldaps socket factory prior to use + LdapsSocketFactory.initialize(ldapsSslContext.getSocketFactory()); + baseEnvironment.put("java.naming.ldap.factory.socket", LdapsSocketFactory.class.getName()); + } + break; case START_TLS: final AbstractTlsDirContextAuthenticationStrategy tlsAuthenticationStrategy = new DefaultTlsDirContextAuthenticationStrategy(); @@ -136,49 +146,13 @@ public class LdapProvider implements LoginIdentityProvider { tlsAuthenticationStrategy.setShutdownTlsGracefully(shutdownGracefully); } - final String rawKeystore = configurationContext.getProperty("TLS - Keystore"); - final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password"); - final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type"); - final String rawTruststore = configurationContext.getProperty("TLS - Truststore"); - final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password"); - final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type"); - final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth"); - final String rawProtocol = configurationContext.getProperty("TLS - Protocol"); - - final ClientAuth clientAuth; - if (StringUtils.isBlank(rawClientAuth)) { - clientAuth = ClientAuth.NONE; - } else { - try { - clientAuth = ClientAuth.valueOf(rawClientAuth); - } catch (final IllegalArgumentException iae) { - throw new ProviderCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]", - rawClientAuth, StringUtils.join(ClientAuth.values(), ", "))); - } - } - - // ensure the protocol is specified - if (StringUtils.isBlank(rawProtocol)) { - throw new ProviderCreationException("TLS - Protocol must be specified."); - } - - try { - final SSLContext sslContext; - if (StringUtils.isBlank(rawKeystore)) { - sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol); - } else { - if (StringUtils.isBlank(rawTruststore)) { - sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol); - } else { - sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, - rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol); - } - } - tlsAuthenticationStrategy.setSslSocketFactory(sslContext.getSocketFactory()); - } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) { - throw new ProviderCreationException(e.getMessage(), e); + // get the configured ssl context + final SSLContext startTlsSslContext = getConfiguredSslContext(configurationContext); + if (startTlsSslContext != null) { + tlsAuthenticationStrategy.setSslSocketFactory(startTlsSslContext.getSocketFactory()); } + // set the authentication strategy context.setAuthenticationStrategy(tlsAuthenticationStrategy); break; } @@ -241,6 +215,11 @@ public class LdapProvider implements LoginIdentityProvider { } } + // set the base environment is necessary + if (!baseEnvironment.isEmpty()) { + context.setBaseEnvironmentProperties(baseEnvironment); + } + try { // handling initializing beans context.afterPropertiesSet(); @@ -269,6 +248,56 @@ public class LdapProvider implements LoginIdentityProvider { } } + private SSLContext getConfiguredSslContext(final LoginIdentityProviderConfigurationContext configurationContext) { + final String rawKeystore = configurationContext.getProperty("TLS - Keystore"); + final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password"); + final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type"); + final String rawTruststore = configurationContext.getProperty("TLS - Truststore"); + final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password"); + final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type"); + final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth"); + final String rawProtocol = configurationContext.getProperty("TLS - Protocol"); + + // create the ssl context + final SSLContext sslContext; + try { + if (StringUtils.isBlank(rawKeystore) && StringUtils.isBlank(rawTruststore)) { + sslContext = null; + } else { + // ensure the protocol is specified + if (StringUtils.isBlank(rawProtocol)) { + throw new ProviderCreationException("TLS - Protocol must be specified."); + } + + if (StringUtils.isBlank(rawKeystore)) { + sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol); + } else if (StringUtils.isBlank(rawTruststore)) { + sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol); + } else { + // determine the client auth if specified + final ClientAuth clientAuth; + if (StringUtils.isBlank(rawClientAuth)) { + clientAuth = ClientAuth.NONE; + } else { + try { + clientAuth = ClientAuth.valueOf(rawClientAuth); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]", + rawClientAuth, StringUtils.join(ClientAuth.values(), ", "))); + } + } + + sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, + rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol); + } + } + } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) { + throw new ProviderCreationException(e.getMessage(), e); + } + + return sslContext; + } + @Override public final AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException { if (provider == null) { diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapsSocketFactory.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapsSocketFactory.java new file mode 100644 index 0000000000..7c4eb873f3 --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapsSocketFactory.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.ldap; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * SSLSocketFactory used when connecting to a Directory Server over LDAPS. + */ +public class LdapsSocketFactory extends SSLSocketFactory { + + // singleton + private static LdapsSocketFactory instance; + + // delegate + private SSLSocketFactory delegate; + + /** + * Initializes the LdapsSocketFactory with the specified SSLSocketFactory. The specified + * socket factory will be used as a delegate for all subsequent instances of this class. + * + * @param sslSocketFactory delegate socket factory + */ + public static void initialize(final SSLSocketFactory sslSocketFactory) { + instance = new LdapsSocketFactory(sslSocketFactory); + } + + /** + * Gets the LdapsSocketFactory that was previously initialized. + * + * @return socket factory + */ + public static SocketFactory getDefault() { + return instance; + } + + /** + * Creates a new LdapsSocketFactory. + * + * @param sslSocketFactory delegate socket factory + */ + private LdapsSocketFactory(final SSLSocketFactory sslSocketFactory) { + delegate = sslSocketFactory; + } + + // delegate methods + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException { + return delegate.createSocket(socket, string, i, bln); + } + + @Override + public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException { + return delegate.createSocket(ia, i, ia1, i1); + } + + @Override + public Socket createSocket(InetAddress ia, int i) throws IOException { + return delegate.createSocket(ia, i); + } + + @Override + public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException { + return delegate.createSocket(string, i, ia, i1); + } + + @Override + public Socket createSocket(String string, int i) throws IOException, UnknownHostException { + return delegate.createSocket(string, i); + } + + @Override + public Socket createSocket() throws IOException { + return delegate.createSocket(); + } +}