NIFI-2529: Added support for SSLContextService protocols in HandleHttpRequest

Forced HandleHTTPRequest to use RestrictedSSLContextService and removed extraneous SSL algorithm checks
Throw RuntimeException if the chosen SSL protocol isn't supported by HandleHttpRequest

This closes #1985.

Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
m-hogue 2017-07-06 10:55:10 -04:00 committed by Andy LoPresto
parent 2c1f5b49e4
commit b7b6d9082c
No known key found for this signature in database
GPG Key ID: 6EC293152D90B61D
2 changed files with 110 additions and 2 deletions

View File

@ -91,10 +91,11 @@ import com.sun.jersey.api.client.ClientResponse.Status;
@WritesAttribute(attribute = "http.method", description = "The HTTP Method that was used for the request, such as GET or POST"),
@WritesAttribute(attribute = HTTPUtils.HTTP_LOCAL_NAME, description = "IP address/hostname of the server"),
@WritesAttribute(attribute = HTTPUtils.HTTP_PORT, description = "Listening port of the server"),
@WritesAttribute(attribute = "http.query.string", description = "The query string portion of hte Request URL"),
@WritesAttribute(attribute = "http.query.string", description = "The query string portion of the Request URL"),
@WritesAttribute(attribute = HTTPUtils.HTTP_REMOTE_HOST, description = "The hostname of the requestor"),
@WritesAttribute(attribute = "http.remote.addr", description = "The hostname:port combination of the requestor"),
@WritesAttribute(attribute = "http.remote.user", description = "The username of the requestor"),
@WritesAttribute(attribute = "http.protocol", description = "The protocol used to communicate"),
@WritesAttribute(attribute = HTTPUtils.HTTP_REQUEST_URI, description = "The full Request URL"),
@WritesAttribute(attribute = "http.auth.type", description = "The type of HTTP Authorization used"),
@WritesAttribute(attribute = "http.principal.name", description = "The name of the authenticated user making the request"),
@ -106,7 +107,7 @@ import com.sun.jersey.api.client.ClientResponse.Status;
+ "attribute, prefixed with \"http.headers.\" For example, if the request contains an HTTP Header named \"x-my-header\", then the value "
+ "will be added to an attribute named \"http.headers.x-my-header\"")})
@SeeAlso(value = {HandleHttpResponse.class},
classNames = {"org.apache.nifi.http.StandardHttpContextMap", "org.apache.nifi.ssl.StandardSSLContextService"})
classNames = {"org.apache.nifi.http.StandardHttpContextMap", "org.apache.nifi.ssl.RestrictedStandardSSLContextService"})
public class HandleHttpRequest extends AbstractProcessor {
private static final Pattern URL_QUERY_PARAM_DELIMITER = Pattern.compile("&");
@ -448,6 +449,8 @@ public class HandleHttpRequest extends AbstractProcessor {
sslFactory.setNeedClientAuth(needClientAuth);
sslFactory.setWantClientAuth(wantClientAuth);
sslFactory.setProtocol(sslService.getSslAlgorithm());
if (sslService.isKeyStoreConfigured()) {
sslFactory.setKeyStorePath(sslService.getKeyStoreFile());
sslFactory.setKeyStorePassword(sslService.getKeyStorePassword());
@ -528,6 +531,7 @@ public class HandleHttpRequest extends AbstractProcessor {
putAttribute(attributes, HTTPUtils.HTTP_REMOTE_HOST, request.getRemoteHost());
putAttribute(attributes, "http.remote.addr", request.getRemoteAddr());
putAttribute(attributes, "http.remote.user", request.getRemoteUser());
putAttribute(attributes, "http.protocol", request.getProtocol());
putAttribute(attributes, HTTPUtils.HTTP_REQUEST_URI, request.getRequestURI());
putAttribute(attributes, "http.request.url", request.getRequestURL().toString());
putAttribute(attributes, "http.auth.type", request.getAuthType());

View File

@ -22,9 +22,13 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -32,6 +36,9 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.http.HttpContextMap;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.ssl.SSLContextService;
import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
import org.apache.nifi.ssl.StandardSSLContextService;
import org.apache.nifi.stream.io.NullOutputStream;
import org.apache.nifi.stream.io.StreamUtils;
import org.apache.nifi.util.MockFlowFile;
@ -42,6 +49,36 @@ import org.junit.Test;
public class TestHandleHttpRequest {
private static Map<String, String> getTruststoreProperties() {
final Map<String, String> props = new HashMap<>();
props.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/localhost-ts.jks");
props.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "localtest");
props.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");
return props;
}
private static Map<String, String> getKeystoreProperties() {
final Map<String, String> properties = new HashMap<>();
properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/localhost-ks.jks");
properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "localtest");
properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS");
return properties;
}
private static SSLContext useSSLContextService(final TestRunner controller, final Map<String, String> sslProperties) {
final SSLContextService service = new StandardRestrictedSSLContextService();
try {
controller.addControllerService("ssl-service", service, sslProperties);
controller.enableControllerService(service);
} catch (InitializationException ex) {
ex.printStackTrace();
Assert.fail("Could not create SSL Context Service");
}
controller.setProperty(HandleHttpRequest.SSL_CONTEXT, "ssl-service");
return service.createSSLContext(SSLContextService.ClientAuth.WANT);
}
@Test(timeout=10000)
public void testRequestAddedToService() throws InitializationException, MalformedURLException, IOException, InterruptedException {
final TestRunner runner = TestRunners.newTestRunner(HandleHttpRequest.class);
@ -165,6 +202,73 @@ public class TestHandleHttpRequest {
}
}
@Test
public void testSecure() throws InitializationException {
final TestRunner runner = TestRunners.newTestRunner(HandleHttpRequest.class);
runner.setProperty(HandleHttpRequest.PORT, "0");
final MockHttpContextMap contextMap = new MockHttpContextMap();
runner.addControllerService("http-context-map", contextMap);
runner.enableControllerService(contextMap);
runner.setProperty(HandleHttpRequest.HTTP_CONTEXT_MAP, "http-context-map");
final Map<String, String> sslProperties = getKeystoreProperties();
sslProperties.putAll(getTruststoreProperties());
sslProperties.put(StandardSSLContextService.SSL_ALGORITHM.getName(), "TLSv1.2");
final SSLContext sslContext = useSSLContextService(runner, sslProperties);
// trigger processor to stop but not shutdown.
runner.run(1, false);
try {
final Thread httpThread = new Thread(new Runnable() {
@Override
public void run() {
try {
final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
final HttpsURLConnection connection = (HttpsURLConnection) new URL("https://localhost:"
+ port + "/my/path?query=true&value1=value1&value2=&value3&value4=apple=orange").openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.setDoOutput(false);
connection.setRequestMethod("GET");
connection.setRequestProperty("header1", "value1");
connection.setRequestProperty("header2", "");
connection.setRequestProperty("header3", "apple=orange");
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);
StreamUtils.copy(connection.getInputStream(), new NullOutputStream());
} catch (final Throwable t) {
t.printStackTrace();
Assert.fail(t.toString());
}
}
});
httpThread.start();
while ( runner.getFlowFilesForRelationship(HandleHttpRequest.REL_SUCCESS).isEmpty() ) {
// process the request.
runner.run(1, false, false);
}
runner.assertAllFlowFilesTransferred(HandleHttpRequest.REL_SUCCESS, 1);
assertEquals(1, contextMap.size());
final MockFlowFile mff = runner.getFlowFilesForRelationship(HandleHttpRequest.REL_SUCCESS).get(0);
mff.assertAttributeEquals("http.query.param.query", "true");
mff.assertAttributeEquals("http.query.param.value1", "value1");
mff.assertAttributeEquals("http.query.param.value2", "");
mff.assertAttributeEquals("http.query.param.value3", "");
mff.assertAttributeEquals("http.query.param.value4", "apple=orange");
mff.assertAttributeEquals("http.headers.header1", "value1");
mff.assertAttributeEquals("http.headers.header3", "apple=orange");
mff.assertAttributeEquals("http.protocol", "HTTP/1.1");
} finally {
// shut down the server
runner.run(1, true);
}
}
private static class MockHttpContextMap extends AbstractControllerService implements HttpContextMap {
private boolean registerSuccessfully = true;