diff options
Diffstat (limited to 'jdisc_http_service/src/main/java')
2 files changed, 90 insertions, 3 deletions
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 30a1b1d885c..2b9cb426dda 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 @@ -9,6 +9,7 @@ import com.yahoo.component.provider.ComponentRegistry; import com.yahoo.container.logging.AccessLog; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.application.OsgiFramework; +import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ServerConfig; import com.yahoo.jdisc.http.ServletPathsConfig; import com.yahoo.jdisc.http.server.FilterBindings; @@ -145,10 +146,13 @@ public class JettyHttpServer extends AbstractServerProvider { setupJmx(server, serverConfig); ((QueuedThreadPool)server.getThreadPool()).setMaxThreads(serverConfig.maxWorkerThreads()); + List<ConnectorConfig> connectorConfigs = new ArrayList<>(); for (ConnectorFactory connectorFactory : connectorFactories.allComponents()) { - ServerSocketChannel preBoundChannel = getChannelFromServiceLayer(connectorFactory.getConnectorConfig().listenPort(), osgiFramework.bundleContext()); + ConnectorConfig connectorConfig = connectorFactory.getConnectorConfig(); + connectorConfigs.add(connectorConfig); + ServerSocketChannel preBoundChannel = getChannelFromServiceLayer(connectorConfig.listenPort(), osgiFramework.bundleContext()); server.addConnector(connectorFactory.createConnector(metric, server, preBoundChannel)); - listenedPorts.add(connectorFactory.getConnectorConfig().listenPort()); + listenedPorts.add(connectorConfig.listenPort()); } janitor = newJanitor(threadFactory); @@ -168,6 +172,7 @@ public class JettyHttpServer extends AbstractServerProvider { getHandlerCollection( serverConfig, servletPathsConfig, + connectorConfigs, jdiscServlet, servletHolders, jDiscFilterInvokerFilter)); @@ -217,6 +222,7 @@ public class JettyHttpServer extends AbstractServerProvider { private HandlerCollection getHandlerCollection( ServerConfig serverConfig, ServletPathsConfig servletPathsConfig, + List<ConnectorConfig> connectorConfigs, ServletHolder jdiscServlet, ComponentRegistry<ServletHolder> servletHolders, FilterHolder jDiscFilterInvokerFilter) { @@ -231,8 +237,11 @@ public class JettyHttpServer extends AbstractServerProvider { servletContextHandler.addServlet(jdiscServlet, "/*"); + var authEnforcer = new TlsClientAuthenticationEnforcer(connectorConfigs); + authEnforcer.setHandler(servletContextHandler); + GzipHandler gzipHandler = newGzipHandler(serverConfig); - gzipHandler.setHandler(servletContextHandler); + gzipHandler.setHandler(authEnforcer); HttpResponseStatisticsCollector statisticsCollector = new HttpResponseStatisticsCollector(); statisticsCollector.setHandler(gzipHandler); diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java new file mode 100644 index 00000000000..546741b3322 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/TlsClientAuthenticationEnforcer.java @@ -0,0 +1,78 @@ +// Copyright 2019 Oath Inc. 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.Response; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.servlet.ServletRequest; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.HandlerWrapper; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A Jetty handler that enforces TLS client authentication with configurable white list. + * + * @author bjorncs + */ +class TlsClientAuthenticationEnforcer extends HandlerWrapper { + + private final Map<Integer, List<String>> portToWhitelistedPathsMapping; + + TlsClientAuthenticationEnforcer(List<ConnectorConfig> connectorConfigs) { + portToWhitelistedPathsMapping = createWhitelistMapping(connectorConfigs); + } + + @Override + public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException { + if (isHttpsRequest(request) + && !isRequestToWhitelistedBinding(servletRequest) + && !isClientAuthenticated(servletRequest)) { + servletResponse.sendError(Response.Status.UNAUTHORIZED, "Client did not present a x509 certificate."); + } else { + _handler.handle(target, request, servletRequest, servletResponse); + } + } + + private static Map<Integer, List<String>> createWhitelistMapping(List<ConnectorConfig> connectorConfigs) { + var mapping = new HashMap<Integer, List<String>>(); + for (ConnectorConfig connectorConfig : connectorConfigs) { + var enforcerConfig = connectorConfig.tlsClientAuthEnforcer(); + if (enforcerConfig.enable()) { + mapping.put(connectorConfig.listenPort(), enforcerConfig.pathWhitelist()); + } + } + return mapping; + } + + private boolean isHttpsRequest(Request request) { + return request.getDispatcherType() == DispatcherType.REQUEST && request.getScheme().equalsIgnoreCase("https"); + } + + private boolean isRequestToWhitelistedBinding(HttpServletRequest servletRequest) { + int localPort = servletRequest.getLocalPort(); + List<String> whiteListedPaths = getWhitelistedPathsForPort(localPort); + if (whiteListedPaths == null) { + return true; // enforcer not enabled + } + // Note: Same path definition as HttpRequestFactory.getUri() + return whiteListedPaths.contains(servletRequest.getRequestURI()); + } + + private List<String> getWhitelistedPathsForPort(int localPort) { + if (portToWhitelistedPathsMapping.containsKey(0) && portToWhitelistedPathsMapping.size() == 1) { + return portToWhitelistedPathsMapping.get(0); // for unit tests which uses 0 for listen port + } + return portToWhitelistedPathsMapping.get(localPort); + } + + private boolean isClientAuthenticated(HttpServletRequest servletRequest) { + return servletRequest.getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT) != null; + } +} |