diff options
-rw-r--r-- | container/pom.xml | 4 | ||||
-rw-r--r-- | http-utils/pom.xml | 5 | ||||
-rw-r--r-- | http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java | 93 | ||||
-rw-r--r-- | parent/pom.xml | 6 |
4 files changed, 108 insertions, 0 deletions
diff --git a/container/pom.xml b/container/pom.xml index cf3aa21513b..4f045aac90e 100644 --- a/container/pom.xml +++ b/container/pom.xml @@ -45,6 +45,10 @@ <groupId>io.airlift</groupId> <artifactId>airline</artifactId> </exclusion> + <exclusion> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + </exclusion> </exclusions> </dependency> </dependencies> diff --git a/http-utils/pom.xml b/http-utils/pom.xml index 6d2e009cf8c..aa261574285 100644 --- a/http-utils/pom.xml +++ b/http-utils/pom.xml @@ -38,6 +38,11 @@ <artifactId>httpcore</artifactId> <scope>compile</scope> </dependency> + <dependency> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + <scope>compile</scope> + </dependency> <!-- test scope --> <dependency> diff --git a/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java b/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java new file mode 100644 index 00000000000..00f16e5d7c7 --- /dev/null +++ b/http-utils/src/main/java/ai/vespa/util/http/VespaAsyncHttpClientBuilder.java @@ -0,0 +1,93 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.util.http; + +import com.yahoo.security.tls.MixedMode; +import com.yahoo.security.tls.TlsContext; +import com.yahoo.security.tls.TransportSecurityUtils; +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner; +import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http.protocol.HttpContext; + +import javax.net.ssl.SSLParameters; + +/** + * Async http client builder for internal Vespa communications over http/https. + * Configures Vespa mTLS and handles TLS mixed mode automatically. + * Client should only be used for requests to Vespa services. + * + * Caveats: + * - custom connection manager must be configured through {@link #create(AsyncConnectionManagerFactory)}. + * + * @author bjorncs + */ +public class VespaAsyncHttpClientBuilder { + + public interface AsyncConnectionManagerFactory { + AsyncClientConnectionManager create(TlsStrategy tlsStrategy); + } + + public static HttpAsyncClientBuilder create() { + return create( + tlsStrategy -> PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(tlsStrategy) + .build()); + } + + public static HttpAsyncClientBuilder create(AsyncConnectionManagerFactory factory) { + HttpAsyncClientBuilder clientBuilder = HttpAsyncClientBuilder.create(); + TlsContext vespaTlsContext = TransportSecurityUtils.createTlsContext().orElse(null); + TlsStrategy tlsStrategy; + if (vespaTlsContext != null) { + SSLParameters vespaTlsParameters = vespaTlsContext.parameters(); + tlsStrategy = ClientTlsStrategyBuilder.create() + .setHostnameVerifier(new NoopHostnameVerifier()) + .setSslContext(vespaTlsContext.context()) + .setTlsVersions(vespaTlsParameters.getProtocols()) + .setCiphers(vespaTlsParameters.getCipherSuites()) + .build(); + if (TransportSecurityUtils.getInsecureMixedMode() != MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER) { + clientBuilder.setRoutePlanner(new HttpToHttpsRoutePlanner()); + } + } else { + tlsStrategy = ClientTlsStrategyBuilder.create().build(); + } + clientBuilder.disableConnectionState(); // Share connections between subsequent requests + clientBuilder.disableCookieManagement(); + clientBuilder.setConnectionManager(factory.create(tlsStrategy)); + return clientBuilder; + } + + private static class HttpToHttpsRoutePlanner implements HttpRoutePlanner { + + private final DefaultRoutePlanner defaultPlanner = new DefaultRoutePlanner(new DefaultSchemePortResolver()); + + @Override + public HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException { + HttpRoute originalRoute = defaultPlanner.determineRoute(target, context); + HttpHost originalHost = originalRoute.getTargetHost(); + String originalScheme = originalHost.getSchemeName(); + String rewrittenScheme = originalScheme.equalsIgnoreCase("http") ? "https" : originalScheme; + boolean rewrittenSecure = target.getSchemeName().equalsIgnoreCase("https"); + HttpHost rewrittenHost = new HttpHost( + rewrittenScheme, originalHost.getAddress(), originalHost.getHostName(), originalHost.getPort()); + return new HttpRoute( + rewrittenHost, + originalRoute.getLocalAddress(), + originalRoute.getProxyHost(), + rewrittenSecure, + originalRoute.getTunnelType(), + originalRoute.getLayerType()); + } + } + +} diff --git a/parent/pom.xml b/parent/pom.xml index cac59966332..72aa330fe13 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -575,6 +575,11 @@ <version>${apache.httpcore.version}</version> </dependency> <dependency> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + <version>${apache.httpclient5.version}</version> + </dependency> + <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.3.6</version> @@ -749,6 +754,7 @@ <antlr4.version>4.5</antlr4.version> <apache.httpclient.version>4.5.12</apache.httpclient.version> <apache.httpcore.version>4.4.13</apache.httpcore.version> + <apache.httpclient5.version>5.0.3</apache.httpclient5.version> <asm.version>7.0</asm.version> <!-- Athenz dependencies. Make sure these dependencies match those in Vespa's internal repositories --> <athenz.version>1.8.49</athenz.version> |