diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f28b4b41..9bbb2d3fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,11 @@ Current creating instances of the generator. * Generators based on `DataApiRequestImpl` are not yet implemented. +- [Add custom ssl context support to druid AsyncHttp requests](https://github.com/yahoo/fili/pull/943) + * Added new Constructors in `AsyncDruidWebServiceImpl` class that accept custom `SslContext` as additional argument. + This custom SslContext if not null, replaces the default ssl context while making the request to druid. + * Added `getSSLContext()` method in `AbstractBinderFactory` class that returns null as default. Custom Ssl Context is passed by overriding this method. + ### Changed: - [Extracted `DataSourceConstraint` into an interface](https://github.com/yahoo/fili/issues/996) * `DataSourceConstraint` is now an interface. @@ -2398,3 +2403,5 @@ Jobs resource. Here are the highlights of what's in this release: - [`DruidDimensionsLoader` doesn't set the dimension's lastUpdated date](https://github.com/yahoo/fili/pull/24) * `DruidDimensionsLoader` now properly sets the `lastUpdated` field after it finished processing the Druid response + +### Added: diff --git a/fili-core/src/main/java/com/yahoo/bard/webservice/application/AbstractBinderFactory.java b/fili-core/src/main/java/com/yahoo/bard/webservice/application/AbstractBinderFactory.java index 06dd0d86ab..5e576d666c 100644 --- a/fili-core/src/main/java/com/yahoo/bard/webservice/application/AbstractBinderFactory.java +++ b/fili-core/src/main/java/com/yahoo/bard/webservice/application/AbstractBinderFactory.java @@ -136,6 +136,7 @@ import com.codahale.metrics.health.HealthCheckRegistry; import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.handler.ssl.SslContext; import org.glassfish.hk2.api.TypeLiteral; import org.glassfish.hk2.utilities.Binder; import org.glassfish.hk2.utilities.binding.AbstractBinder; @@ -1218,6 +1219,14 @@ protected Class getPhysicalTableResolver() { return new HashMap<>(0); } + /** + * Get a custom configured ssl context. If null, the system default SSL Context will be applied + * @return SSL context + */ + protected SslContext getSSLContext() { + return null; + } + /** * Create a DruidWebService. *

@@ -1232,14 +1241,15 @@ protected DruidWebService buildDruidWebService(DruidServiceConfig druidServiceCo Supplier> supplier = buildDruidWebServiceHeaderSupplier(); return DRUID_UNCOVERED_INTERVAL_LIMIT > 0 ? new AsyncDruidWebServiceImpl( - druidServiceConfig, - mapper, - supplier, - new HeaderNestingJsonBuilderStrategy( - AsyncDruidWebServiceImpl.DEFAULT_JSON_NODE_BUILDER_STRATEGY - ) - ) - : new AsyncDruidWebServiceImpl(druidServiceConfig, mapper, supplier); + druidServiceConfig, + mapper, + supplier, + getSSLContext(), + new HeaderNestingJsonBuilderStrategy( + AsyncDruidWebServiceImpl.DEFAULT_JSON_NODE_BUILDER_STRATEGY + ) + ) + : new AsyncDruidWebServiceImpl(druidServiceConfig, mapper, supplier, getSSLContext()); } /** diff --git a/fili-core/src/main/java/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImpl.java b/fili-core/src/main/java/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImpl.java index c19e989002..f73f4dc2cb 100644 --- a/fili-core/src/main/java/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImpl.java +++ b/fili-core/src/main/java/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImpl.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import io.netty.handler.ssl.SslContext; import org.asynchttpclient.AsyncCompletionHandler; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; @@ -109,7 +110,7 @@ public AsyncDruidWebServiceImpl( ) { this( serviceConfig, - initializeWebClient(serviceConfig.getTimeout()), + initializeWebClient(serviceConfig.getTimeout(), null), mapper, HashMap::new, DEFAULT_JSON_NODE_BUILDER_STRATEGY @@ -150,13 +151,62 @@ public AsyncDruidWebServiceImpl( ) { this( serviceConfig, - initializeWebClient(serviceConfig.getTimeout()), + initializeWebClient(serviceConfig.getTimeout(), null), mapper, headersToAppend, DEFAULT_JSON_NODE_BUILDER_STRATEGY ); } + /** + * Friendly non-DI constructor useful for manual tests. + *

+ * This constructor uses default JSON builder, which only uses response body to build the JSON response. + * + * @param serviceConfig Configuration for the Druid Service + * @param mapper A shared jackson object mapper resource + * @param headersToAppend Supplier for map of headers for Druid requests + * @param sslContext Custom Configured SslContext (null if default SSL context has to be used) + */ + public AsyncDruidWebServiceImpl( + DruidServiceConfig serviceConfig, + ObjectMapper mapper, + Supplier> headersToAppend, + SslContext sslContext + ) { + this( + serviceConfig, + initializeWebClient(serviceConfig.getTimeout(), sslContext), + mapper, + headersToAppend, + DEFAULT_JSON_NODE_BUILDER_STRATEGY + ); + } + + /** + * Friendly non-DI constructor useful for manual tests. + * + * @param serviceConfig Configuration for the Druid Service + * @param mapper A shared jackson object mapper resource + * @param headersToAppend Supplier for map of headers for Druid requests + * @param jsonNodeBuilderStrategy A function to build JSON nodes from the response + */ + public AsyncDruidWebServiceImpl( + DruidServiceConfig serviceConfig, + ObjectMapper mapper, + Supplier> headersToAppend, + Function jsonNodeBuilderStrategy + ) { + this( + serviceConfig, + initializeWebClient(serviceConfig.getTimeout(), null), + mapper, + headersToAppend, + jsonNodeBuilderStrategy + ); + } + + /** * Friendly non-DI constructor useful for manual tests. * @@ -164,16 +214,18 @@ public AsyncDruidWebServiceImpl( * @param mapper A shared jackson object mapper resource * @param headersToAppend Supplier for map of headers for Druid requests * @param jsonNodeBuilderStrategy A function to build JSON nodes from the response + * @param sslContext Custom Configured SslContext (null if default SSL context has to be used) */ public AsyncDruidWebServiceImpl( DruidServiceConfig serviceConfig, ObjectMapper mapper, Supplier> headersToAppend, + SslContext sslContext, Function jsonNodeBuilderStrategy ) { this( serviceConfig, - initializeWebClient(serviceConfig.getTimeout()), + initializeWebClient(serviceConfig.getTimeout(), sslContext), mapper, headersToAppend, jsonNodeBuilderStrategy @@ -237,10 +289,11 @@ public AsyncDruidWebServiceImpl( * Initialize the client config. * * @param requestTimeout Timeout to use for the client configuration. + * @param sslContext Custom Configured SslContext. (null if default ssl context has to be used) * * @return the set up client */ - private static AsyncHttpClient initializeWebClient(int requestTimeout) { + private static AsyncHttpClient initializeWebClient(int requestTimeout, SslContext sslContext) { LOG.debug("Druid request timeout: {}ms", requestTimeout); List cipherSuites = SYSTEM_CONFIG.getListProperty(SSL_ENABLED_CIPHER_KEY, null); @@ -257,6 +310,7 @@ private static AsyncHttpClient initializeWebClient(int requestTimeout) { .setPooledConnectionIdleTimeout(requestTimeout) .setEnabledCipherSuites(enabledCipherSuites) .setFollowRedirect(true) + .setSslContext(sslContext) .build(); return new DefaultAsyncHttpClient(config); diff --git a/fili-core/src/test/groovy/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImplSpec.groovy b/fili-core/src/test/groovy/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImplSpec.groovy index 28c697e218..551433aab2 100644 --- a/fili-core/src/test/groovy/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImplSpec.groovy +++ b/fili-core/src/test/groovy/com/yahoo/bard/webservice/druid/client/impl/AsyncDruidWebServiceImplSpec.groovy @@ -10,6 +10,8 @@ import com.yahoo.bard.webservice.druid.model.query.WeightEvaluationQuery import com.fasterxml.jackson.databind.ObjectMapper import io.netty.handler.codec.http.HttpHeaders +import io.netty.handler.ssl.SslContext +import org.asynchttpclient.AsyncHttpClient; import spock.lang.Specification import java.util.function.Supplier @@ -71,4 +73,16 @@ class AsyncDruidWebServiceImplSpec extends Specification { assert actualHeaders.get(header.getKey()) == header.getValue() } } + + def "Check whether given ssl context was set in HttpClient"() { + setup: + SslContext sslContext = Mock(SslContext); + + when: + AsyncHttpClient httpClient = AsyncDruidWebServiceImpl.initializeWebClient(6000, sslContext); + + then: + assert httpClient.getConfig().getSslContext() == sslContext; + + } }