diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java index d2bfa04710..5d23370562 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java @@ -34,6 +34,7 @@ import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.util.StandardValidators; import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet; import org.apache.nifi.processors.standard.servlets.ListenHTTPServlet; +import org.apache.nifi.ssl.RestrictedSSLContextService; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.stream.io.LeakyBucketStreamThrottler; import org.apache.nifi.stream.io.StreamThrottler; @@ -118,7 +119,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor { .name("SSL Context Service") .description("The Controller Service to use in order to obtain an SSL Context") .required(false) - .identifiesControllerService(SSLContextService.class) + .identifiesControllerService(RestrictedSSLContextService.class) .build(); public static final PropertyDescriptor HEADERS_AS_ATTRIBUTES_REGEX = new PropertyDescriptor.Builder() .name("HTTP Headers to receive as Attributes (Regex)") @@ -227,6 +228,10 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor { contextFactory.setKeyStoreType(keyStoreType); } + if (sslContextService != null) { + contextFactory.setProtocol(sslContextService.getSslAlgorithm()); + } + // thread pool for the jetty instance final QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setName(String.format("%s (%s) Web Server", getClass().getSimpleName(), getIdentifier())); @@ -249,6 +254,7 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor { httpConfiguration.addCustomizer(new SecureRequestCustomizer()); // build the connector + connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration)); } diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java index 9193f8e9b3..c46d4cded6 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java @@ -20,11 +20,13 @@ import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSessionFactory; import org.apache.nifi.remote.io.socket.NetworkUtils; import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.ssl.StandardRestrictedSSLContextService; import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.ssl.StandardSSLContextService; import org.apache.nifi.util.MockFlowFile; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -35,10 +37,15 @@ import java.net.URL; import java.util.ArrayList; 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.junit.Assert.fail; + 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_BASE_PATH = "basePath"; @@ -64,9 +71,13 @@ public class TestListenHTTP { } + @After + public void teardown() { + proc.shutdownHttpServer(); + } + @Test public void testPOSTRequestsReceivedWithoutEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort)); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); @@ -75,30 +86,79 @@ public class TestListenHTTP { @Test public void testPOSTRequestsReceivedWithEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); + runner.assertValid(); 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 { + 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); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); + if(secure) { + 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) { wr.writeBytes(message); } wr.flush(); wr.close(); - - return con.getResponseCode(); - + return connection.getResponseCode(); } + private void testPOSTRequestsReceived() throws Exception { final List messages = new ArrayList<>(); messages.add("payload 1"); @@ -122,7 +182,7 @@ public class TestListenHTTP { final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory(); final ProcessContext context = runner.getProcessContext(); - proc.createHttpServer(context); + proc.createHttpServer(context); Runnable sendMessagestoWebServer = () -> { try { @@ -151,18 +211,30 @@ public class TestListenHTTP { } private SSLContextService configureProcessorSslContextService() throws InitializationException { - final SSLContextService sslContextService = new StandardSSLContextService(); - runner.addControllerService("ssl-context", sslContextService); + final SSLContextService sslContextService = new StandardRestrictedSSLContextService(); + 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.enableControllerService(sslContextService); - runner.setProperty(ListenHTTP.SSL_CONTEXT_SERVICE, "ssl-context"); + runner.setProperty(ListenHTTP.SSL_CONTEXT_SERVICE, SSL_CONTEXT_SERVICE_IDENTIFIER); 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; + } } diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java new file mode 100644 index 0000000000..4ad7bbeb2f --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java @@ -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 properties; + + static { + List 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 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(); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java index 2570855757..6b267ac915 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java @@ -18,20 +18,15 @@ package org.apache.nifi.ssl; import java.io.File; import java.net.MalformedURLException; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import javax.net.ssl.SSLContext; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.lifecycle.OnEnabled; -import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.PropertyValue; 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"}) @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 static final String STORE_TYPE_JKS = "JKS"; @@ -110,14 +108,14 @@ public class StandardSSLContextService extends AbstractControllerService impleme .displayName("TLS Protocol") .defaultValue("TLS") .required(false) - .allowableValues(buildAlgorithmAllowableValues()) + .allowableValues(SSLContextService.buildAlgorithmAllowableValues()) .description("The algorithm to use for this TLS/SSL context") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(false) .build(); private static final List properties; - private ConfigurationContext configContext; + protected ConfigurationContext configContext; private boolean isValidated; // TODO: This can be made configurable if necessary @@ -252,8 +250,12 @@ public class StandardSSLContextService extends AbstractControllerService impleme return VALIDATION_CACHE_EXPIRATION; } + protected String getSSLProtocolForValidation(final ValidationContext validationContext) { + return validationContext.getProperty(SSL_ALGORITHM).getValue(); + } + private void verifySslConfig(final ValidationContext validationContext) throws ProcessException { - final String protocol = validationContext.getProperty(SSL_ALGORITHM).getValue(); + final String protocol = getSSLProtocolForValidation(validationContext); try { final PropertyValue keyPasswdProp = validationContext.getProperty(KEY_PASSWORD); final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; @@ -295,7 +297,7 @@ public class StandardSSLContextService extends AbstractControllerService impleme @Override public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException { - final String protocol = configContext.getProperty(SSL_ALGORITHM).getValue(); + final String protocol = getSslAlgorithm(); try { final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD); final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null; @@ -458,36 +460,6 @@ public class StandardSSLContextService extends AbstractControllerService impleme KEYSTORE, TRUSTSTORE } - private static AllowableValue[] buildAlgorithmAllowableValues() { - final Set 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 supportedProtocolList = new ArrayList<>(supportedProtocols); - Collections.sort(supportedProtocolList); - - final List protocolAllowableValues = new ArrayList<>(); - for (final String protocol : supportedProtocolList) { - protocolAllowableValues.add(new AllowableValue(protocol)); - } - return protocolAllowableValues.toArray(new AllowableValue[numProtocols]); - } - @Override public String toString() { return "SSLContextService[id=" + getIdentifier() + "]"; diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService index b1b612471b..115fe0defa 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService @@ -12,4 +12,5 @@ # 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. -org.apache.nifi.ssl.StandardSSLContextService \ No newline at end of file +org.apache.nifi.ssl.StandardSSLContextService +org.apache.nifi.ssl.StandardRestrictedSSLContextService \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/RestrictedSSLContextServiceTest.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/RestrictedSSLContextServiceTest.java new file mode 100644 index 0000000000..3a44ef457a --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/RestrictedSSLContextServiceTest.java @@ -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 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())); + } + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java index 60b09bd4db..4c00bdcb3e 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java @@ -16,6 +16,9 @@ */ 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.assertTrue; @@ -23,9 +26,17 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; 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.ValidationResult; import org.apache.nifi.reporting.InitializationException; @@ -311,4 +322,22 @@ public class SSLContextServiceTest { 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 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())); + } + } } diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/RestrictedSSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/RestrictedSSLContextService.java new file mode 100644 index 0000000000..b76f31dea2 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/RestrictedSSLContextService.java @@ -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 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 supportedProtocolList = new ArrayList<>(supportedProtocols); + Collections.sort(supportedProtocolList); + + final List protocolAllowableValues = new ArrayList<>(); + for (final String protocol : supportedProtocolList) { + protocolAllowableValues.add(new AllowableValue(protocol)); + } + return protocolAllowableValues.toArray(new AllowableValue[numProtocols]); + } +} diff --git a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java index 94f8bda526..61f3b81cc3 100644 --- a/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java +++ b/nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java @@ -16,10 +16,19 @@ */ 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 org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.AllowableValue; import org.apache.nifi.controller.ControllerService; import org.apache.nifi.processor.exception.ProcessException; @@ -60,4 +69,39 @@ public interface SSLContextService extends ControllerService { public boolean isKeyStoreConfigured(); 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 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 supportedProtocolList = new ArrayList<>(supportedProtocols); + Collections.sort(supportedProtocolList); + + final List protocolAllowableValues = new ArrayList<>(); + for (final String protocol : supportedProtocolList) { + protocolAllowableValues.add(new AllowableValue(protocol)); + } + return protocolAllowableValues.toArray(new AllowableValue[numProtocols]); + } }