diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-11-28 21:35:16 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-11-28 21:35:16 +0100 |
commit | 1d6791e6fa004ae80e85dbc6a6c7c2e4b8037a4f (patch) | |
tree | 650307f35d321145410248f703943ef7525f94fb /jdisc_http_service/src/main/java/com | |
parent | 0606896d63cc8bbe4919c7c37126fb9bc3f6e34e (diff) | |
parent | 7e8f8da8f249cf3c529cec8ecdcf13b69c99da13 (diff) |
Merge with master
Diffstat (limited to 'jdisc_http_service/src/main/java/com')
29 files changed, 458 insertions, 1012 deletions
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java index 2268b568b18..21e492fe57e 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/HttpRequest.java @@ -16,6 +16,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; +import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -71,6 +72,7 @@ public class HttpRequest extends Request implements ServletOrJdiscHttpRequest { private final HeaderFields trailers = new HeaderFields(); private final Map<String, List<String>> parameters = new HashMap<>(); + private Principal principal; private final long connectedAt; private Method method; private Version version; @@ -294,6 +296,14 @@ public class HttpRequest extends Request implements ServletOrJdiscHttpRequest { return version == Version.HTTP_1_1; } + public Principal getUserPrincipal() { + return principal; + } + + public void setUserPrincipal(Principal principal) { + this.principal = principal; + } + public static HttpRequest newServerRequest(CurrentContainer container, URI uri) { return newServerRequest(container, uri, Method.GET); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java index a46d35f8e70..617f0cbd184 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java @@ -36,14 +36,12 @@ public abstract class DiscFilterRequest { protected static final String HTTPS_PREFIX = "https"; protected static final int DEFAULT_HTTP_PORT = 80; protected static final int DEFAULT_HTTPS_PORT = 443; - private static final String JDISC_REQUEST_PRINCIPAL = "jdisc.request.principal"; private final ServletOrJdiscHttpRequest parent; protected final InetSocketAddress localAddress; protected final Map<String, List<String>> untreatedParams; private final HeaderFields untreatedHeaders; private List<Cookie> untreatedCookies = null; - private Principal userPrincipal = null; private String remoteUser = null; private String[] roles = null; private boolean overrideIsUserInRole = false; @@ -330,9 +328,7 @@ public abstract class DiscFilterRequest { return port; } - public Principal getUserPrincipal() { - return (Principal) getAttribute(JDISC_REQUEST_PRINCIPAL); - } + public abstract Principal getUserPrincipal(); public boolean isSecure() { if(getScheme().equalsIgnoreCase(HTTPS_PREFIX)) { @@ -375,9 +371,7 @@ public abstract class DiscFilterRequest { this.remoteUser = remoteUser; } - public void setUserPrincipal(Principal principal) { - setAttribute(JDISC_REQUEST_PRINCIPAL, principal); - } + public abstract void setUserPrincipal(Principal principal); public void setUserRoles(String[] roles) { this.roles = roles; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java index 1e9d09ecb17..07e3b97ba90 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java @@ -5,6 +5,7 @@ import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpRequest; import java.net.URI; +import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -103,6 +104,16 @@ public class JdiscFilterRequest extends DiscFilterRequest { } @Override + public Principal getUserPrincipal() { + return parent.getUserPrincipal(); + } + + @Override + public void setUserPrincipal(Principal principal) { + this.parent.setUserPrincipal(principal); + } + + @Override public void clearCookies() { parent.headers().remove(HttpHeaders.Names.COOKIE); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java index 0fd52d3f12a..11c2baf0176 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java @@ -6,6 +6,7 @@ import com.yahoo.jdisc.http.servlet.ServletRequest; import java.io.UnsupportedEncodingException; import java.net.URI; +import java.security.Principal; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; @@ -128,6 +129,16 @@ class ServletFilterRequest extends DiscFilterRequest { } @Override + public Principal getUserPrincipal() { + return parent.getUserPrincipal(); + } + + @Override + public void setUserPrincipal(Principal principal) { + parent.setUserPrincipal(principal); + } + + @Override public void removeHeaders(String name) { parent.removeHeaders(name); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java index 3363c7d3284..c3c83474e56 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java @@ -5,6 +5,7 @@ import com.google.common.base.Objects; import com.yahoo.container.logging.AccessLog; import com.yahoo.container.logging.AccessLogEntry; +import com.yahoo.jdisc.http.servlet.ServletRequest; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; @@ -17,6 +18,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.security.Principal; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -107,6 +109,12 @@ public class AccessLogRequestLog extends AbstractLifeCycle implements RequestLog accessLogEntry.setPeerPort(peerPort); } accessLogEntry.setHttpVersion(request.getProtocol()); + accessLogEntry.setScheme(request.getScheme()); + accessLogEntry.setLocalPort(request.getLocalPort()); + Principal principal = (Principal) request.getAttribute(ServletRequest.JDISC_REQUEST_PRINCIPAL); + if (principal != null) { + accessLogEntry.setUserPrincipal(principal); + } } private static String getRemoteAddress(final HttpServletRequest request) { diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java index 43513b4efba..e30d50ecdbf 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java @@ -54,24 +54,27 @@ public class AccessLoggingRequestHandler extends AbstractRequestHandler { Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request); final HttpRequest httpRequest = (HttpRequest) request; httpRequest.context().put(CONTEXT_KEY_ACCESS_LOG_ENTRY, accessLogEntry); - final ResponseHandler accessLoggingResponseHandler = new AccessLoggingResponseHandler(handler, accessLogEntry); + final ResponseHandler accessLoggingResponseHandler = new AccessLoggingResponseHandler(httpRequest, handler, accessLogEntry); final ContentChannel requestContentChannel = delegate.handleRequest(request, accessLoggingResponseHandler); return requestContentChannel; } private static class AccessLoggingResponseHandler implements ResponseHandler { + private final HttpRequest request; private final ResponseHandler delegateHandler; private final AccessLogEntry accessLogEntry; public AccessLoggingResponseHandler( - final ResponseHandler delegateHandler, + HttpRequest request, final ResponseHandler delegateHandler, final AccessLogEntry accessLogEntry) { + this.request = request; this.delegateHandler = delegateHandler; this.accessLogEntry = accessLogEntry; } @Override public ContentChannel handleResponse(Response response) { + accessLogEntry.setUserPrincipal(request.getUserPrincipal()); return delegateHandler.handleResponse(response); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java index 96180f48229..8255e16e0ee 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java @@ -1,66 +1,41 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.server.jetty; -import com.google.common.base.Preconditions; import com.google.inject.Inject; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ConnectorConfig.Ssl; -import com.yahoo.jdisc.http.ConnectorConfig.Ssl.PemKeyStore; import com.yahoo.jdisc.http.SecretStore; -import com.yahoo.jdisc.http.ssl.ReaderForPath; -import com.yahoo.jdisc.http.ssl.SslKeyStore; -import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore; +import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreContext; +import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnectionStatistics; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; -import javax.servlet.ServletRequest; -import java.io.IOException; -import java.io.Reader; -import java.lang.reflect.Field; -import java.net.Socket; -import java.net.SocketException; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.google.common.io.Closeables.closeQuietly; -import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.JKS; -import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.PEM; -import static com.yahoo.jdisc.http.server.jetty.Exceptions.throwUnchecked; /** * @author Einar M R Rosenvinge + * @author bjorncs */ public class ConnectorFactory { - private final static Logger log = Logger.getLogger(ConnectorFactory.class.getName()); private final ConnectorConfig connectorConfig; private final SecretStore secretStore; + private final SslKeyStoreConfigurator sslKeyStoreConfigurator; @Inject - public ConnectorFactory(ConnectorConfig connectorConfig, SecretStore secretStore) { + public ConnectorFactory(ConnectorConfig connectorConfig, + SecretStore secretStore, + SslKeyStoreConfigurator sslKeyStoreConfigurator) { this.connectorConfig = connectorConfig; this.secretStore = secretStore; + this.sslKeyStoreConfigurator = sslKeyStoreConfigurator; if (connectorConfig.ssl().enabled()) validateSslConfig(connectorConfig); @@ -69,14 +44,8 @@ public class ConnectorFactory { // TODO: can be removed when we have dedicated SSL config in services.xml private static void validateSslConfig(ConnectorConfig config) { ConnectorConfig.Ssl ssl = config.ssl(); - - if (ssl.keyStoreType() == JKS) { - if (! ssl.pemKeyStore().keyPath().isEmpty() || ! ssl.pemKeyStore().certificatePath().isEmpty()) - throw new IllegalArgumentException("pemKeyStore attributes can not be set when keyStoreType is JKS."); - } - if (ssl.keyStoreType() == PEM) { - if (! ssl.keyStorePath().isEmpty()) - throw new IllegalArgumentException("keyStorePath can not be set when keyStoreType is PEM"); + if (!ssl.trustStorePath().isEmpty() && ssl.useTrustStorePassword() && ssl.keyDbKey().isEmpty()) { + throw new IllegalArgumentException("Missing password for JKS truststore"); } } @@ -84,11 +53,11 @@ public class ConnectorFactory { return connectorConfig; } - public ServerConnector createConnector(final Metric metric, final Server server, final ServerSocketChannel ch, Map<Path, FileChannel> keyStoreChannels) { + public ServerConnector createConnector(final Metric metric, final Server server, final ServerSocketChannel ch) { ServerConnector connector; if (connectorConfig.ssl().enabled()) { connector = new JDiscServerConnector(connectorConfig, metric, server, ch, - newSslConnectionFactory(keyStoreChannels), + newSslConnectionFactory(), newHttpConnectionFactory()); } else { connector = new JDiscServerConnector(connectorConfig, metric, server, ch, @@ -125,10 +94,13 @@ public class ConnectorFactory { } //TODO: does not support loading non-yahoo readable JKS key stores. - private SslConnectionFactory newSslConnectionFactory(Map<Path, FileChannel> keyStoreChannels) { + private SslConnectionFactory newSslConnectionFactory() { Ssl sslConfig = connectorConfig.ssl(); SslContextFactory factory = new SslContextFactory(); + + sslKeyStoreConfigurator.configure(new DefaultSslKeyStoreContext(factory)); + switch (sslConfig.clientAuth()) { case NEED_AUTH: factory.setNeedClientAuth(true); @@ -172,25 +144,14 @@ public class ConnectorFactory { factory.setIncludeCipherSuites(ciphs); } - Optional<String> keyDbPassword = secret(sslConfig.keyDbKey()); - switch (sslConfig.keyStoreType()) { - case PEM: - factory.setKeyStore(getKeyStore(sslConfig.pemKeyStore(), keyStoreChannels)); - if (keyDbPassword.isPresent()) - log.warning("Encrypted PEM key stores are not supported."); - break; - case JKS: - factory.setKeyStorePath(sslConfig.keyStorePath()); - factory.setKeyStoreType(sslConfig.keyStoreType().toString()); - factory.setKeyStorePassword(keyDbPassword.orElseThrow(passwordRequiredForJKSKeyStore("key"))); - break; - } + String keyDbPassword = sslConfig.keyDbKey(); if (!sslConfig.trustStorePath().isEmpty()) { factory.setTrustStorePath(sslConfig.trustStorePath()); factory.setTrustStoreType(sslConfig.trustStoreType().toString()); - if (sslConfig.useTrustStorePassword()) - factory.setTrustStorePassword(keyDbPassword.orElseThrow(passwordRequiredForJKSKeyStore("trust"))); + if (sslConfig.useTrustStorePassword()) { + factory.setTrustStorePassword(secretStore.getSecret(keyDbPassword)); + } } factory.setKeyManagerFactoryAlgorithm(sslConfig.sslKeyManagerFactoryAlgorithm()); @@ -198,162 +159,4 @@ public class ConnectorFactory { return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString()); } - /** Returns the secret password with the given name, or empty if the password name is null or empty */ - private Optional<String> secret(String keyname) { - return Optional.of(keyname).filter(key -> !key.isEmpty()).map(secretStore::getSecret); - } - - @SuppressWarnings("ThrowableInstanceNeverThrown") - private Supplier<RuntimeException> passwordRequiredForJKSKeyStore(String type) { - return () -> new RuntimeException(String.format("Password is required for JKS %s store", type)); - } - - private KeyStore getKeyStore(PemKeyStore pemKeyStore, Map<Path, FileChannel> keyStoreChannels) { - Preconditions.checkArgument(!pemKeyStore.certificatePath().isEmpty(), "Missing certificate path."); - Preconditions.checkArgument(!pemKeyStore.keyPath().isEmpty(), "Missing key path."); - - class KeyStoreReaderForPath implements AutoCloseable { - private final Optional<FileChannel> channel; - public final ReaderForPath readerForPath; - - - KeyStoreReaderForPath(String pathString) { - Path path = Paths.get(pathString); - channel = Optional.ofNullable(keyStoreChannels.get(path)); - readerForPath = new ReaderForPath(channel.map(this::getReader).orElseGet(() -> getReader(path)), path); - } - - private Reader getReader(FileChannel channel) { - try { - channel.position(0); - return Channels.newReader(channel, StandardCharsets.UTF_8.newDecoder(), -1); - } catch (IOException e) { - throw throwUnchecked(e); - } - - } - - private Reader getReader(Path path) { - try { - return Files.newBufferedReader(path); - } catch (IOException e) { - throw new RuntimeException("Failed opening " + path, e); - } - } - - @Override - public void close() { - //channels are reused - if (!channel.isPresent()) { - closeQuietly(readerForPath.reader); - } - } - } - - try (KeyStoreReaderForPath certificateReader = new KeyStoreReaderForPath(pemKeyStore.certificatePath()); - KeyStoreReaderForPath keyReader = new KeyStoreReaderForPath(pemKeyStore.keyPath())) { - SslKeyStore keyStore = new PemSslKeyStore( - new com.yahoo.jdisc.http.ssl.pem.PemKeyStore.KeyStoreLoadParameter( - certificateReader.readerForPath, keyReader.readerForPath)); - return keyStore.loadJavaKeyStore(); - } catch (Exception e) { - throw new RuntimeException("Failed setting up key store for " + pemKeyStore.keyPath() + ", " + pemKeyStore.certificatePath(), e); - } - } - - public static class JDiscServerConnector extends ServerConnector { - public static final String REQUEST_ATTRIBUTE = JDiscServerConnector.class.getName(); - private final static Logger log = Logger.getLogger(JDiscServerConnector.class.getName()); - private final Metric.Context metricCtx; - private final ServerConnectionStatistics statistics; - private final boolean tcpKeepAlive; - private final boolean tcpNoDelay; - private final ServerSocketChannel channelOpenedByActivator; - - private JDiscServerConnector(ConnectorConfig config, Metric metric, Server server, - ServerSocketChannel channelOpenedByActivator, ConnectionFactory... factories) { - super(server, factories); - this.channelOpenedByActivator = channelOpenedByActivator; - this.tcpKeepAlive = config.tcpKeepAliveEnabled(); - this.tcpNoDelay = config.tcpNoDelay(); - this.metricCtx = createMetricContext(config, metric); - - this.statistics = new ServerConnectionStatistics(); - addBean(statistics); - } - - private Metric.Context createMetricContext(ConnectorConfig config, Metric metric) { - Map<String, Object> props = new TreeMap<>(); - props.put(JettyHttpServer.Metrics.NAME_DIMENSION, config.name()); - props.put(JettyHttpServer.Metrics.PORT_DIMENSION, config.listenPort()); - return metric.createContext(props); - } - - @Override - protected void configure(final Socket socket) { - super.configure(socket); - try { - socket.setKeepAlive(tcpKeepAlive); - socket.setTcpNoDelay(tcpNoDelay); - } catch (SocketException ignored) { - } - } - - @Override - public void open() throws IOException { - if (channelOpenedByActivator == null) { - log.log(Level.INFO, "No channel set by activator, opening channel ourselves."); - try { - super.open(); - } catch (RuntimeException e) { - log.log(Level.SEVERE, "failed org.eclipse.jetty.server.Server open() with port "+getPort()); - throw e; - } - return; - } - log.log(Level.INFO, "Using channel set by activator: " + channelOpenedByActivator); - - channelOpenedByActivator.socket().setReuseAddress(getReuseAddress()); - int localPort = channelOpenedByActivator.socket().getLocalPort(); - try { - uglySetLocalPort(localPort); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException("Could not set local port.", e); - } - if (localPort <= 0) { - throw new IOException("Server channel not bound"); - } - addBean(channelOpenedByActivator); - channelOpenedByActivator.configureBlocking(true); - addBean(channelOpenedByActivator); - - try { - uglySetChannel(channelOpenedByActivator); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException("Could not set server channel.", e); - } - } - - private void uglySetLocalPort(int localPort) throws NoSuchFieldException, IllegalAccessException { - Field localPortField = ServerConnector.class.getDeclaredField("_localPort"); - localPortField.setAccessible(true); - localPortField.set(this, localPort); - } - - private void uglySetChannel(ServerSocketChannel channelOpenedByActivator) throws NoSuchFieldException, - IllegalAccessException { - Field acceptChannelField = ServerConnector.class.getDeclaredField("_acceptChannel"); - acceptChannelField.setAccessible(true); - acceptChannelField.set(this, channelOpenedByActivator); - } - - public ServerConnectionStatistics getStatistics() { return statistics; } - - public Metric.Context getMetricContext() { return metricCtx; } - - public static JDiscServerConnector fromRequest(ServletRequest request) { - return (JDiscServerConnector)request.getAttribute(REQUEST_ATTRIBUTE); - } - } - } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java index 7f169c7c8d0..5cabe8acd27 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java @@ -12,6 +12,7 @@ import com.yahoo.jdisc.handler.OverloadException; import com.yahoo.jdisc.handler.RequestHandler; import com.yahoo.jdisc.http.HttpHeaders; import com.yahoo.jdisc.http.HttpRequest; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.HttpConnection; import javax.servlet.AsyncContext; @@ -122,7 +123,11 @@ class HttpRequestDispatch { boolean reportedError = false; if (error != null) { - if (!(error instanceof OverloadException || error instanceof BindingNotFoundException)) { + if (error instanceof EofException) { + log.log(Level.FINE, + "Network connection was unexpectedly terminated: " + parent.servletRequest.getRequestURI(), + error); + } else if (!(error instanceof OverloadException || error instanceof BindingNotFoundException)) { log.log(Level.WARNING, "Request failed: " + parent.servletRequest.getRequestURI(), error); } reportedError = true; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java index 543cf8ab43e..27f72c7b4bf 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java @@ -20,7 +20,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import static com.yahoo.jdisc.http.core.HttpServletRequestUtils.getConnection; -import static com.yahoo.jdisc.http.server.jetty.ConnectorFactory.JDiscServerConnector; /** * @author Simon Thoresen Hult diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java new file mode 100644 index 00000000000..8dd50074c32 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscServerConnector.java @@ -0,0 +1,122 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty; + +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.http.ConnectorConfig; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnectionStatistics; +import org.eclipse.jetty.server.ServerConnector; + +import javax.servlet.ServletRequest; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.Socket; +import java.net.SocketException; +import java.nio.channels.ServerSocketChannel; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author bjorncs + */ +class JDiscServerConnector extends ServerConnector { + public static final String REQUEST_ATTRIBUTE = JDiscServerConnector.class.getName(); + private final static Logger log = Logger.getLogger(JDiscServerConnector.class.getName()); + private final Metric.Context metricCtx; + private final ServerConnectionStatistics statistics; + private final boolean tcpKeepAlive; + private final boolean tcpNoDelay; + private final ServerSocketChannel channelOpenedByActivator; + + JDiscServerConnector(ConnectorConfig config, Metric metric, Server server, + ServerSocketChannel channelOpenedByActivator, ConnectionFactory... factories) { + super(server, factories); + this.channelOpenedByActivator = channelOpenedByActivator; + this.tcpKeepAlive = config.tcpKeepAliveEnabled(); + this.tcpNoDelay = config.tcpNoDelay(); + this.metricCtx = createMetricContext(config, metric); + + this.statistics = new ServerConnectionStatistics(); + addBean(statistics); + } + + private Metric.Context createMetricContext(ConnectorConfig config, Metric metric) { + Map<String, Object> props = new TreeMap<>(); + props.put(JettyHttpServer.Metrics.NAME_DIMENSION, config.name()); + props.put(JettyHttpServer.Metrics.PORT_DIMENSION, config.listenPort()); + return metric.createContext(props); + } + + @Override + protected void configure(final Socket socket) { + super.configure(socket); + try { + socket.setKeepAlive(tcpKeepAlive); + socket.setTcpNoDelay(tcpNoDelay); + } catch (SocketException ignored) { + } + } + + @Override + public void open() throws IOException { + if (channelOpenedByActivator == null) { + log.log(Level.INFO, "No channel set by activator, opening channel ourselves."); + try { + super.open(); + } catch (RuntimeException e) { + log.log(Level.SEVERE, "failed org.eclipse.jetty.server.Server open() with port " + getPort()); + throw e; + } + return; + } + log.log(Level.INFO, "Using channel set by activator: " + channelOpenedByActivator); + + channelOpenedByActivator.socket().setReuseAddress(getReuseAddress()); + int localPort = channelOpenedByActivator.socket().getLocalPort(); + try { + uglySetLocalPort(localPort); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Could not set local port.", e); + } + if (localPort <= 0) { + throw new IOException("Server channel not bound"); + } + addBean(channelOpenedByActivator); + channelOpenedByActivator.configureBlocking(true); + addBean(channelOpenedByActivator); + + try { + uglySetChannel(channelOpenedByActivator); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Could not set server channel.", e); + } + } + + private void uglySetLocalPort(int localPort) throws NoSuchFieldException, IllegalAccessException { + Field localPortField = ServerConnector.class.getDeclaredField("_localPort"); + localPortField.setAccessible(true); + localPortField.set(this, localPort); + } + + private void uglySetChannel(ServerSocketChannel channelOpenedByActivator) throws NoSuchFieldException, + IllegalAccessException { + Field acceptChannelField = ServerConnector.class.getDeclaredField("_acceptChannel"); + acceptChannelField.setAccessible(true); + acceptChannelField.set(this, channelOpenedByActivator); + } + + public ServerConnectionStatistics getStatistics() { + return statistics; + } + + public Metric.Context getMetricContext() { + return metricCtx; + } + + public static JDiscServerConnector fromRequest(ServletRequest request) { + return (JDiscServerConnector) request.getAttribute(REQUEST_ATTRIBUTE); + } +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java index 509bf42d466..7bff685e780 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java @@ -44,15 +44,11 @@ import javax.servlet.DispatcherType; import java.lang.management.ManagementFactory; import java.net.BindException; import java.net.MalformedURLException; -import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -62,9 +58,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import static com.yahoo.jdisc.http.server.jetty.ConnectorFactory.JDiscServerConnector; -import static com.yahoo.jdisc.http.server.jetty.Exceptions.throwUnchecked; - /** * @author Simon Thoresen Hult * @author bjorncs @@ -147,11 +140,9 @@ public class JettyHttpServer extends AbstractServerProvider { setupJmx(server, serverConfig); ((QueuedThreadPool)server.getThreadPool()).setMaxThreads(serverConfig.maxWorkerThreads()); - Map<Path, FileChannel> keyStoreChannels = getKeyStoreFileChannels(osgiFramework.bundleContext()); - for (ConnectorFactory connectorFactory : connectorFactories.allComponents()) { ServerSocketChannel preBoundChannel = getChannelFromServiceLayer(connectorFactory.getConnectorConfig().listenPort(), osgiFramework.bundleContext()); - server.addConnector(connectorFactory.createConnector(metric, server, preBoundChannel, keyStoreChannels)); + server.addConnector(connectorFactory.createConnector(metric, server, preBoundChannel)); listenedPorts.add(connectorFactory.getConnectorConfig().listenPort()); } @@ -257,43 +248,6 @@ public class JettyHttpServer extends AbstractServerProvider { return "/" + servletPathsConfig.servlets(id.stringValue()).path(); } - // Ugly trick to get generic type literal. - @SuppressWarnings("unchecked") - private static final Class<Map<?, ?>> mapClass = (Class<Map<?, ?>>) (Object) Map.class; - - private Map<Path, FileChannel> getKeyStoreFileChannels(BundleContext bundleContext) { - try { - Collection<ServiceReference<Map<?, ?>>> serviceReferences = bundleContext.getServiceReferences(mapClass, - "(role=com.yahoo.container.standalone.StandaloneContainerActivator.KeyStoreFileChannels)"); - - if (serviceReferences == null || serviceReferences.isEmpty()) - return Collections.emptyMap(); - - if (serviceReferences.size() != 1) - throw new IllegalStateException("Multiple KeyStoreFileChannels registered"); - - return getKeyStoreFileChannels(bundleContext, serviceReferences.iterator().next()); - } catch (InvalidSyntaxException e) { - throw throwUnchecked(e); - } - } - - @SuppressWarnings("unchecked") - private Map<Path, FileChannel> getKeyStoreFileChannels(BundleContext bundleContext, ServiceReference<Map<?, ?>> keyStoreFileChannelReference) { - Map<?, ?> fileChannelMap = bundleContext.getService(keyStoreFileChannelReference); - try { - if (fileChannelMap == null) - return Collections.emptyMap(); - - Map<Path, FileChannel> result = (Map<Path, FileChannel>) fileChannelMap; - log.fine("Using file channel for " + result.keySet()); - return result; - } finally { - //if we change this to be anything other than a simple map, we should hold the reference as long as the object is in use. - bundleContext.ungetService(keyStoreFileChannelReference); - } - } - private ServletContextHandler createServletContextHandler() { ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS); servletContextHandler.setContextPath("/"); @@ -324,8 +278,8 @@ public class JettyHttpServer extends AbstractServerProvider { return bundleContext.getService(ref); } - private static ExecutorService newJanitor(final ThreadFactory factory) { - final int threadPoolSize = Runtime.getRuntime().availableProcessors(); + private static ExecutorService newJanitor(ThreadFactory factory) { + int threadPoolSize = Runtime.getRuntime().availableProcessors(); log.info("Creating janitor executor with " + threadPoolSize + " threads"); return Executors.newFixedThreadPool( threadPoolSize, diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java index 3cbe415d39d..db8780b087c 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/servlet/ServletRequest.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletRequestWrapper; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; +import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -36,6 +37,7 @@ import static com.yahoo.jdisc.http.core.HttpServletRequestUtils.getConnection; * @since 5.27 */ public class ServletRequest extends HttpServletRequestWrapper implements ServletOrJdiscHttpRequest { + public static final String JDISC_REQUEST_PRINCIPAL = "jdisc.request.principal"; private final HttpServletRequest request; private final HeaderFields headerFields; @@ -252,4 +254,14 @@ public class ServletRequest extends HttpServletRequestWrapper implements Servlet public long getConnectedAt(TimeUnit unit) { return unit.convert(connectedAt, TimeUnit.MILLISECONDS); } + + @Override + public Principal getUserPrincipal() { + // NOTE: The principal from the underlying servlet request is ignored. JDisc filters are the source-of-truth. + return (Principal) request.getAttribute(JDISC_REQUEST_PRINCIPAL); + } + + public void setUserPrincipal(Principal principal) { + request.setAttribute(JDISC_REQUEST_PRINCIPAL, principal); + } } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java new file mode 100644 index 00000000000..fb0a5869bb3 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java @@ -0,0 +1,95 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import com.google.inject.Inject; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.SecretStore; +import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.util.logging.Logger; + +/** + * @author bjorncs + */ +public class DefaultSslKeyStoreConfigurator implements SslKeyStoreConfigurator { + + private static final Logger log = Logger.getLogger(DefaultSslKeyStoreConfigurator.class.getName()); + + private final SecretStore secretStore; + private final ConnectorConfig.Ssl config; + + @Inject + public DefaultSslKeyStoreConfigurator(ConnectorConfig config, SecretStore secretStore) { + validateConfig(config.ssl()); + this.secretStore = secretStore; + this.config = config.ssl(); + } + + private static void validateConfig(ConnectorConfig.Ssl config) { + if (!config.enabled()) return; + switch (config.keyStoreType()) { + case JKS: + validateJksConfig(config); + break; + case PEM: + validatePemConfig(config); + break; + } + } + + @Override + public void configure(SslKeyStoreContext context) { + if (!config.enabled()) return; + switch (config.keyStoreType()) { + case JKS: + context.updateKeyStore(config.keyStorePath(), "JKS", secretStore.getSecret(config.keyDbKey())); + break; + case PEM: + context.updateKeyStore(createPemKeyStore(config.pemKeyStore())); + break; + } + } + + private static void validateJksConfig(ConnectorConfig.Ssl ssl) { + if (!ssl.pemKeyStore().keyPath().isEmpty() || ! ssl.pemKeyStore().certificatePath().isEmpty()) { + throw new IllegalArgumentException("pemKeyStore attributes can not be set when keyStoreType is JKS."); + } + if (ssl.keyDbKey().isEmpty()) { + throw new IllegalArgumentException("Missing password for JKS keystore"); + } + } + + private static void validatePemConfig(ConnectorConfig.Ssl ssl) { + if (! ssl.keyStorePath().isEmpty()) { + throw new IllegalArgumentException("keyStorePath can not be set when keyStoreType is PEM"); + } + if (!ssl.keyDbKey().isEmpty()) { + // TODO Make an error once there are separate passwords for truststore and keystore + log.warning("Encrypted PEM key stores are not supported. Password is only applied to truststore"); + } + if (ssl.pemKeyStore().certificatePath().isEmpty()) { + throw new IllegalArgumentException("Missing certificate path."); + } + if (ssl.pemKeyStore().keyPath().isEmpty()) { + throw new IllegalArgumentException("Missing key path."); + } + } + + private static KeyStore createPemKeyStore(ConnectorConfig.Ssl.PemKeyStore pemKeyStore) { + try { + Path certificatePath = Paths.get(pemKeyStore.certificatePath()); + Path keyPath = Paths.get(pemKeyStore.keyPath()); + return new PemSslKeyStore(certificatePath, keyPath).loadJavaKeyStore(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (Exception e) { + throw new RuntimeException("Failed setting up key store for " + pemKeyStore.keyPath() + ", " + pemKeyStore.certificatePath(), e); + } + } + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java new file mode 100644 index 00000000000..44a9c606576 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java @@ -0,0 +1,51 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.security.KeyStore; +import java.util.function.Consumer; + +/** + * @author bjorncs + */ +public class DefaultSslKeyStoreContext implements SslKeyStoreContext { + + private final SslContextFactory sslContextFactory; + + public DefaultSslKeyStoreContext(SslContextFactory sslContextFactory) { + this.sslContextFactory = sslContextFactory; + } + + @Override + public void updateKeyStore(KeyStore keyStore) { + updateKeyStore(keyStore, null); + } + + @Override + public void updateKeyStore(KeyStore keyStore, String password) { + updateKeyStore(sslContextFactory -> { + sslContextFactory.setKeyStore(keyStore); + if (password != null) { + sslContextFactory.setKeyStorePassword(password); + } + }); + } + + @Override + public void updateKeyStore(String keyStorePath, String keyStoreType, String keyStorePassword) { + updateKeyStore(sslContextFactory -> { + sslContextFactory.setKeyStorePath(keyStorePath); + sslContextFactory.setKeyStoreType(keyStoreType); + sslContextFactory.setKeyStorePassword(keyStorePassword); + }); + } + + private void updateKeyStore(Consumer<SslContextFactory> reloader) { + try { + sslContextFactory.reload(reloader); + } catch (Exception e) { + throw new RuntimeException("Could not update keystore: " + e.getMessage(), e); + } + } +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/JKSKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/JKSKeyStore.java deleted file mode 100644 index ce3c6f6ea4a..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/JKSKeyStore.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; - -/** - * @author tonytv - */ -public class JKSKeyStore extends SslKeyStore { - - private static final String keyStoreType = "JKS"; - private final Path keyStoreFile; - - public JKSKeyStore(Path keyStoreFile) { - this.keyStoreFile = keyStoreFile; - } - - @Override - public KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - try(InputStream stream = Files.newInputStream(keyStoreFile)) { - KeyStore keystore = KeyStore.getInstance(keyStoreType); - keystore.load(stream, getKeyStorePassword().map(String::toCharArray).orElse(null)); - return keystore; - } - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ReaderForPath.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ReaderForPath.java deleted file mode 100644 index b04d91d7403..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ReaderForPath.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl; - -import java.io.Reader; -import java.nio.file.Path; - -/** - * A reader along with the path used to construct it. - * - * @author tonytv - */ -public final class ReaderForPath { - - public final Reader reader; - public final Path path; - - public ReaderForPath(Reader reader, Path path) { - this.reader = reader; - this.path = path; - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactory.java deleted file mode 100644 index 0f39c132b67..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslContextFactory.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.io.IOException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author <a href="mailto:charlesk@yahoo-inc.com">Charles Kim</a> - */ -public class SslContextFactory { - - private static final Logger log = Logger.getLogger(SslContextFactory.class.getName()); - private static final String DEFAULT_ALGORITHM = "SunX509"; - private static final String DEFAULT_PROTOCOL = "TLS"; - private final SSLContext sslContext; - - private SslContextFactory(SSLContext sslContext) { - this.sslContext = sslContext; - } - - public SSLContext getServerSSLContext() { - return this.sslContext; - } - - public static SslContextFactory newInstanceFromTrustStore(SslKeyStore trustStore) { - return newInstance(DEFAULT_ALGORITHM, DEFAULT_PROTOCOL, null, trustStore); - } - - public static SslContextFactory newInstance(SslKeyStore trustStore, SslKeyStore keyStore) { - return newInstance(DEFAULT_ALGORITHM, DEFAULT_PROTOCOL, keyStore, trustStore); - } - - public static SslContextFactory newInstance(String sslAlgorithm, String sslProtocol, - SslKeyStore keyStore, SslKeyStore trustStore) { - log.fine("Configuring SSLContext..."); - log.fine("Using " + sslAlgorithm + " algorithm."); - try { - SSLContext sslContext = SSLContext.getInstance(sslProtocol); - sslContext.init( - keyStore == null ? null : getKeyManagers(keyStore, sslAlgorithm), - trustStore == null ? null : getTrustManagers(trustStore, sslAlgorithm), - null); - return new SslContextFactory(sslContext); - } catch (Exception e) { - log.log(Level.SEVERE, "Got exception creating SSLContext.", e); - throw new RuntimeException(e); - } - } - - /** - * Used for the key store, which contains the SSL cert and private key. - */ - public static javax.net.ssl.KeyManager[] getKeyManagers(SslKeyStore keyStore, - String sslAlgorithm) - throws NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, - KeyStoreException { - - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(sslAlgorithm); - keyManagerFactory.init( - keyStore.loadJavaKeyStore(), - keyStore.getKeyStorePassword().map(String::toCharArray).orElse(null)); - log.fine("KeyManagerFactory initialized with keystore"); - return keyManagerFactory.getKeyManagers(); - } - - /** - * Used for the trust store, which contains certificates from other parties that you expect to communicate with, - * or from Certificate Authorities that you trust to identify other parties. - */ - public static javax.net.ssl.TrustManager[] getTrustManagers(SslKeyStore trustStore, - String sslAlgorithm) - throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(sslAlgorithm); - trustManagerFactory.init(trustStore.loadJavaKeyStore()); - log.fine("TrustManagerFactory initialized with truststore."); - return trustManagerFactory.getTrustManagers(); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java deleted file mode 100644 index 1201bb08afc..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl; - -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.util.Optional; - -/** - * - * @author <a href="mailto:charlesk@yahoo-inc.com">Charles Kim</a> - */ -public abstract class SslKeyStore { - - private Optional<String> keyStorePassword = Optional.empty(); - - public Optional<String> getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = Optional.of(keyStorePassword); - } - - public abstract KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException; - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java new file mode 100644 index 00000000000..619f4a636ed --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java @@ -0,0 +1,14 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +/** + * An interface for an component that can configure an {@link SslKeyStoreContext}. The implementor can assume that + * the {@link SslKeyStoreContext} instance is thread-safe and be updated at any time + * during and after the call to{@link #configure(SslKeyStoreContext)}. + * Modifying the {@link SslKeyStoreContext} instance will trigger a hot reload of the keystore in JDisc. + * + * @author bjorncs + */ +public interface SslKeyStoreConfigurator { + void configure(SslKeyStoreContext context); +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java new file mode 100644 index 00000000000..2a25f6d78b5 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java @@ -0,0 +1,16 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl; + +import java.security.KeyStore; + +/** + * An interface to update the keystore in JDisc. Any update will trigger a hot reload and new connections will + * immediately see the new certificate chain. + * + * @author bjorncs + */ +public interface SslKeyStoreContext { + void updateKeyStore(KeyStore keyStore); + void updateKeyStore(KeyStore keyStore, String password); + void updateKeyStore(String keyStorePath, String keyStoreType, String keyStorePassword); +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java index c47d36991d4..5f817d4cfc2 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java @@ -1,4 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author bjorncs + */ @ExportPackage package com.yahoo.jdisc.http.ssl; + import com.yahoo.osgi.annotation.ExportPackage; diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java index 21272f202ea..b52e923662f 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStore.java @@ -2,7 +2,6 @@ package com.yahoo.jdisc.http.ssl.pem; import com.google.common.base.Preconditions; -import com.yahoo.jdisc.http.ssl.ReaderForPath; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; @@ -16,9 +15,13 @@ import javax.annotation.concurrent.GuardedBy; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.Key; +import java.security.KeyStore; import java.security.KeyStore.LoadStoreParameter; -import java.security.KeyStore.ProtectionParameter; import java.security.KeyStoreException; import java.security.KeyStoreSpi; import java.security.NoSuchAlgorithmException; @@ -58,10 +61,6 @@ public class PemKeyStore extends KeyStoreSpi { @GuardedBy("this") private final Map<String, Certificate> aliasToCertificate = new LinkedHashMap<>(); - - public PemKeyStore() {} - - /** * The user is responsible for closing any readers given in the parameter. */ @@ -287,30 +286,51 @@ public class PemKeyStore extends KeyStoreSpi { } } - public static class PemLoadStoreParameter implements LoadStoreParameter { - private PemLoadStoreParameter() {} + // A reader along with the path used to construct it. + private static class ReaderForPath { + final Reader reader; + final Path path; - @Override - public ProtectionParameter getProtectionParameter() { - return null; + private ReaderForPath(Reader reader, Path path) { + this.reader = reader; + this.path = path; + } + + static ReaderForPath of(Path path) { + try { + return new ReaderForPath(Files.newBufferedReader(path), path); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } } - public static final class KeyStoreLoadParameter extends PemLoadStoreParameter { - public final ReaderForPath certificateReader; - public final ReaderForPath keyReader; + static class TrustStoreLoadParameter implements KeyStore.LoadStoreParameter { + final ReaderForPath certificateReader; - public KeyStoreLoadParameter(ReaderForPath certificateReader, ReaderForPath keyReader) { - this.certificateReader = certificateReader; - this.keyReader = keyReader; + TrustStoreLoadParameter(Path certificateReader) { + this.certificateReader = ReaderForPath.of(certificateReader); + } + + @Override + public KeyStore.ProtectionParameter getProtectionParameter() { + return null; } } - public static final class TrustStoreLoadParameter extends PemLoadStoreParameter { - public final ReaderForPath certificateReader; + static class KeyStoreLoadParameter implements KeyStore.LoadStoreParameter { + final ReaderForPath certificateReader; + final ReaderForPath keyReader; + + KeyStoreLoadParameter(Path certificateReader, Path keyReader) { + this.certificateReader = ReaderForPath.of(certificateReader); + this.keyReader = ReaderForPath.of(keyReader); + } - public TrustStoreLoadParameter(ReaderForPath certificateReader) { - this.certificateReader = certificateReader; + @Override + public KeyStore.ProtectionParameter getProtectionParameter() { + return null; } } + } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java deleted file mode 100644 index c1fcf8c33bf..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemKeyStoreProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.ssl.pem; - -import java.security.Provider; - -/** - * @author Tony Vaagenes - */ -public class PemKeyStoreProvider extends Provider { - - public static final String name = "PEMKeyStoreProvider"; - public static final double version = 1; - public static final String description = "Provides PEM keystore support"; - - public PemKeyStoreProvider() { - super(name, version, description); - putService(new Service(this, "KeyStore", "PEM", PemKeyStore. class.getName(), PemKeyStore.aliases, PemKeyStore.attributes)); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java index bf91f0eb259..2ae1894a8d4 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java @@ -1,15 +1,16 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.ssl.pem; -import com.yahoo.jdisc.http.ssl.SslKeyStore; import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.KeyStoreLoadParameter; -import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.PemLoadStoreParameter; import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.TrustStoreLoadParameter; import java.io.IOException; +import java.nio.file.Path; import java.security.KeyStore; +import java.security.KeyStore.LoadStoreParameter; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.Provider; import java.security.Security; import java.security.cert.CertificateException; @@ -17,37 +18,46 @@ import java.security.cert.CertificateException; * Responsible for creating pem key stores. * * @author Tony Vaagenes + * @author bjorncs */ -public class PemSslKeyStore extends SslKeyStore { +public class PemSslKeyStore { static { Security.addProvider(new PemKeyStoreProvider()); } - private static final String keyStoreType = "PEM"; - private final PemLoadStoreParameter loadParameter; + private static final String KEY_STORE_TYPE = "PEM"; + + private final LoadStoreParameter loadParameter; private KeyStore keyStore; - public PemSslKeyStore(KeyStoreLoadParameter loadParameter) { - this.loadParameter = loadParameter; + public PemSslKeyStore(Path certificatePath, Path keyPath) { + this.loadParameter = new KeyStoreLoadParameter(certificatePath, keyPath); } - public PemSslKeyStore(TrustStoreLoadParameter loadParameter) { - this.loadParameter = loadParameter; + public PemSslKeyStore(Path certificatePath) { + this.loadParameter = new TrustStoreLoadParameter(certificatePath); } - @Override - public KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - if (getKeyStorePassword().isPresent()) { - throw new UnsupportedOperationException("PEM key store with password is currently not supported. Please file a feature request."); - } - - //cached since Reader(in loadParameter) can only be used one time. + public KeyStore loadJavaKeyStore() + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { if (keyStore == null) { - keyStore = KeyStore.getInstance(keyStoreType); + keyStore = KeyStore.getInstance(KEY_STORE_TYPE); keyStore.load(loadParameter); } return keyStore; } + private static class PemKeyStoreProvider extends Provider { + + static final String NAME = "PEMKeyStoreProvider"; + static final double VERSION = 1; + static final String DESCRIPTION = "Provides PEM keystore support"; + + PemKeyStoreProvider() { + super(NAME, VERSION, DESCRIPTION); + putService(new Service(this, "KeyStore", "PEM", PemKeyStore. class.getName(), PemKeyStore.aliases, PemKeyStore.attributes)); + } + } + } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ChunkReader.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ChunkReader.java deleted file mode 100644 index a550a013a3b..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ChunkReader.java +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.test; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> - */ -public class ChunkReader { - - private static final Pattern CONTENT_LENGTH = Pattern.compile(".+^content-length: (\\d+)$.*", - Pattern.CASE_INSENSITIVE | - Pattern.MULTILINE | - Pattern.DOTALL); - private static final Pattern CHUNKED_ENCODING = Pattern.compile(".+^transfer-encoding: chunked$.*", - Pattern.CASE_INSENSITIVE | - Pattern.MULTILINE | - Pattern.DOTALL); - private final InputStream in; - private StringBuilder reading = new StringBuilder(); - private boolean readingHeader = true; - - public ChunkReader(InputStream in) { - this.in = in; - } - - public boolean isEndOfContent() throws IOException { - if (in.available() != 0) { - StringBuilder sb = new StringBuilder(); - sb.append(in.available()).append(": "); - for(int c = in.read(); c != -1; c = in.read()) { - sb.append('\''); - sb.append(c); - sb.append("' "); - } - throw new IllegalStateException("This is not the end '" + sb.toString()); - } - return in.available() == 0; - } - - public String readChunk() throws IOException { - while (true) { - String ret = removeNextChunk(); - if (ret != null) { - return ret; - } - readFromStream(); - } - } - - private String readContent(int length) throws IOException { - while (reading.length() < length) { - readFromStream(); - } - return splitReadBuffer(length); - } - - private void readFromStream() throws IOException { - byte[] buf = new byte[4096]; - try { - while (!Thread.currentThread().isInterrupted()) { - int len = in.read(buf, 0, buf.length); - if (len < 0) { - throw new IOException("Socket is closed."); - } - if (len > 0) { - reading.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buf, 0, len))); - break; - } - Thread.sleep(10); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private String removeNextChunk() throws IOException { - if (readingHeader) { - int pos = reading.indexOf("\r\n\r\n"); - if (pos < 0) { - return null; - } - String ret = splitReadBuffer(pos + 4); - Matcher m = CONTENT_LENGTH.matcher(ret); - if (m.matches()) { - ret += readContent(Integer.valueOf(m.group(1))); - } - readingHeader = !CHUNKED_ENCODING.matcher(ret).matches(); - return ret; - } else if (reading.indexOf("0\r\n") == 0) { - int pos = reading.indexOf("\r\n\r\n", 1); - if (pos < 0) { - return null; - } - readingHeader = true; - return splitReadBuffer(pos + 4); - } else { - int pos = reading.indexOf("\r\n"); - if (pos < 0) { - return null; - } - pos = reading.indexOf("\r\n", pos + 2); - if (pos < 0) { - return null; - } - return splitReadBuffer(pos + 2); - } - } - - private String splitReadBuffer(int pos) { - String ret = reading.substring(0, pos); - if (pos < reading.length()) { - reading = new StringBuilder(reading.substring(pos)); - } else { - reading = new StringBuilder(); - } - return ret; - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/FilterTestDriver.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/FilterTestDriver.java deleted file mode 100644 index 1532bc65bdf..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/FilterTestDriver.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.test; - -import com.yahoo.jdisc.Request; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.application.BindingRepository; -import com.yahoo.jdisc.handler.AbstractRequestHandler; -import com.yahoo.jdisc.handler.ContentChannel; -import com.yahoo.jdisc.handler.ResponseDispatch; -import com.yahoo.jdisc.handler.ResponseHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; - -import java.io.IOException; -import java.util.concurrent.Exchanger; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static com.yahoo.jdisc.http.test.ServerTestDriver.newFilterModule; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> - * - * TODO: dead code? - */ -public class FilterTestDriver { - - private final ServerTestDriver driver; - private final MyRequestHandler requestHandler; - - private FilterTestDriver(ServerTestDriver driver, MyRequestHandler requestHandler) { - this.driver = driver; - this.requestHandler = requestHandler; - } - - public boolean close() throws IOException { - return driver.close(); - } - - public HttpRequest filterRequest(String request) throws IOException, TimeoutException, InterruptedException { - driver.client().writeRequest(request); - return (HttpRequest)requestHandler.exchanger.exchange(null, 60, TimeUnit.SECONDS); - } - - public static FilterTestDriver newInstance(final BindingRepository<RequestFilter> requestFilters, - final BindingRepository<ResponseFilter> responseFilters) - throws IOException { - MyRequestHandler handler = new MyRequestHandler(); - return new FilterTestDriver(ServerTestDriver.newInstance(handler, - newFilterModule(requestFilters, responseFilters)), - handler); - } - - private static class MyRequestHandler extends AbstractRequestHandler { - - final Exchanger<Request> exchanger = new Exchanger<>(); - - @Override - public ContentChannel handleRequest(Request request, ResponseHandler handler) { - ResponseDispatch.newInstance(Response.Status.OK).dispatch(handler); - try { - exchanger.exchange(request); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return null; - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteClient.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteClient.java deleted file mode 100644 index dd6033c9975..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteClient.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.test; - -import com.yahoo.jdisc.http.server.jetty.JettyHttpServer; -import com.yahoo.jdisc.http.ssl.SslContextFactory; -import com.yahoo.jdisc.http.ssl.SslKeyStore; - -import javax.net.ssl.SSLContext; -import java.io.IOException; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> - */ -public class RemoteClient extends ChunkReader { - - private final Socket socket; - - private RemoteClient(Socket socket) throws IOException { - super(socket.getInputStream()); - this.socket = socket; - } - - public void close() throws IOException { - socket.close(); - } - - public void writeRequest(String request) throws IOException { - socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8)); - } - - public static RemoteClient newInstance(JettyHttpServer server) throws IOException { - return newInstance(server.getListenPort()); - } - - public static RemoteClient newInstance(int listenPort) throws IOException { - return new RemoteClient(new Socket("localhost", listenPort)); - } - - public static RemoteClient newSslInstance(int listenPort, SslKeyStore sslKeyStore) throws IOException { - SSLContext ctx = SslContextFactory.newInstanceFromTrustStore(sslKeyStore).getServerSSLContext(); - if (ctx == null) { - throw new RuntimeException("Failed to create socket with SSLContext."); - } - return new RemoteClient(ctx.getSocketFactory().createSocket("localhost", listenPort)); - } - - public static RemoteClient newSslInstance(JettyHttpServer server, SslKeyStore keyStore) throws IOException { - return newSslInstance(server.getListenPort(), keyStore); - } - -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteServer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteServer.java deleted file mode 100644 index 62b4bb306ed..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/RemoteServer.java +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.test; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> - */ -public class RemoteServer implements Runnable { - - private final Thread thread = new Thread(this, "RemoteServer@" + System.identityHashCode(this)); - private final LinkedBlockingQueue<Socket> clients = new LinkedBlockingQueue<>(); - private final ServerSocket server; - - private RemoteServer(int listenPort) throws IOException { - this.server = new ServerSocket(listenPort); - } - - @Override - public void run() { - try { - while (!Thread.interrupted()) { - Socket client = server.accept(); - if (client != null) { - clients.add(client); - } - } - } catch (IOException e) { - if (!server.isClosed()) { - e.printStackTrace(); - } - } - } - - public URI newRequestUri(String uri) { - return newRequestUri(URI.create(uri)); - } - - public URI newRequestUri(URI uri) { - URI serverUri = connectionSpec(); - try { - return new URI(serverUri.getScheme(), serverUri.getUserInfo(), serverUri.getHost(), - serverUri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - - public URI connectionSpec() { - return URI.create("http://localhost:" + server.getLocalPort() + "/"); - } - - public Connection awaitConnection(int timeout, TimeUnit unit) throws InterruptedException, IOException { - Socket client = clients.poll(timeout, unit); - if (client == null) { - return null; - } - return new Connection(client); - } - - public boolean close(int timeout, TimeUnit unit) { - try { - server.close(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - try { - thread.join(unit.toMillis(timeout)); - } catch (InterruptedException e) { - return false; - } - return !thread.isAlive(); - } - - public static RemoteServer newInstance() throws IOException { - RemoteServer ret = new RemoteServer(0); - ret.thread.start(); - return ret; - } - - public static class Connection extends ChunkReader { - - private final Socket socket; - private final PrintWriter out; - - private Connection(Socket socket) throws IOException { - super(socket.getInputStream()); - this.socket = socket; - this.out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); - } - - public void writeChunk(String chunk) { - out.print(chunk); - } - - public void close() throws IOException { - out.close(); - socket.close(); - } - } -} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ServerTestDriver.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ServerTestDriver.java deleted file mode 100644 index 03e2257ce70..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/test/ServerTestDriver.java +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.jdisc.http.test; - -import com.google.inject.AbstractModule; -import com.google.inject.Module; -import com.google.inject.TypeLiteral; -import com.yahoo.jdisc.application.BindingRepository; -import com.yahoo.jdisc.application.ContainerActivator; -import com.yahoo.jdisc.application.ContainerBuilder; -import com.yahoo.jdisc.handler.RequestHandler; -import com.yahoo.jdisc.http.HttpRequest; -import com.yahoo.jdisc.http.filter.RequestFilter; -import com.yahoo.jdisc.http.filter.ResponseFilter; -import com.yahoo.jdisc.http.server.jetty.JettyHttpServer; -import com.yahoo.jdisc.http.ssl.SslKeyStore; -import com.yahoo.jdisc.test.TestDriver; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -/** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> - */ -public class ServerTestDriver { - - private final TestDriver driver; - private final JettyHttpServer server; - private final RemoteClient client; - - private ServerTestDriver(TestDriver driver, JettyHttpServer server, RemoteClient client) { - this.driver = driver; - this.server = server; - this.client = client; - } - - public boolean close() throws IOException { - client.close(); - server.close(); - server.release(); - return driver.close(); - } - - public TestDriver parent() { - return driver; - } - - public ContainerActivator containerActivator() { - return driver; - } - - public JettyHttpServer server() { - return server; - } - - public RemoteClient client() { - return client; - } - - public HttpRequest newRequest(HttpRequest.Method method, String uri, HttpRequest.Version version) { - return HttpRequest.newServerRequest(driver, newRequestUri(uri), method, version); - } - - public URI newRequestUri(String uri) { - return newRequestUri(URI.create(uri)); - } - - public URI newRequestUri(URI uri) { - try { - return new URI("http", null, "locahost", - server.getListenPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - - public static ServerTestDriver newInstance(RequestHandler requestHandler, Module... guiceModules) throws IOException { - return newInstance(requestHandler, Arrays.asList(guiceModules)); - } - - public static ServerTestDriver newInstance(RequestHandler requestHandler, Iterable<Module> guiceModules) - throws IOException { - List<Module> lst = new LinkedList<>(); - lst.add(newDefaultModule()); - for (Module module : guiceModules) { - lst.add(module); - } - TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(lst.toArray(new Module[lst.size()])); - ContainerBuilder builder = driver.newContainerBuilder(); - builder.serverBindings().bind("*://*/*", requestHandler); - JettyHttpServer server = builder.guiceModules().getInstance(JettyHttpServer.class); - return newInstance(null, driver, builder, server); - } - - private static ServerTestDriver newInstance(SslKeyStore clientTrustStore, TestDriver driver, ContainerBuilder builder, - JettyHttpServer server) throws IOException { - builder.serverProviders().install(server); - driver.activateContainer(builder); - try { - server.start(); - } catch (RuntimeException e) { - server.release(); - driver.close(); - throw e; - } - RemoteClient client; - if (clientTrustStore == null) { - client = RemoteClient.newInstance(server); - } else { - client = RemoteClient.newSslInstance(server, clientTrustStore); - } - return new ServerTestDriver(driver, server, client); - } - - public static Module newDefaultModule() { - return new AbstractModule() { - - @Override - protected void configure() { - bind(new TypeLiteral<BindingRepository<RequestFilter>>() { }) - .toInstance(new BindingRepository<>()); - bind(new TypeLiteral<BindingRepository<ResponseFilter>>() { }) - .toInstance(new BindingRepository<>()); - } - }; - } - - public static Module newFilterModule(final BindingRepository<RequestFilter> requestFilters, - final BindingRepository<ResponseFilter> responseFilters) { - return new AbstractModule() { - - @Override - protected void configure() { - if (requestFilters != null) { - bind(new TypeLiteral<BindingRepository<RequestFilter>>() { }).toInstance(requestFilters); - } - if (responseFilters != null) { - bind(new TypeLiteral<BindingRepository<ResponseFilter>>() { }).toInstance(responseFilters); - } - } - }; - } -} |