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:
m-hogue 2017-07-06 11:42:46 -04:00 committed by Andy LoPresto
parent e62417ea6b
commit d28e61c5dd
No known key found for this signature in database
GPG Key ID: 6EC293152D90B61D
9 changed files with 370 additions and 57 deletions

View File

@ -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));
} }

View File

@ -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;
}
} }

View File

@ -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();
}
}

View File

@ -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() + "]";

View File

@ -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

View File

@ -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()));
}
}
}

View File

@ -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()));
}
}
} }

View File

@ -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]);
}
}

View File

@ -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]);
}
} }