diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-02-21 15:26:50 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-02-21 15:30:27 +0100 |
commit | 432aa33d62644ea97fef2bfd4003e1ea603ed04a (patch) | |
tree | aa6af6608d016d5eb90d6498e306d3d56ceedf0c /security-utils | |
parent | 81552c6b6014118007449547c21a0aa779c37739 (diff) |
Introduce http client that follows Vespa TLS config
Diffstat (limited to 'security-utils')
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 |