mirror of https://github.com/apache/nifi.git
NIFI-2528 Added RestrictedSSLContextService interface with implementation. Changed ListenHTTP to allow only new StandardRestrictedSSLContextService.
This closes #1986. Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
parent
e62417ea6b
commit
d28e61c5dd
|
@ -34,6 +34,7 @@ import org.apache.nifi.processor.Relationship;
|
||||||
import org.apache.nifi.processor.util.StandardValidators;
|
import org.apache.nifi.processor.util.StandardValidators;
|
||||||
import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet;
|
import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet;
|
||||||
import org.apache.nifi.processors.standard.servlets.ListenHTTPServlet;
|
import org.apache.nifi.processors.standard.servlets.ListenHTTPServlet;
|
||||||
|
import org.apache.nifi.ssl.RestrictedSSLContextService;
|
||||||
import org.apache.nifi.ssl.SSLContextService;
|
import org.apache.nifi.ssl.SSLContextService;
|
||||||
import org.apache.nifi.stream.io.LeakyBucketStreamThrottler;
|
import org.apache.nifi.stream.io.LeakyBucketStreamThrottler;
|
||||||
import org.apache.nifi.stream.io.StreamThrottler;
|
import org.apache.nifi.stream.io.StreamThrottler;
|
||||||
|
@ -118,7 +119,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
||||||
.name("SSL Context Service")
|
.name("SSL Context Service")
|
||||||
.description("The Controller Service to use in order to obtain an SSL Context")
|
.description("The Controller Service to use in order to obtain an SSL Context")
|
||||||
.required(false)
|
.required(false)
|
||||||
.identifiesControllerService(SSLContextService.class)
|
.identifiesControllerService(RestrictedSSLContextService.class)
|
||||||
.build();
|
.build();
|
||||||
public static final PropertyDescriptor HEADERS_AS_ATTRIBUTES_REGEX = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor HEADERS_AS_ATTRIBUTES_REGEX = new PropertyDescriptor.Builder()
|
||||||
.name("HTTP Headers to receive as Attributes (Regex)")
|
.name("HTTP Headers to receive as Attributes (Regex)")
|
||||||
|
@ -227,6 +228,10 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
||||||
contextFactory.setKeyStoreType(keyStoreType);
|
contextFactory.setKeyStoreType(keyStoreType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sslContextService != null) {
|
||||||
|
contextFactory.setProtocol(sslContextService.getSslAlgorithm());
|
||||||
|
}
|
||||||
|
|
||||||
// thread pool for the jetty instance
|
// thread pool for the jetty instance
|
||||||
final QueuedThreadPool threadPool = new QueuedThreadPool();
|
final QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||||
threadPool.setName(String.format("%s (%s) Web Server", getClass().getSimpleName(), getIdentifier()));
|
threadPool.setName(String.format("%s (%s) Web Server", getClass().getSimpleName(), getIdentifier()));
|
||||||
|
@ -249,6 +254,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor {
|
||||||
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
|
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
|
||||||
// build the connector
|
// build the connector
|
||||||
|
|
||||||
connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration));
|
connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,13 @@ import org.apache.nifi.processor.ProcessContext;
|
||||||
import org.apache.nifi.processor.ProcessSessionFactory;
|
import org.apache.nifi.processor.ProcessSessionFactory;
|
||||||
import org.apache.nifi.remote.io.socket.NetworkUtils;
|
import org.apache.nifi.remote.io.socket.NetworkUtils;
|
||||||
import org.apache.nifi.reporting.InitializationException;
|
import org.apache.nifi.reporting.InitializationException;
|
||||||
|
import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
|
||||||
import org.apache.nifi.ssl.SSLContextService;
|
import org.apache.nifi.ssl.SSLContextService;
|
||||||
import org.apache.nifi.ssl.StandardSSLContextService;
|
import org.apache.nifi.ssl.StandardSSLContextService;
|
||||||
import org.apache.nifi.util.MockFlowFile;
|
import org.apache.nifi.util.MockFlowFile;
|
||||||
import org.apache.nifi.util.TestRunner;
|
import org.apache.nifi.util.TestRunner;
|
||||||
import org.apache.nifi.util.TestRunners;
|
import org.apache.nifi.util.TestRunners;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -35,10 +37,15 @@ import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS;
|
import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
|
||||||
public class TestListenHTTP {
|
public class TestListenHTTP {
|
||||||
|
private static final String SSL_CONTEXT_SERVICE_IDENTIFIER = "ssl-context";
|
||||||
|
|
||||||
private static final String HTTP_POST_METHOD = "POST";
|
private static final String HTTP_POST_METHOD = "POST";
|
||||||
private static final String HTTP_BASE_PATH = "basePath";
|
private static final String HTTP_BASE_PATH = "basePath";
|
||||||
|
@ -64,9 +71,13 @@ public class TestListenHTTP {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void teardown() {
|
||||||
|
proc.shutdownHttpServer();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPOSTRequestsReceivedWithoutEL() throws Exception {
|
public void testPOSTRequestsReceivedWithoutEL() throws Exception {
|
||||||
|
|
||||||
runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
|
runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
|
||||||
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
|
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
|
||||||
|
|
||||||
|
@ -75,30 +86,79 @@ public class TestListenHTTP {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPOSTRequestsReceivedWithEL() throws Exception {
|
public void testPOSTRequestsReceivedWithEL() throws Exception {
|
||||||
|
|
||||||
runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
|
runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
|
||||||
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL);
|
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL);
|
||||||
|
runner.assertValid();
|
||||||
|
|
||||||
testPOSTRequestsReceived();
|
testPOSTRequestsReceived();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurePOSTRequestsReceivedWithoutEL() throws Exception {
|
||||||
|
SSLContextService sslContextService = configureProcessorSslContextService();
|
||||||
|
runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
|
||||||
|
runner.enableControllerService(sslContextService);
|
||||||
|
|
||||||
|
runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
|
||||||
|
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
|
||||||
|
runner.assertValid();
|
||||||
|
|
||||||
|
testPOSTRequestsReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurePOSTRequestsReceivedWithEL() throws Exception {
|
||||||
|
SSLContextService sslContextService = configureProcessorSslContextService();
|
||||||
|
runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
|
||||||
|
runner.enableControllerService(sslContextService);
|
||||||
|
|
||||||
|
runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
|
||||||
|
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL);
|
||||||
|
runner.assertValid();
|
||||||
|
|
||||||
|
testPOSTRequestsReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecureInvalidSSLConfiguration() throws Exception {
|
||||||
|
SSLContextService sslContextService = configureInvalidProcessorSslContextService();
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.2");
|
||||||
|
runner.enableControllerService(sslContextService);
|
||||||
|
|
||||||
|
runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
|
||||||
|
runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL);
|
||||||
|
runner.assertNotValid();
|
||||||
|
}
|
||||||
|
|
||||||
private int executePOST(String message) throws Exception {
|
private int executePOST(String message) throws Exception {
|
||||||
|
final SSLContextService sslContextService = runner.getControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, SSLContextService.class);
|
||||||
|
final boolean secure = (sslContextService != null);
|
||||||
|
final String scheme = secure ? "https" : "http";
|
||||||
|
final URL url = new URL(scheme + "://localhost:" + availablePort + "/" + HTTP_BASE_PATH);
|
||||||
|
HttpURLConnection connection;
|
||||||
|
|
||||||
URL url= new URL("http://localhost:" + availablePort + "/" + HTTP_BASE_PATH);
|
if(secure) {
|
||||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
final HttpsURLConnection sslCon = (HttpsURLConnection) url.openConnection();
|
||||||
|
final SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.WANT);
|
||||||
|
sslCon.setSSLSocketFactory(sslContext.getSocketFactory());
|
||||||
|
connection = sslCon;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
}
|
||||||
|
connection.setRequestMethod(HTTP_POST_METHOD);
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
|
final DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
|
||||||
|
|
||||||
con.setRequestMethod(HTTP_POST_METHOD);
|
|
||||||
con.setDoOutput(true);
|
|
||||||
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
|
|
||||||
if (message!=null) {
|
if (message!=null) {
|
||||||
wr.writeBytes(message);
|
wr.writeBytes(message);
|
||||||
}
|
}
|
||||||
wr.flush();
|
wr.flush();
|
||||||
wr.close();
|
wr.close();
|
||||||
|
return connection.getResponseCode();
|
||||||
return con.getResponseCode();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testPOSTRequestsReceived() throws Exception {
|
private void testPOSTRequestsReceived() throws Exception {
|
||||||
final List<String> messages = new ArrayList<>();
|
final List<String> messages = new ArrayList<>();
|
||||||
messages.add("payload 1");
|
messages.add("payload 1");
|
||||||
|
@ -122,7 +182,7 @@ public class TestListenHTTP {
|
||||||
|
|
||||||
final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory();
|
final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory();
|
||||||
final ProcessContext context = runner.getProcessContext();
|
final ProcessContext context = runner.getProcessContext();
|
||||||
proc.createHttpServer(context);
|
proc.createHttpServer(context);
|
||||||
|
|
||||||
Runnable sendMessagestoWebServer = () -> {
|
Runnable sendMessagestoWebServer = () -> {
|
||||||
try {
|
try {
|
||||||
|
@ -151,18 +211,30 @@ public class TestListenHTTP {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSLContextService configureProcessorSslContextService() throws InitializationException {
|
private SSLContextService configureProcessorSslContextService() throws InitializationException {
|
||||||
final SSLContextService sslContextService = new StandardSSLContextService();
|
final SSLContextService sslContextService = new StandardRestrictedSSLContextService();
|
||||||
runner.addControllerService("ssl-context", sslContextService);
|
runner.addControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, sslContextService);
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, "src/test/resources/localhost-ts.jks");
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, "src/test/resources/localhost-ts.jks");
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, "localtest");
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, "localtest");
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, "JKS");
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, "JKS");
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, "src/test/resources/localhost-ks.jks");
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, "src/test/resources/localhost-ks.jks");
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, "localtest");
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, "localtest");
|
||||||
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
|
||||||
runner.enableControllerService(sslContextService);
|
|
||||||
|
|
||||||
runner.setProperty(ListenHTTP.SSL_CONTEXT_SERVICE, "ssl-context");
|
runner.setProperty(ListenHTTP.SSL_CONTEXT_SERVICE, SSL_CONTEXT_SERVICE_IDENTIFIER);
|
||||||
return sslContextService;
|
return sslContextService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SSLContextService configureInvalidProcessorSslContextService() throws InitializationException {
|
||||||
|
final SSLContextService sslContextService = new StandardSSLContextService();
|
||||||
|
runner.addControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, sslContextService);
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, "src/test/resources/localhost-ts.jks");
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, "localtest");
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, "JKS");
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, "src/test/resources/localhost-ks.jks");
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, "localtest");
|
||||||
|
runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
|
||||||
|
|
||||||
|
runner.setProperty(ListenHTTP.SSL_CONTEXT_SERVICE, SSL_CONTEXT_SERVICE_IDENTIFIER);
|
||||||
|
return sslContextService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||||
|
import org.apache.nifi.annotation.documentation.Tags;
|
||||||
|
import org.apache.nifi.components.PropertyDescriptor;
|
||||||
|
import org.apache.nifi.components.ValidationContext;
|
||||||
|
import org.apache.nifi.processor.util.StandardValidators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is functionally the same as {@link StandardSSLContextService}, but it restricts the allowable
|
||||||
|
* values that can be selected for TLS/SSL protocols.
|
||||||
|
*/
|
||||||
|
@Tags({"tls", "ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs"})
|
||||||
|
@CapabilityDescription("Restricted implementation of the SSLContextService. Provides the ability to configure "
|
||||||
|
+ "keystore and/or truststore properties once and reuse that configuration throughout the application, "
|
||||||
|
+ "but only allows a restricted set of TLS/SSL protocols to be chosen (no SSL protocols are supported). The set of protocols selectable will "
|
||||||
|
+ "evolve over time as new protocols emerge and older protocols are deprecated. This service is recommended "
|
||||||
|
+ "over StandardSSLContextService if a component doesn't expect to communicate with legacy systems since it is "
|
||||||
|
+ "unlikely that legacy systems will support these protocols.")
|
||||||
|
public class StandardRestrictedSSLContextService extends StandardSSLContextService implements RestrictedSSLContextService {
|
||||||
|
|
||||||
|
public static final PropertyDescriptor RESTRICTED_SSL_ALGORITHM = new PropertyDescriptor.Builder()
|
||||||
|
.name("SSL Protocol")
|
||||||
|
.displayName("TLS Protocol")
|
||||||
|
.defaultValue("TLS")
|
||||||
|
.required(false)
|
||||||
|
.allowableValues(RestrictedSSLContextService.buildAlgorithmAllowableValues())
|
||||||
|
.description("The algorithm to use for this SSL context. By default, this will choose the highest supported TLS protocol version.")
|
||||||
|
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||||
|
.sensitive(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final List<PropertyDescriptor> properties;
|
||||||
|
|
||||||
|
static {
|
||||||
|
List<PropertyDescriptor> props = new ArrayList<>();
|
||||||
|
props.add(KEYSTORE);
|
||||||
|
props.add(KEYSTORE_PASSWORD);
|
||||||
|
props.add(KEY_PASSWORD);
|
||||||
|
props.add(KEYSTORE_TYPE);
|
||||||
|
props.add(TRUSTSTORE);
|
||||||
|
props.add(TRUSTSTORE_PASSWORD);
|
||||||
|
props.add(TRUSTSTORE_TYPE);
|
||||||
|
props.add(RESTRICTED_SSL_ALGORITHM);
|
||||||
|
properties = Collections.unmodifiableList(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSslAlgorithm() {
|
||||||
|
return configContext.getProperty(RESTRICTED_SSL_ALGORITHM).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getSSLProtocolForValidation(final ValidationContext validationContext) {
|
||||||
|
return validationContext.getProperty(RESTRICTED_SSL_ALGORITHM).getValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,20 +18,15 @@ package org.apache.nifi.ssl;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||||
import org.apache.nifi.annotation.documentation.Tags;
|
import org.apache.nifi.annotation.documentation.Tags;
|
||||||
import org.apache.nifi.annotation.lifecycle.OnEnabled;
|
import org.apache.nifi.annotation.lifecycle.OnEnabled;
|
||||||
import org.apache.nifi.components.AllowableValue;
|
|
||||||
import org.apache.nifi.components.PropertyDescriptor;
|
import org.apache.nifi.components.PropertyDescriptor;
|
||||||
import org.apache.nifi.components.PropertyValue;
|
import org.apache.nifi.components.PropertyValue;
|
||||||
import org.apache.nifi.components.ValidationContext;
|
import org.apache.nifi.components.ValidationContext;
|
||||||
|
@ -48,7 +43,10 @@ import org.apache.nifi.security.util.SslContextFactory;
|
||||||
|
|
||||||
@Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs", "tls"})
|
@Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs", "tls"})
|
||||||
@CapabilityDescription("Standard implementation of the SSLContextService. Provides the ability to configure "
|
@CapabilityDescription("Standard implementation of the SSLContextService. Provides the ability to configure "
|
||||||
+ "keystore and/or truststore properties once and reuse that configuration throughout the application")
|
+ "keystore and/or truststore properties once and reuse that configuration throughout the application. "
|
||||||
|
+ "This service can be used to communicate with both legacy and modern systems. If you only need to "
|
||||||
|
+ "communicate with non-legacy systems, then the StandardRestrictedSSLContextService is recommended as it only "
|
||||||
|
+ "allows a specific set of SSL protocols to be chosen.")
|
||||||
public class StandardSSLContextService extends AbstractControllerService implements SSLContextService {
|
public class StandardSSLContextService extends AbstractControllerService implements SSLContextService {
|
||||||
|
|
||||||
public static final String STORE_TYPE_JKS = "JKS";
|
public static final String STORE_TYPE_JKS = "JKS";
|
||||||
|
@ -110,14 +108,14 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
.displayName("TLS Protocol")
|
.displayName("TLS Protocol")
|
||||||
.defaultValue("TLS")
|
.defaultValue("TLS")
|
||||||
.required(false)
|
.required(false)
|
||||||
.allowableValues(buildAlgorithmAllowableValues())
|
.allowableValues(SSLContextService.buildAlgorithmAllowableValues())
|
||||||
.description("The algorithm to use for this TLS/SSL context")
|
.description("The algorithm to use for this TLS/SSL context")
|
||||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||||
.sensitive(false)
|
.sensitive(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final List<PropertyDescriptor> properties;
|
private static final List<PropertyDescriptor> properties;
|
||||||
private ConfigurationContext configContext;
|
protected ConfigurationContext configContext;
|
||||||
private boolean isValidated;
|
private boolean isValidated;
|
||||||
|
|
||||||
// TODO: This can be made configurable if necessary
|
// TODO: This can be made configurable if necessary
|
||||||
|
@ -252,8 +250,12 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
return VALIDATION_CACHE_EXPIRATION;
|
return VALIDATION_CACHE_EXPIRATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getSSLProtocolForValidation(final ValidationContext validationContext) {
|
||||||
|
return validationContext.getProperty(SSL_ALGORITHM).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
private void verifySslConfig(final ValidationContext validationContext) throws ProcessException {
|
private void verifySslConfig(final ValidationContext validationContext) throws ProcessException {
|
||||||
final String protocol = validationContext.getProperty(SSL_ALGORITHM).getValue();
|
final String protocol = getSSLProtocolForValidation(validationContext);
|
||||||
try {
|
try {
|
||||||
final PropertyValue keyPasswdProp = validationContext.getProperty(KEY_PASSWORD);
|
final PropertyValue keyPasswdProp = validationContext.getProperty(KEY_PASSWORD);
|
||||||
final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null;
|
final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null;
|
||||||
|
@ -295,7 +297,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException {
|
public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException {
|
||||||
final String protocol = configContext.getProperty(SSL_ALGORITHM).getValue();
|
final String protocol = getSslAlgorithm();
|
||||||
try {
|
try {
|
||||||
final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD);
|
final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD);
|
||||||
final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null;
|
final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null;
|
||||||
|
@ -458,36 +460,6 @@ public class StandardSSLContextService extends AbstractControllerService impleme
|
||||||
KEYSTORE, TRUSTSTORE
|
KEYSTORE, TRUSTSTORE
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AllowableValue[] buildAlgorithmAllowableValues() {
|
|
||||||
final Set<String> supportedProtocols = new HashSet<>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prepopulate protocols with generic instance types commonly used
|
|
||||||
* see: http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
|
|
||||||
*/
|
|
||||||
supportedProtocols.add("SSL");
|
|
||||||
supportedProtocols.add("TLS");
|
|
||||||
|
|
||||||
// Determine those provided by the JVM on the system
|
|
||||||
try {
|
|
||||||
supportedProtocols.addAll(Arrays.asList(SSLContext.getDefault().createSSLEngine().getSupportedProtocols()));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
// ignored as default is used
|
|
||||||
}
|
|
||||||
|
|
||||||
final int numProtocols = supportedProtocols.size();
|
|
||||||
|
|
||||||
// Sort for consistent presentation in configuration views
|
|
||||||
final List<String> supportedProtocolList = new ArrayList<>(supportedProtocols);
|
|
||||||
Collections.sort(supportedProtocolList);
|
|
||||||
|
|
||||||
final List<AllowableValue> protocolAllowableValues = new ArrayList<>();
|
|
||||||
for (final String protocol : supportedProtocolList) {
|
|
||||||
protocolAllowableValues.add(new AllowableValue(protocol));
|
|
||||||
}
|
|
||||||
return protocolAllowableValues.toArray(new AllowableValue[numProtocols]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SSLContextService[id=" + getIdentifier() + "]";
|
return "SSLContextService[id=" + getIdentifier() + "]";
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
org.apache.nifi.ssl.StandardSSLContextService
|
org.apache.nifi.ssl.StandardSSLContextService
|
||||||
|
org.apache.nifi.ssl.StandardRestrictedSSLContextService
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.core.IsNull.notNullValue;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.apache.nifi.components.AllowableValue;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class RestrictedSSLContextServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTLSAlgorithms() {
|
||||||
|
final Set<String> expected = new HashSet<>();
|
||||||
|
expected.add("TLS");
|
||||||
|
expected.add("TLSv1.2");
|
||||||
|
|
||||||
|
final AllowableValue[] allowableValues = RestrictedSSLContextService.buildAlgorithmAllowableValues();
|
||||||
|
assertThat(allowableValues, notNullValue());
|
||||||
|
assertThat(allowableValues.length, equalTo(expected.size()));
|
||||||
|
for(final AllowableValue value : allowableValues) {
|
||||||
|
assertTrue(expected.contains(value.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.ssl;
|
package org.apache.nifi.ssl;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.core.IsNull.notNullValue;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@ -23,9 +26,17 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.apache.nifi.components.AllowableValue;
|
||||||
import org.apache.nifi.components.ValidationContext;
|
import org.apache.nifi.components.ValidationContext;
|
||||||
import org.apache.nifi.components.ValidationResult;
|
import org.apache.nifi.components.ValidationResult;
|
||||||
import org.apache.nifi.reporting.InitializationException;
|
import org.apache.nifi.reporting.InitializationException;
|
||||||
|
@ -311,4 +322,22 @@ public class SSLContextServiceTest {
|
||||||
Assert.fail("Should not have thrown a exception " + e.getMessage());
|
Assert.fail("Should not have thrown a exception " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSSLAlgorithms() throws NoSuchAlgorithmException {
|
||||||
|
final AllowableValue[] allowableValues = SSLContextService.buildAlgorithmAllowableValues();
|
||||||
|
|
||||||
|
// we expect TLS, SSL, and all available configured JVM protocols
|
||||||
|
final Set<String> expected = new HashSet<>();
|
||||||
|
expected.add("SSL");
|
||||||
|
expected.add("TLS");
|
||||||
|
final String[] supportedProtocols = SSLContext.getDefault().createSSLEngine().getSupportedProtocols();
|
||||||
|
expected.addAll(Arrays.asList(supportedProtocols));
|
||||||
|
|
||||||
|
assertThat(allowableValues, notNullValue());
|
||||||
|
assertThat(allowableValues.length, equalTo(expected.size()));
|
||||||
|
for(final AllowableValue value : allowableValues) {
|
||||||
|
assertTrue(expected.contains(value.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.ssl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.nifi.components.AllowableValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple extension of the regular {@link SSLContextService} to allow for restricted implementations
|
||||||
|
* of that interface.
|
||||||
|
*/
|
||||||
|
public interface RestrictedSSLContextService extends SSLContextService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a restricted set of allowable TLS protocol algorithms.
|
||||||
|
*
|
||||||
|
* @return the computed set of allowable values
|
||||||
|
*/
|
||||||
|
static AllowableValue[] buildAlgorithmAllowableValues() {
|
||||||
|
final Set<String> supportedProtocols = new HashSet<>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepopulate protocols with generic instance types commonly used
|
||||||
|
* see: http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
|
||||||
|
*/
|
||||||
|
supportedProtocols.add("TLS");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add specifically supported TLS versions
|
||||||
|
*/
|
||||||
|
supportedProtocols.add("TLSv1.2");
|
||||||
|
|
||||||
|
final int numProtocols = supportedProtocols.size();
|
||||||
|
|
||||||
|
// Sort for consistent presentation in configuration views
|
||||||
|
final List<String> supportedProtocolList = new ArrayList<>(supportedProtocols);
|
||||||
|
Collections.sort(supportedProtocolList);
|
||||||
|
|
||||||
|
final List<AllowableValue> protocolAllowableValues = new ArrayList<>();
|
||||||
|
for (final String protocol : supportedProtocolList) {
|
||||||
|
protocolAllowableValues.add(new AllowableValue(protocol));
|
||||||
|
}
|
||||||
|
return protocolAllowableValues.toArray(new AllowableValue[numProtocols]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,19 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.ssl;
|
package org.apache.nifi.ssl;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||||
import org.apache.nifi.annotation.documentation.Tags;
|
import org.apache.nifi.annotation.documentation.Tags;
|
||||||
|
import org.apache.nifi.components.AllowableValue;
|
||||||
import org.apache.nifi.controller.ControllerService;
|
import org.apache.nifi.controller.ControllerService;
|
||||||
import org.apache.nifi.processor.exception.ProcessException;
|
import org.apache.nifi.processor.exception.ProcessException;
|
||||||
|
|
||||||
|
@ -60,4 +69,39 @@ public interface SSLContextService extends ControllerService {
|
||||||
public boolean isKeyStoreConfigured();
|
public boolean isKeyStoreConfigured();
|
||||||
|
|
||||||
String getSslAlgorithm();
|
String getSslAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a set of allowable TLS/SSL protocol algorithms based on JVM configuration.
|
||||||
|
*
|
||||||
|
* @return the computed set of allowable values
|
||||||
|
*/
|
||||||
|
static AllowableValue[] buildAlgorithmAllowableValues() {
|
||||||
|
final Set<String> supportedProtocols = new HashSet<>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepopulate protocols with generic instance types commonly used
|
||||||
|
* see: http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
|
||||||
|
*/
|
||||||
|
supportedProtocols.add("TLS");
|
||||||
|
supportedProtocols.add("SSL");
|
||||||
|
|
||||||
|
// Determine those provided by the JVM on the system
|
||||||
|
try {
|
||||||
|
supportedProtocols.addAll(Arrays.asList(SSLContext.getDefault().createSSLEngine().getSupportedProtocols()));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// ignored as default is used
|
||||||
|
}
|
||||||
|
|
||||||
|
final int numProtocols = supportedProtocols.size();
|
||||||
|
|
||||||
|
// Sort for consistent presentation in configuration views
|
||||||
|
final List<String> supportedProtocolList = new ArrayList<>(supportedProtocols);
|
||||||
|
Collections.sort(supportedProtocolList);
|
||||||
|
|
||||||
|
final List<AllowableValue> protocolAllowableValues = new ArrayList<>();
|
||||||
|
for (final String protocol : supportedProtocolList) {
|
||||||
|
protocolAllowableValues.add(new AllowableValue(protocol));
|
||||||
|
}
|
||||||
|
return protocolAllowableValues.toArray(new AllowableValue[numProtocols]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue