summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2019-02-21 16:08:24 +0100
committerGitHub <noreply@github.com>2019-02-21 16:08:24 +0100
commit69e4f91fa50744e4709410da488ae47fb04c3335 (patch)
tree37a3e171e39c0fda7a6fe580e30424b0b96419b4
parent6271efca50863d07086302c6ed5a7140b7f441af (diff)
parent432aa33d62644ea97fef2bfd4003e1ea603ed04a (diff)
Merge pull request #8577 from vespa-engine/bjorncs/tls-aware-http-client
Introduce http client that follows Vespa TLS config
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClient.java101
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClientBuilder.java97
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpRequest.java103
-rw-r--r--security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java8
4 files changed, 309 insertions, 0 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClient.java b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClient.java
new file mode 100644
index 00000000000..2911b77707a
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClient.java
@@ -0,0 +1,101 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security.tls.https;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ProxySelector;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link HttpClient} that uses either http or https based on the global Vespa TLS configuration.
+ *
+ * @author bjorncs
+ */
+class TlsAwareHttpClient extends HttpClient {
+
+ private final HttpClient wrappedClient;
+ private final String userAgent;
+
+ TlsAwareHttpClient(HttpClient wrappedClient, String userAgent) {
+ this.wrappedClient = wrappedClient;
+ this.userAgent = userAgent;
+ }
+
+ @Override
+ public Optional<CookieHandler> cookieHandler() {
+ return wrappedClient.cookieHandler();
+ }
+
+ @Override
+ public Optional<Duration> connectTimeout() {
+ return wrappedClient.connectTimeout();
+ }
+
+ @Override
+ public Redirect followRedirects() {
+ return wrappedClient.followRedirects();
+ }
+
+ @Override
+ public Optional<ProxySelector> proxy() {
+ return wrappedClient.proxy();
+ }
+
+ @Override
+ public SSLContext sslContext() {
+ return wrappedClient.sslContext();
+ }
+
+ @Override
+ public SSLParameters sslParameters() {
+ return wrappedClient.sslParameters();
+ }
+
+ @Override
+ public Optional<Authenticator> authenticator() {
+ return wrappedClient.authenticator();
+ }
+
+ @Override
+ public Version version() {
+ return wrappedClient.version();
+ }
+
+ @Override
+ public Optional<Executor> executor() {
+ return wrappedClient.executor();
+ }
+
+ @Override
+ public <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException {
+ return wrappedClient.send(wrapRequest(request), responseBodyHandler);
+ }
+
+ @Override
+ public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) {
+ return wrappedClient.sendAsync(wrapRequest(request), responseBodyHandler);
+ }
+
+ @Override
+ public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, HttpResponse.PushPromiseHandler<T> pushPromiseHandler) {
+ return wrappedClient.sendAsync(wrapRequest(request), responseBodyHandler, pushPromiseHandler);
+ }
+
+ @Override
+ public String toString() {
+ return wrappedClient.toString();
+ }
+
+ private HttpRequest wrapRequest(HttpRequest request) {
+ return new TlsAwareHttpRequest(request, userAgent);
+ }
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClientBuilder.java b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClientBuilder.java
new file mode 100644
index 00000000000..7eca2463ba7
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpClientBuilder.java
@@ -0,0 +1,97 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security.tls.https;
+
+import com.yahoo.security.tls.TlsContext;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ProxySelector;
+import java.net.http.HttpClient;
+import java.time.Duration;
+import java.util.concurrent.Executor;
+
+/**
+ * A client builder for {@link HttpClient} which uses {@link TlsContext} for TLS configuration.
+ * Intended for internal Vespa communication only.
+ *
+ * @author bjorncs
+ */
+public class TlsAwareHttpClientBuilder implements HttpClient.Builder {
+
+ private final HttpClient.Builder wrappedBuilder;
+ private final String userAgent;
+
+ public TlsAwareHttpClientBuilder(TlsContext tlsContext) {
+ this(tlsContext, "vespa-tls-aware-client");
+ }
+
+ public TlsAwareHttpClientBuilder(TlsContext tlsContext, String userAgent) {
+ this.wrappedBuilder = HttpClient.newBuilder()
+ .sslContext(tlsContext.context())
+ .sslParameters(tlsContext.parameters());
+ this.userAgent = userAgent;
+ }
+
+ @Override
+ public HttpClient.Builder cookieHandler(CookieHandler cookieHandler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpClient.Builder connectTimeout(Duration duration) {
+ wrappedBuilder.connectTimeout(duration);
+ return this;
+ }
+
+ @Override
+ public HttpClient.Builder sslContext(SSLContext sslContext) {
+ throw new UnsupportedOperationException("SSLContext is given from tls context");
+ }
+
+ @Override
+ public HttpClient.Builder sslParameters(SSLParameters sslParameters) {
+ throw new UnsupportedOperationException("SSLParameters is given from tls context");
+ }
+
+ @Override
+ public HttpClient.Builder executor(Executor executor) {
+ wrappedBuilder.executor(executor);
+ return this;
+ }
+
+ @Override
+ public HttpClient.Builder followRedirects(HttpClient.Redirect policy) {
+ wrappedBuilder.followRedirects(policy);
+ return this;
+ }
+
+ @Override
+ public HttpClient.Builder version(HttpClient.Version version) {
+ wrappedBuilder.version(version);
+ return this;
+ }
+
+ @Override
+ public HttpClient.Builder priority(int priority) {
+ wrappedBuilder.priority(priority);
+ return this;
+ }
+
+ @Override
+ public HttpClient.Builder proxy(ProxySelector proxySelector) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpClient.Builder authenticator(Authenticator authenticator) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpClient build() {
+ // TODO Stop wrapping the client once TLS is mandatory
+ return new TlsAwareHttpClient(wrappedBuilder.build(), userAgent);
+ }
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpRequest.java b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpRequest.java
new file mode 100644
index 00000000000..bbdd8af791f
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/https/TlsAwareHttpRequest.java
@@ -0,0 +1,103 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.security.tls.https;
+
+import com.yahoo.security.tls.MixedMode;
+import com.yahoo.security.tls.TransportSecurityUtils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A {@link HttpRequest} where the scheme is either http or https based on the global Vespa TLS configuration.
+ *
+ * @author bjorncs
+ */
+class TlsAwareHttpRequest extends HttpRequest {
+
+ private final URI rewrittenUri;
+ private final HttpRequest wrappedRequest;
+ private final HttpHeaders rewrittenHeaders;
+
+ TlsAwareHttpRequest(HttpRequest wrappedRequest, String userAgent) {
+ this.wrappedRequest = wrappedRequest;
+ this.rewrittenUri = rewriteUri(wrappedRequest.uri());
+ this.rewrittenHeaders = rewriteHeaders(wrappedRequest, userAgent);
+ }
+
+ @Override
+ public Optional<BodyPublisher> bodyPublisher() {
+ return wrappedRequest.bodyPublisher();
+ }
+
+ @Override
+ public String method() {
+ return wrappedRequest.method();
+ }
+
+ @Override
+ public Optional<Duration> timeout() {
+ return wrappedRequest.timeout();
+ }
+
+ @Override
+ public boolean expectContinue() {
+ return wrappedRequest.expectContinue();
+ }
+
+ @Override
+ public URI uri() {
+ return rewrittenUri;
+ }
+
+ @Override
+ public Optional<HttpClient.Version> version() {
+ return wrappedRequest.version();
+ }
+
+ @Override
+ public HttpHeaders headers() {
+ return rewrittenHeaders;
+ }
+
+ private static URI rewriteUri(URI uri) {
+ if (!uri.getScheme().equals("http")) {
+ return uri;
+ }
+ String rewrittenScheme =
+ TransportSecurityUtils.getConfigFile().isPresent() && TransportSecurityUtils.getInsecureMixedMode() != MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER ?
+ "https" :
+ "http";
+ int port = uri.getPort();
+ int rewrittenPort = port != -1 ? port : (rewrittenScheme.equals("http") ? 80 : 443);
+ try {
+ return new URI(rewrittenScheme, uri.getUserInfo(), uri.getHost(), rewrittenPort, uri.getPath(), uri.getQuery(), uri.getFragment());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static HttpHeaders rewriteHeaders(HttpRequest request, String userAgent) {
+ HttpHeaders headers = request.headers();
+ if (headers.firstValue("User-Agent").isPresent()) {
+ return headers;
+ }
+ HashMap<String, List<String>> rewrittenHeaders = new HashMap<>(headers.map());
+ rewrittenHeaders.put("User-Agent", List.of(userAgent));
+ return HttpHeaders.of(rewrittenHeaders, (ignored1, ignored2) -> true);
+ }
+
+ @Override
+ public String toString() {
+ return "TlsAwareHttpRequest{" +
+ "rewrittenUri=" + rewrittenUri +
+ ", wrappedRequest=" + wrappedRequest +
+ '}';
+ }
+}
diff --git a/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java b/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java
new file mode 100644
index 00000000000..43067705fa3
--- /dev/null
+++ b/security-utils/src/main/java/com/yahoo/security/tls/https/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
+@ExportPackage
+package com.yahoo.security.tls.https;
+
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file