NIFI-4655: This closes #2347. Fixed NPE in the InvokeHTTP processor caused when an SSLContextService was assigned without keystore properties

- Added check for keystore properties and only initialized keystore when necessary.
- Added TestInvokeHttpTwoWaySSL test class to test with two-way SSL
- Modified TestInvokeHttpSSL to test with one-way SSL

Signed-off-by: joewitt <joewitt@apache.org>
This commit is contained in:
Josh Anderton 2017-12-17 20:01:34 -06:00 committed by joewitt
parent 9217e2fc63
commit f50a07ec88
3 changed files with 130 additions and 35 deletions

View File

@ -62,6 +62,7 @@ import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
@ -567,6 +568,14 @@ public final class InvokeHTTP extends AbstractProcessor {
*/ */
private void setSslSocketFactory(OkHttpClient.Builder okHttpClientBuilder, SSLContextService sslService, SSLContext sslContext) private void setSslSocketFactory(OkHttpClient.Builder okHttpClientBuilder, SSLContextService sslService, SSLContext sslContext)
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
// initialize the KeyManager array to null and we will overwrite later if a keystore is loaded
KeyManager[] keyManagers = null;
// we will only initialize the keystore if properties have been supplied by the SSLContextService
if (sslService.isKeyStoreConfigured()) {
final String keystoreLocation = sslService.getKeyStoreFile(); final String keystoreLocation = sslService.getKeyStoreFile();
final String keystorePass = sslService.getKeyStorePassword(); final String keystorePass = sslService.getKeyStorePassword();
final String keystoreType = sslService.getKeyStoreType(); final String keystoreType = sslService.getKeyStoreType();
@ -578,18 +587,21 @@ public final class InvokeHTTP extends AbstractProcessor {
keyStore.load(keyStoreStream, keystorePass.toCharArray()); keyStore.load(keyStoreStream, keystorePass.toCharArray());
} }
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keystorePass.toCharArray()); keyManagerFactory.init(keyStore, keystorePass.toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
}
// we will only initialize the truststure if properties have been supplied by the SSLContextService
if (sslService.isTrustStoreConfigured()) {
// load truststore // load truststore
final String truststoreLocation = sslService.getTrustStoreFile(); final String truststoreLocation = sslService.getTrustStoreFile();
final String truststorePass = sslService.getTrustStorePassword(); final String truststorePass = sslService.getTrustStorePassword();
final String truststoreType = sslService.getTrustStoreType(); final String truststoreType = sslService.getTrustStoreType();
KeyStore truststore = KeyStore.getInstance(truststoreType); KeyStore truststore = KeyStore.getInstance(truststoreType);
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
truststore.load(new FileInputStream(truststoreLocation), truststorePass.toCharArray()); truststore.load(new FileInputStream(truststoreLocation), truststorePass.toCharArray());
trustManagerFactory.init(truststore); trustManagerFactory.init(truststore);
}
/* /*
TrustManagerFactory.getTrustManagers returns a trust manager for each type of trust material. Since we are getting a trust manager factory that uses "X509" TrustManagerFactory.getTrustManagers returns a trust manager for each type of trust material. Since we are getting a trust manager factory that uses "X509"
@ -605,7 +617,8 @@ public final class InvokeHTTP extends AbstractProcessor {
throw new IllegalStateException("List of trust managers is null"); throw new IllegalStateException("List of trust managers is null");
} }
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); // if keystore properties were not supplied, the keyManagers array will be null
sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
okHttpClientBuilder.sslSocketFactory(sslSocketFactory, x509TrustManager); okHttpClientBuilder.sslSocketFactory(sslSocketFactory, x509TrustManager);

View File

@ -29,9 +29,15 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/**
* Executes the same tests as TestInvokeHttp but with one-way SSL enabled. The Jetty server created for these tests
* will not require client certificates and the client will not use keystore properties in the SSLContextService.
*/
public class TestInvokeHttpSSL extends TestInvokeHttpCommon { public class TestInvokeHttpSSL extends TestInvokeHttpCommon {
private static Map<String, String> sslProperties; protected static Map<String, String> sslProperties;
protected static Map<String, String> serverSslProperties;
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
@ -41,7 +47,8 @@ public class TestInvokeHttpSSL extends TestInvokeHttpCommon {
// create the SSL properties, which basically store keystore / trustore information // create the SSL properties, which basically store keystore / trustore information
// this is used by the StandardSSLContextService and the Jetty Server // this is used by the StandardSSLContextService and the Jetty Server
sslProperties = createSslProperties(); serverSslProperties = createServerSslProperties(false);
sslProperties = createSslProperties(false);
// create a Jetty server on a random port // create a Jetty server on a random port
server = createServer(); server = createServer();
@ -81,15 +88,39 @@ public class TestInvokeHttpSSL extends TestInvokeHttpCommon {
runner.shutdown(); runner.shutdown();
} }
private static TestServer createServer() throws IOException { protected static TestServer createServer() throws IOException {
return new TestServer(sslProperties); return new TestServer(serverSslProperties);
} }
private static Map<String, String> createSslProperties() { protected static Map<String, String> createServerSslProperties(boolean clientAuth) {
final Map<String, String> map = new HashMap<>(); final Map<String, String> map = new HashMap<>();
// if requesting client auth then we must also provide a truststore
if (clientAuth) {
map.put(TestServer.NEED_CLIENT_AUTH, Boolean.toString(true));
map.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/localhost-ts.jks");
map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "localtest");
map.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");
} else {
map.put(TestServer.NEED_CLIENT_AUTH, Boolean.toString(false));
}
// keystore is always required for the server SSL properties
map.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/localhost-ks.jks"); map.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/localhost-ks.jks");
map.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "localtest"); map.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "localtest");
map.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS"); map.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS");
return map;
}
protected static Map<String, String> createSslProperties(boolean clientAuth) {
final Map<String, String> map = new HashMap<>();
// if requesting client auth then we must provide a keystore
if (clientAuth) {
map.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/localhost-ks.jks");
map.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "localtest");
map.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS");
}
// truststore is always required for the client SSL properties
map.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/localhost-ts.jks"); map.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/localhost-ts.jks");
map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "localtest"); map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "localtest");
map.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS"); map.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");

View File

@ -0,0 +1,51 @@
/*
* 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.processors.standard;
import org.junit.BeforeClass;
/**
* This is probably overkill but in keeping with the same pattern as the TestInvokeHttp and TestInvokeHttpSSL class,
* we will execute the same tests using two-way SSL. The Jetty server created for these tests will require client
* certificates and the client will utilize keystore properties in the SSLContextService.
*/
public class TestInvokeHttpTwoWaySSL extends TestInvokeHttpSSL {
@BeforeClass
public static void beforeClass() throws Exception {
// useful for verbose logging output
// don't commit this with this property enabled, or any 'mvn test' will be really verbose
// System.setProperty("org.slf4j.simpleLogger.log.nifi.processors.standard", "debug");
// create the SSL properties, which basically store keystore / trustore information
// this is used by the StandardSSLContextService and the Jetty Server
serverSslProperties = createServerSslProperties(true);
sslProperties = createSslProperties(true);
// create a Jetty server on a random port
server = createServer();
server.startServer();
// Allow time for the server to start
Thread.sleep(500);
// this is the base url with the random port
url = server.getSecureUrl();
}
}