From ee37f81bc6979a32450b0746f9e62437b76168cc Mon Sep 17 00:00:00 2001 From: sbouchex Date: Tue, 27 Mar 2018 15:10:09 +0200 Subject: [PATCH 1/3] Added HTTP 'basic' authentication to ListenHTTP processor --- .../nifi/processors/standard/ListenHTTP.java | 119 ++++++- .../standard/TestListenHTTPAuthenticated.java | 299 ++++++++++++++++++ 2 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java 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 c441104824e2..bf356f8dcecf 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 @@ -28,9 +28,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; + +import javax.security.auth.Subject; import javax.servlet.Servlet; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Path; + import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.documentation.CapabilityDescription; @@ -53,6 +57,11 @@ import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.stream.io.LeakyBucketStreamThrottler; import org.apache.nifi.stream.io.StreamThrottler; +import org.eclipse.jetty.security.AbstractLoginService; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -60,10 +69,15 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; + @InputRequirement(Requirement.INPUT_FORBIDDEN) @Tags({"ingest", "http", "https", "rest", "listen"}) @CapabilityDescription("Starts an HTTP Server and listens on a given base path to transform incoming requests into FlowFiles. " @@ -134,6 +148,37 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor { .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR) .build(); + // Per RFC 7235, 2617, and 2616. + // basic-credentials = base64-user-pass + // base64-user-pass = userid ":" password + // userid = * + // password = *TEXT + // + // OCTET = + // CTL = + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // + // Per RFC 7230, username & password in URL are now disallowed in HTTP and HTTPS URIs. + public static final PropertyDescriptor PROP_BASIC_AUTH_USERNAME = new PropertyDescriptor.Builder() + .name("Basic Authentication Username") + .displayName("Basic Authentication Username") + .description("The username to be used by the client to authenticate against the Remote URL. Cannot include control characters (0-31), ':', or DEL (127).") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x39\\x3b-\\x7e\\x80-\\xff]+$"))) + .build(); + + public static final PropertyDescriptor PROP_BASIC_AUTH_PASSWORD = new PropertyDescriptor.Builder() + .name("Basic Authentication Password") + .displayName("Basic Authentication Password") + .description("The password to be used by the client to authenticate against the Remote URL.") + .required(false) + .sensitive(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x7e\\x80-\\xff]+$"))) + .build(); + public static final String CONTEXT_ATTRIBUTE_PROCESSOR = "processor"; public static final String CONTEXT_ATTRIBUTE_LOGGER = "logger"; public static final String CONTEXT_ATTRIBUTE_SESSION_FACTORY_HOLDER = "sessionFactoryHolder"; @@ -165,6 +210,8 @@ protected void init(final ProcessorInitializationContext context) { descriptors.add(MAX_UNCONFIRMED_TIME); descriptors.add(HEADERS_AS_ATTRIBUTES_REGEX); descriptors.add(RETURN_CODE); + descriptors.add(PROP_BASIC_AUTH_USERNAME); + descriptors.add(PROP_BASIC_AUTH_PASSWORD); this.properties = Collections.unmodifiableList(descriptors); } @@ -213,6 +260,8 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex final Double maxBytesPerSecond = context.getProperty(MAX_DATA_RATE).asDataSize(DataUnit.B); final StreamThrottler streamThrottler = (maxBytesPerSecond == null) ? null : new LeakyBucketStreamThrottler(maxBytesPerSecond.intValue()); final int returnCode = context.getProperty(RETURN_CODE).asInteger(); + final String authenticationUsername = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_USERNAME).evaluateAttributeExpressions().getValue()); + final String authenticationPassword = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_PASSWORD).evaluateAttributeExpressions().getValue()); throttlerRef.set(streamThrottler); final boolean needClientAuth = sslContextService != null && sslContextService.getTrustStoreFile() != null; @@ -250,6 +299,14 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex // get the configured port final int port = context.getProperty(PORT).evaluateAttributeExpressions().asInteger(); + + ConstraintSecurityHandler security = null; + if (!authenticationUsername.isEmpty()) + { + security = basicAuth(authenticationUsername,authenticationPassword,"Forbidden"); + server.setHandler(security); + server.addBean(security.getLoginService()); + } final ServerConnector connector; final HttpConfiguration httpConfiguration = new HttpConfiguration(); @@ -273,7 +330,7 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex // add the connector to the server server.setConnectors(new Connector[] {connector}); - final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, (keystorePath != null)); + final ServletContextHandler contextHandler = new ServletContextHandler(security != null ? security : server, "/", true, (security != null || keystorePath != null)); for (final Class cls : getServerClasses()) { final Path path = cls.getAnnotation(Path.class); // Note: servlets must have a path annotation - this will NPE otherwise @@ -308,6 +365,66 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex this.server = server; } + + static private class MemoryLoginService extends AbstractLoginService + { + private UserIdentity identity_; + private String username_; + private String password_; + + public MemoryLoginService(String username, String password) + { + identity_ = new DefaultUserIdentity(new Subject(),null,new String[] {"admin"}); + username_ = username; + password_ = password; + } + + @Override + protected String[] loadRoleInfo(UserPrincipal arg0) { + return null; + } + + @Override + protected UserPrincipal loadUserInfo(String arg0) { + return null; + } + + @Override + public UserIdentity login(String username, Object credentials, ServletRequest request) + { + String scredentials = (String)credentials; + if ((username.compareTo(username_) == 0) && (scredentials.compareTo(password_) == 0)) + { + return identity_; + } + return UserIdentity.UNAUTHENTICATED_IDENTITY; + } + } + + private static final ConstraintSecurityHandler basicAuth(String username, String password, String realm) { + + ConstraintSecurityHandler security = new ConstraintSecurityHandler(); + + MemoryLoginService loginService = new MemoryLoginService(username, password); + loginService.setName(realm); + + Constraint constraint = new Constraint(); + constraint.setName(Constraint.__BASIC_AUTH); + constraint.setAuthenticate(true); + constraint.setRoles(new String[] { username,"admin" }); + + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setPathSpec("/*"); + mapping.setConstraint(constraint); + + security.setConstraintMappings(Collections.singletonList(mapping)); + security.setAuthenticator(new BasicAuthenticator()); + security.setRealmName(realm); + security.addConstraintMapping(mapping); + security.setLoginService(loginService); + + return security; + } @OnScheduled public void createHttpServer(final ProcessContext context) throws Exception { diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java new file mode 100644 index 000000000000..21f5aaa60c04 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.standard; + +import org.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; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.servlet.http.HttpServletResponse; + +import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS; +import static org.junit.Assert.fail; + +public class TestListenHTTPAuthenticated { + + 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"; + + private final static String PORT_VARIABLE = "HTTP_PORT"; + private final static String HTTP_SERVER_PORT_EL = "${" + PORT_VARIABLE + "}"; + + private final static String BASEPATH_VARIABLE = "HTTP_BASEPATH"; + private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}"; + + private final static String HTTP_LOGIN = "nifi" ; + private final static String HTTP_PASSWORD = "nifi"; + + private ListenHTTP proc; + private TestRunner runner; + + private int availablePort; + + @Before + public void setup() throws IOException { + proc = new ListenHTTP(); + runner = TestRunners.newTestRunner(proc); + availablePort = NetworkUtils.availablePort();; + runner.setVariable(PORT_VARIABLE, Integer.toString(availablePort)); + runner.setVariable(BASEPATH_VARIABLE, HTTP_BASE_PATH); + runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_USERNAME, HTTP_LOGIN); + runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_PASSWORD, HTTP_PASSWORD); + } + + @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); + + testPOSTRequestsReceived(HttpServletResponse.SC_OK); + } + + @Test + public void testPOSTRequestsReceivedReturnCodeWithoutEL() throws Exception { + runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort)); + runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); + runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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(HttpServletResponse.SC_OK); + } + + @Test + public void testPOSTRequestsReturnCodeReceivedWithEL() throws Exception { + runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); + runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); + runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + runner.assertValid(); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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(HttpServletResponse.SC_OK); + } + + @Test + public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + runner.assertValid(); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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(HttpServletResponse.SC_OK); + } + + @Test + public void testSecurePOSTRequestsReturnCodeReceivedWithEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + runner.assertValid(); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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; + + 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); + + String userpass = HTTP_LOGIN + ":" + HTTP_PASSWORD; + String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes())); + connection.setRequestProperty ("Authorization", basicAuth); + + final DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + + if (message != null) { + wr.writeBytes(message); + } + wr.flush(); + wr.close(); + return connection.getResponseCode(); + } + + private void testPOSTRequestsReceived(int returnCode) throws Exception { + final List messages = new ArrayList<>(); + messages.add("payload 1"); + messages.add(""); + messages.add(null); + messages.add("payload 2"); + + startWebServerAndSendMessages(messages, returnCode); + + List mockFlowFiles = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS); + + runner.assertTransferCount(RELATIONSHIP_SUCCESS, 4); + mockFlowFiles.get(0).assertContentEquals("payload 1"); + mockFlowFiles.get(1).assertContentEquals(""); + mockFlowFiles.get(2).assertContentEquals(""); + mockFlowFiles.get(3).assertContentEquals("payload 2"); + } + + private void startWebServerAndSendMessages(final List messages, int returnCode) + throws Exception { + + final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory(); + final ProcessContext context = runner.getProcessContext(); + proc.createHttpServer(context); + + Runnable sendMessagestoWebServer = () -> { + try { + for (final String message : messages) { + if (executePOST(message) != returnCode) { + fail("HTTP POST failed."); + } + } + } catch (Exception e) { + e.printStackTrace(); + fail("Not expecting error here."); + } + }; + new Thread(sendMessagestoWebServer).start(); + + long responseTimeout = 10000; + + int numTransferred = 0; + long startTime = System.currentTimeMillis(); + while (numTransferred < messages.size() && (System.currentTimeMillis() - startTime < responseTimeout)) { + proc.onTrigger(context, processSessionFactory); + numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size(); + Thread.sleep(100); + } + + runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, messages.size()); + + } + + private SSLContextService configureProcessorSslContextService() throws InitializationException { + 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.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; + } +} From 160a4398b9ef6197aae1887fc7004ea853d962b1 Mon Sep 17 00:00:00 2001 From: sbouchex Date: Tue, 27 Mar 2018 15:10:09 +0200 Subject: [PATCH 2/3] Restrict use of BASIC authentication to HTTPS --- .../nifi/processors/standard/ListenHTTP.java | 122 +++++++- .../standard/TestListenHTTPAuthenticated.java | 263 ++++++++++++++++++ 2 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java 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 c441104824e2..ea26e872298a 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 @@ -28,9 +28,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; + +import javax.security.auth.Subject; import javax.servlet.Servlet; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Path; + import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.documentation.CapabilityDescription; @@ -53,6 +57,11 @@ import org.apache.nifi.ssl.SSLContextService; import org.apache.nifi.stream.io.LeakyBucketStreamThrottler; import org.apache.nifi.stream.io.StreamThrottler; +import org.eclipse.jetty.security.AbstractLoginService; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.DefaultUserIdentity; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -60,10 +69,15 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import static org.apache.commons.lang3.StringUtils.trimToEmpty; + @InputRequirement(Requirement.INPUT_FORBIDDEN) @Tags({"ingest", "http", "https", "rest", "listen"}) @CapabilityDescription("Starts an HTTP Server and listens on a given base path to transform incoming requests into FlowFiles. " @@ -134,6 +148,37 @@ public class ListenHTTP extends AbstractSessionFactoryProcessor { .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR) .build(); + // Per RFC 7235, 2617, and 2616. + // basic-credentials = base64-user-pass + // base64-user-pass = userid ":" password + // userid = * + // password = *TEXT + // + // OCTET = + // CTL = + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // + // Per RFC 7230, username & password in URL are now disallowed in HTTP and HTTPS URIs. + public static final PropertyDescriptor PROP_BASIC_AUTH_USERNAME = new PropertyDescriptor.Builder() + .name("Basic Authentication Username") + .displayName("Basic Authentication Username") + .description("The username to be used by the client to authenticate against the Remote URL. Cannot include control characters (0-31), ':', or DEL (127). Only works when SSL is enabled") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x39\\x3b-\\x7e\\x80-\\xff]+$"))) + .build(); + + public static final PropertyDescriptor PROP_BASIC_AUTH_PASSWORD = new PropertyDescriptor.Builder() + .name("Basic Authentication Password") + .displayName("Basic Authentication Password") + .description("The password to be used by the client to authenticate against the Remote URL. Only works when SSL is enabled") + .required(false) + .sensitive(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x7e\\x80-\\xff]+$"))) + .build(); + public static final String CONTEXT_ATTRIBUTE_PROCESSOR = "processor"; public static final String CONTEXT_ATTRIBUTE_LOGGER = "logger"; public static final String CONTEXT_ATTRIBUTE_SESSION_FACTORY_HOLDER = "sessionFactoryHolder"; @@ -165,6 +210,8 @@ protected void init(final ProcessorInitializationContext context) { descriptors.add(MAX_UNCONFIRMED_TIME); descriptors.add(HEADERS_AS_ATTRIBUTES_REGEX); descriptors.add(RETURN_CODE); + descriptors.add(PROP_BASIC_AUTH_USERNAME); + descriptors.add(PROP_BASIC_AUTH_PASSWORD); this.properties = Collections.unmodifiableList(descriptors); } @@ -213,6 +260,8 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex final Double maxBytesPerSecond = context.getProperty(MAX_DATA_RATE).asDataSize(DataUnit.B); final StreamThrottler streamThrottler = (maxBytesPerSecond == null) ? null : new LeakyBucketStreamThrottler(maxBytesPerSecond.intValue()); final int returnCode = context.getProperty(RETURN_CODE).asInteger(); + final String authenticationUsername = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_USERNAME).evaluateAttributeExpressions().getValue()); + final String authenticationPassword = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_PASSWORD).evaluateAttributeExpressions().getValue()); throttlerRef.set(streamThrottler); final boolean needClientAuth = sslContextService != null && sslContextService.getTrustStoreFile() != null; @@ -250,6 +299,8 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex // get the configured port final int port = context.getProperty(PORT).evaluateAttributeExpressions().asInteger(); + + ConstraintSecurityHandler security = null; final ServerConnector connector; final HttpConfiguration httpConfiguration = new HttpConfiguration(); @@ -261,9 +312,16 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex httpConfiguration.setSecureScheme("https"); httpConfiguration.setSecurePort(port); httpConfiguration.addCustomizer(new SecureRequestCustomizer()); + + // Create the 'basic' authenticator + if (!authenticationUsername.isEmpty()) + { + security = basicAuth(authenticationUsername,authenticationPassword,"Forbidden"); + server.setHandler(security); + server.addBean(security.getLoginService()); + } // build the connector - connector = new ServerConnector(server, new SslConnectionFactory(contextFactory, "http/1.1"), new HttpConnectionFactory(httpConfiguration)); } @@ -273,7 +331,7 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex // add the connector to the server server.setConnectors(new Connector[] {connector}); - final ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, (keystorePath != null)); + final ServletContextHandler contextHandler = new ServletContextHandler(security != null ? security : server, "/", true, keystorePath != null); for (final Class cls : getServerClasses()) { final Path path = cls.getAnnotation(Path.class); // Note: servlets must have a path annotation - this will NPE otherwise @@ -308,6 +366,66 @@ private void createHttpServerFromService(final ProcessContext context) throws Ex this.server = server; } + + static private class MemoryLoginService extends AbstractLoginService + { + private UserIdentity identity_; + private String username_; + private String password_; + + public MemoryLoginService(String username, String password) + { + identity_ = new DefaultUserIdentity(new Subject(),null,new String[] {"admin"}); + username_ = username; + password_ = password; + } + + @Override + protected String[] loadRoleInfo(UserPrincipal arg0) { + return null; + } + + @Override + protected UserPrincipal loadUserInfo(String arg0) { + return null; + } + + @Override + public UserIdentity login(String username, Object credentials, ServletRequest request) + { + String scredentials = (String)credentials; + if ((username.compareTo(username_) == 0) && (scredentials.compareTo(password_) == 0)) + { + return identity_; + } + return UserIdentity.UNAUTHENTICATED_IDENTITY; + } + } + + private static final ConstraintSecurityHandler basicAuth(String username, String password, String realm) { + + ConstraintSecurityHandler security = new ConstraintSecurityHandler(); + + MemoryLoginService loginService = new MemoryLoginService(username, password); + loginService.setName(realm); + + Constraint constraint = new Constraint(); + constraint.setName(Constraint.__BASIC_AUTH); + constraint.setAuthenticate(true); + constraint.setRoles(new String[] { username,"admin" }); + + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setPathSpec("/*"); + mapping.setConstraint(constraint); + + security.setConstraintMappings(Collections.singletonList(mapping)); + security.setAuthenticator(new BasicAuthenticator()); + security.setRealmName(realm); + security.addConstraintMapping(mapping); + security.setLoginService(loginService); + + return security; + } @OnScheduled public void createHttpServer(final ProcessContext context) throws Exception { diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java new file mode 100644 index 000000000000..0eeca2026cf0 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.standard; + +import org.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; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.servlet.http.HttpServletResponse; + +import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS; +import static org.junit.Assert.fail; + +public class TestListenHTTPAuthenticated { + + 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"; + + private final static String PORT_VARIABLE = "HTTP_PORT"; + private final static String HTTP_SERVER_PORT_EL = "${" + PORT_VARIABLE + "}"; + + private final static String BASEPATH_VARIABLE = "HTTP_BASEPATH"; + private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}"; + + private final static String HTTP_LOGIN = "nifi" ; + private final static String HTTP_PASSWORD = "nifi"; + + private ListenHTTP proc; + private TestRunner runner; + + private int availablePort; + + @Before + public void setup() throws IOException { + proc = new ListenHTTP(); + runner = TestRunners.newTestRunner(proc); + availablePort = NetworkUtils.availablePort();; + runner.setVariable(PORT_VARIABLE, Integer.toString(availablePort)); + runner.setVariable(BASEPATH_VARIABLE, HTTP_BASE_PATH); + runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_USERNAME, HTTP_LOGIN); + runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_PASSWORD, HTTP_PASSWORD); + } + + @After + public void teardown() { + proc.shutdownHttpServer(); + } + + @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(HttpServletResponse.SC_OK); + } + + @Test + public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + runner.assertValid(); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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(HttpServletResponse.SC_OK); + } + + @Test + public void testSecurePOSTRequestsReturnCodeReceivedWithEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); + runner.assertValid(); + + testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); + } + + @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; + + 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); + + String userpass = HTTP_LOGIN + ":" + HTTP_PASSWORD; + String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes())); + connection.setRequestProperty ("Authorization", basicAuth); + + final DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + + if (message != null) { + wr.writeBytes(message); + } + wr.flush(); + wr.close(); + return connection.getResponseCode(); + } + + private void testPOSTRequestsReceived(int returnCode) throws Exception { + final List messages = new ArrayList<>(); + messages.add("payload 1"); + messages.add(""); + messages.add(null); + messages.add("payload 2"); + + startWebServerAndSendMessages(messages, returnCode); + + List mockFlowFiles = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS); + + runner.assertTransferCount(RELATIONSHIP_SUCCESS, 4); + mockFlowFiles.get(0).assertContentEquals("payload 1"); + mockFlowFiles.get(1).assertContentEquals(""); + mockFlowFiles.get(2).assertContentEquals(""); + mockFlowFiles.get(3).assertContentEquals("payload 2"); + } + + private void startWebServerAndSendMessages(final List messages, int returnCode) + throws Exception { + + final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory(); + final ProcessContext context = runner.getProcessContext(); + proc.createHttpServer(context); + + Runnable sendMessagestoWebServer = () -> { + try { + for (final String message : messages) { + if (executePOST(message) != returnCode) { + fail("HTTP POST failed."); + } + } + } catch (Exception e) { + e.printStackTrace(); + fail("Not expecting error here."); + } + }; + new Thread(sendMessagestoWebServer).start(); + + long responseTimeout = 10000; + + int numTransferred = 0; + long startTime = System.currentTimeMillis(); + while (numTransferred < messages.size() && (System.currentTimeMillis() - startTime < responseTimeout)) { + proc.onTrigger(context, processSessionFactory); + numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size(); + Thread.sleep(100); + } + + runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, messages.size()); + + } + + private SSLContextService configureProcessorSslContextService() throws InitializationException { + 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.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; + } +} From e407e46cdd1303be96f560f9765d763cf4782f38 Mon Sep 17 00:00:00 2001 From: sbouchex Date: Wed, 28 Mar 2018 09:29:29 +0200 Subject: [PATCH 3/3] Fixed tests --- .../standard/TestListenHTTPAuthenticated.java | 598 ------------------ 1 file changed, 598 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java index 847d12d80cff..0eeca2026cf0 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTPAuthenticated.java @@ -261,601 +261,3 @@ private SSLContextService configureInvalidProcessorSslContextService() throws In return sslContextService; } } -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.processors.standard; - -import org.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; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.servlet.http.HttpServletResponse; - -import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS; -import static org.junit.Assert.fail; - -public class TestListenHTTPAuthenticated { - - 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"; - - private final static String PORT_VARIABLE = "HTTP_PORT"; - private final static String HTTP_SERVER_PORT_EL = "${" + PORT_VARIABLE + "}"; - - private final static String BASEPATH_VARIABLE = "HTTP_BASEPATH"; - private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}"; - - private final static String HTTP_LOGIN = "nifi" ; - private final static String HTTP_PASSWORD = "nifi"; - - private ListenHTTP proc; - private TestRunner runner; - - private int availablePort; - - @Before - public void setup() throws IOException { - proc = new ListenHTTP(); - runner = TestRunners.newTestRunner(proc); - availablePort = NetworkUtils.availablePort();; - runner.setVariable(PORT_VARIABLE, Integer.toString(availablePort)); - runner.setVariable(BASEPATH_VARIABLE, HTTP_BASE_PATH); - runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_USERNAME, HTTP_LOGIN); - runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_PASSWORD, HTTP_PASSWORD); - } - - @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); - - testPOSTRequestsReceived(HttpServletResponse.SC_OK); - } - - @Test - public void testPOSTRequestsReceivedReturnCodeWithoutEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort)); - runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); - runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testPOSTRequestsReturnCodeReceivedWithEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); - runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); - runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testSecurePOSTRequestsReturnCodeReceivedWithEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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; - - 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); - - String userpass = HTTP_LOGIN + ":" + HTTP_PASSWORD; - String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes())); - connection.setRequestProperty ("Authorization", basicAuth); - - final DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); - - if (message != null) { - wr.writeBytes(message); - } - wr.flush(); - wr.close(); - return connection.getResponseCode(); - } - - private void testPOSTRequestsReceived(int returnCode) throws Exception { - final List messages = new ArrayList<>(); - messages.add("payload 1"); - messages.add(""); - messages.add(null); - messages.add("payload 2"); - - startWebServerAndSendMessages(messages, returnCode); - - List mockFlowFiles = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS); - - runner.assertTransferCount(RELATIONSHIP_SUCCESS, 4); - mockFlowFiles.get(0).assertContentEquals("payload 1"); - mockFlowFiles.get(1).assertContentEquals(""); - mockFlowFiles.get(2).assertContentEquals(""); - mockFlowFiles.get(3).assertContentEquals("payload 2"); - } - - private void startWebServerAndSendMessages(final List messages, int returnCode) - throws Exception { - - final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory(); - final ProcessContext context = runner.getProcessContext(); - proc.createHttpServer(context); - - Runnable sendMessagestoWebServer = () -> { - try { - for (final String message : messages) { - if (executePOST(message) != returnCode) { - fail("HTTP POST failed."); - } - } - } catch (Exception e) { - e.printStackTrace(); - fail("Not expecting error here."); - } - }; - new Thread(sendMessagestoWebServer).start(); - - long responseTimeout = 10000; - - int numTransferred = 0; - long startTime = System.currentTimeMillis(); - while (numTransferred < messages.size() && (System.currentTimeMillis() - startTime < responseTimeout)) { - proc.onTrigger(context, processSessionFactory); - numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size(); - Thread.sleep(100); - } - - runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, messages.size()); - - } - - private SSLContextService configureProcessorSslContextService() throws InitializationException { - 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.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; - } -} -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.nifi.processors.standard; - -import org.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; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.servlet.http.HttpServletResponse; - -import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS; -import static org.junit.Assert.fail; - -public class TestListenHTTPAuthenticated { - - 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"; - - private final static String PORT_VARIABLE = "HTTP_PORT"; - private final static String HTTP_SERVER_PORT_EL = "${" + PORT_VARIABLE + "}"; - - private final static String BASEPATH_VARIABLE = "HTTP_BASEPATH"; - private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}"; - - private final static String HTTP_LOGIN = "nifi" ; - private final static String HTTP_PASSWORD = "nifi"; - - private ListenHTTP proc; - private TestRunner runner; - - private int availablePort; - - @Before - public void setup() throws IOException { - proc = new ListenHTTP(); - runner = TestRunners.newTestRunner(proc); - availablePort = NetworkUtils.availablePort();; - runner.setVariable(PORT_VARIABLE, Integer.toString(availablePort)); - runner.setVariable(BASEPATH_VARIABLE, HTTP_BASE_PATH); - runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_USERNAME, HTTP_LOGIN); - runner.setProperty(ListenHTTP.PROP_BASIC_AUTH_PASSWORD, HTTP_PASSWORD); - } - - @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); - - testPOSTRequestsReceived(HttpServletResponse.SC_OK); - } - - @Test - public void testPOSTRequestsReceivedReturnCodeWithoutEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort)); - runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH); - runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testPOSTRequestsReturnCodeReceivedWithEL() throws Exception { - runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL); - runner.setProperty(ListenHTTP.BASE_PATH, HTTP_SERVER_BASEPATH_EL); - runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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(HttpServletResponse.SC_OK); - } - - @Test - public void testSecurePOSTRequestsReturnCodeReceivedWithEL() 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.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_NO_CONTENT)); - runner.assertValid(); - - testPOSTRequestsReceived(HttpServletResponse.SC_NO_CONTENT); - } - - @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; - - 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); - - String userpass = HTTP_LOGIN + ":" + HTTP_PASSWORD; - String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes())); - connection.setRequestProperty ("Authorization", basicAuth); - - final DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); - - if (message != null) { - wr.writeBytes(message); - } - wr.flush(); - wr.close(); - return connection.getResponseCode(); - } - - private void testPOSTRequestsReceived(int returnCode) throws Exception { - final List messages = new ArrayList<>(); - messages.add("payload 1"); - messages.add(""); - messages.add(null); - messages.add("payload 2"); - - startWebServerAndSendMessages(messages, returnCode); - - List mockFlowFiles = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS); - - runner.assertTransferCount(RELATIONSHIP_SUCCESS, 4); - mockFlowFiles.get(0).assertContentEquals("payload 1"); - mockFlowFiles.get(1).assertContentEquals(""); - mockFlowFiles.get(2).assertContentEquals(""); - mockFlowFiles.get(3).assertContentEquals("payload 2"); - } - - private void startWebServerAndSendMessages(final List messages, int returnCode) - throws Exception { - - final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory(); - final ProcessContext context = runner.getProcessContext(); - proc.createHttpServer(context); - - Runnable sendMessagestoWebServer = () -> { - try { - for (final String message : messages) { - if (executePOST(message) != returnCode) { - fail("HTTP POST failed."); - } - } - } catch (Exception e) { - e.printStackTrace(); - fail("Not expecting error here."); - } - }; - new Thread(sendMessagestoWebServer).start(); - - long responseTimeout = 10000; - - int numTransferred = 0; - long startTime = System.currentTimeMillis(); - while (numTransferred < messages.size() && (System.currentTimeMillis() - startTime < responseTimeout)) { - proc.onTrigger(context, processSessionFactory); - numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size(); - Thread.sleep(100); - } - - runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, messages.size()); - - } - - private SSLContextService configureProcessorSslContextService() throws InitializationException { - 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.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; - } -}