diff --git a/docs/configuration/index.md b/docs/configuration/index.md index a14b3beb56c..cfc2c284623 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -1823,6 +1823,7 @@ Druid uses Jetty to serve HTTP requests. Each query being processed consumes a s |`druid.server.http.maxQueryTimeout`|Maximum allowed value (in milliseconds) for `timeout` parameter. See [query-context](../querying/query-context.md) to know more about `timeout`. Query is rejected if the query context `timeout` is greater than this value. |Long.MAX_VALUE| |`druid.server.http.maxRequestHeaderSize`|Maximum size of a request header in bytes. Larger headers consume more memory and can make a server more vulnerable to denial of service attacks. |8 * 1024| |`druid.server.http.contentSecurityPolicy`|Content-Security-Policy header value to set on each non-POST response. Setting this property to an empty string, or omitting it, both result in the default `frame-ancestors: none` being set.|`frame-ancestors 'none'`| +|`druid.server.http.enableHSTS`|If set to true, druid services will add strict transport security header `Strict-Transport-Security: max-age=63072000; includeSubDomains` to all HTTP responses|`false`| ##### Client Configuration diff --git a/server/pom.xml b/server/pom.xml index 14e1e574578..cb8beef975f 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -131,6 +131,10 @@ org.eclipse.jetty jetty-proxy + + org.eclipse.jetty + jetty-rewrite + com.google.code.findbugs jsr305 diff --git a/server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java b/server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java index 460153c95d9..d6b62822115 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java +++ b/server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java @@ -67,7 +67,8 @@ public class ServerConfig @NotNull List allowedHttpMethods, boolean showDetailedJettyErrors, @NotNull ErrorResponseTransformStrategy errorResponseTransformStrategy, - @Nullable String contentSecurityPolicy + @Nullable String contentSecurityPolicy, + boolean enableHSTS ) { this.numThreads = numThreads; @@ -88,6 +89,7 @@ public class ServerConfig this.showDetailedJettyErrors = showDetailedJettyErrors; this.errorResponseTransformStrategy = errorResponseTransformStrategy; this.contentSecurityPolicy = contentSecurityPolicy; + this.enableHSTS = enableHSTS; } public ServerConfig() @@ -161,6 +163,9 @@ public class ServerConfig @JsonProperty("contentSecurityPolicy") private String contentSecurityPolicy; + @JsonProperty + private boolean enableHSTS = false; + @JsonProperty private boolean showDetailedJettyErrors = true; @@ -255,6 +260,11 @@ public class ServerConfig return contentSecurityPolicy; } + public boolean isEnableHSTS() + { + return enableHSTS; + } + @Override public boolean equals(Object o) { @@ -282,7 +292,8 @@ public class ServerConfig unannouncePropagationDelay.equals(that.unannouncePropagationDelay) && allowedHttpMethods.equals(that.allowedHttpMethods) && errorResponseTransformStrategy.equals(that.errorResponseTransformStrategy) && - Objects.equals(contentSecurityPolicy, that.getContentSecurityPolicy()); + Objects.equals(contentSecurityPolicy, that.getContentSecurityPolicy()) && + enableHSTS == that.enableHSTS; } @Override @@ -306,7 +317,8 @@ public class ServerConfig allowedHttpMethods, errorResponseTransformStrategy, showDetailedJettyErrors, - contentSecurityPolicy + contentSecurityPolicy, + enableHSTS ); } @@ -332,6 +344,7 @@ public class ServerConfig ", errorResponseTransformStrategy=" + errorResponseTransformStrategy + ", showDetailedJettyErrors=" + showDetailedJettyErrors + ", contentSecurityPolicy=" + contentSecurityPolicy + + ", enableHSTS=" + enableHSTS + '}'; } diff --git a/server/src/main/java/org/apache/druid/server/initialization/jetty/CliIndexerServerModule.java b/server/src/main/java/org/apache/druid/server/initialization/jetty/CliIndexerServerModule.java index ee60b8e59bd..491ee4fd98b 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/jetty/CliIndexerServerModule.java +++ b/server/src/main/java/org/apache/druid/server/initialization/jetty/CliIndexerServerModule.java @@ -163,7 +163,8 @@ public class CliIndexerServerModule implements Module oldConfig.getAllowedHttpMethods(), oldConfig.isShowDetailedJettyErrors(), oldConfig.getErrorResponseTransformStrategy(), - oldConfig.getContentSecurityPolicy() + oldConfig.getContentSecurityPolicy(), + oldConfig.isEnableHSTS() ); } } diff --git a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerInitUtils.java b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerInitUtils.java index fc4213b16ad..b82d7952922 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerInitUtils.java +++ b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerInitUtils.java @@ -23,8 +23,12 @@ import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; import org.apache.druid.java.util.common.ISE; +import org.apache.druid.server.initialization.ServerConfig; import org.apache.druid.server.security.AllowHttpMethodsResourceFilter; +import org.eclipse.jetty.rewrite.handler.HeaderPatternRule; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.servlet.FilterHolder; @@ -32,6 +36,7 @@ import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletContextHandler; import javax.ws.rs.HttpMethod; + import java.util.Arrays; import java.util.List; import java.util.Set; @@ -122,4 +127,25 @@ public class JettyServerInitUtils null ); } + + public static void maybeAddHSTSPatternRule(ServerConfig serverConfig, RewriteHandler rewriteHandler) + { + if (serverConfig.isEnableHSTS()) { + rewriteHandler.addRule(getHSTSHeaderPattern()); + } + } + + public static void maybeAddHSTSRewriteHandler(ServerConfig serverConfig, HandlerList handlerList) + { + if (serverConfig.isEnableHSTS()) { + RewriteHandler rewriteHandler = new RewriteHandler(); + rewriteHandler.addRule(getHSTSHeaderPattern()); + handlerList.addHandler(rewriteHandler); + } + } + + private static HeaderPatternRule getHSTSHeaderPattern() + { + return new HeaderPatternRule("*", "Strict-Transport-Security", "max-age=63072000; includeSubDomains"); + } } diff --git a/server/src/main/java/org/apache/druid/server/initialization/jetty/StandardResponseHeaderFilterHolder.java b/server/src/main/java/org/apache/druid/server/initialization/jetty/StandardResponseHeaderFilterHolder.java index 4f36beb05cc..2960135f208 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/jetty/StandardResponseHeaderFilterHolder.java +++ b/server/src/main/java/org/apache/druid/server/initialization/jetty/StandardResponseHeaderFilterHolder.java @@ -37,6 +37,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.HttpMethod; + import java.io.IOException; import java.util.Collections; import java.util.EnumSet; @@ -48,7 +49,9 @@ import java.util.Set; */ public class StandardResponseHeaderFilterHolder implements ServletFilterHolder { - private static final Set STANDARD_HEADERS = ImmutableSet.of("Cache-Control", "Content-Security-Policy"); + private static final Set STANDARD_HEADERS = ImmutableSet.of("Cache-Control", + "Content-Security-Policy", + "Strict-Transport-Security"); private static final String DEFAULT_CONTENT_SECURITY_POLICY = "frame-ancestors 'none'"; private final String contentSecurityPolicy; diff --git a/server/src/test/java/org/apache/druid/initialization/ServerConfigTest.java b/server/src/test/java/org/apache/druid/initialization/ServerConfigTest.java index 1f7e8165b27..f6161e6fa57 100644 --- a/server/src/test/java/org/apache/druid/initialization/ServerConfigTest.java +++ b/server/src/test/java/org/apache/druid/initialization/ServerConfigTest.java @@ -42,6 +42,7 @@ public class ServerConfigTest ServerConfig defaultConfig2 = OBJECT_MAPPER.readValue(defaultConfigJson, ServerConfig.class); Assert.assertEquals(defaultConfig, defaultConfig2); Assert.assertFalse(defaultConfig2.isEnableForwardedRequestCustomizer()); + Assert.assertFalse(defaultConfig2.isEnableHSTS()); ServerConfig modifiedConfig = new ServerConfig( 999, @@ -61,7 +62,8 @@ public class ServerConfigTest ImmutableList.of(HttpMethod.OPTIONS), true, new AllowedRegexErrorResponseTransformStrategy(ImmutableList.of(".*")), - "my-cool-policy" + "my-cool-policy", + true ); String modifiedConfigJson = OBJECT_MAPPER.writeValueAsString(modifiedConfig); ServerConfig modifiedConfig2 = OBJECT_MAPPER.readValue(modifiedConfigJson, ServerConfig.class); @@ -74,6 +76,7 @@ public class ServerConfigTest Assert.assertTrue(modifiedConfig2.getAllowedHttpMethods().contains(HttpMethod.OPTIONS)); Assert.assertEquals("my-cool-policy", modifiedConfig.getContentSecurityPolicy()); Assert.assertEquals("my-cool-policy", modifiedConfig2.getContentSecurityPolicy()); + Assert.assertTrue(modifiedConfig2.isEnableHSTS()); } @Test diff --git a/services/src/main/java/org/apache/druid/cli/CliOverlord.java b/services/src/main/java/org/apache/druid/cli/CliOverlord.java index 3a8b25443c0..3fb093856b4 100644 --- a/services/src/main/java/org/apache/druid/cli/CliOverlord.java +++ b/services/src/main/java/org/apache/druid/cli/CliOverlord.java @@ -122,6 +122,7 @@ import org.apache.druid.server.security.Authenticator; import org.apache.druid.server.security.AuthenticatorMapper; import org.apache.druid.tasklogs.TaskLogStreamer; import org.apache.druid.tasklogs.TaskLogs; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerList; @@ -449,10 +450,13 @@ public class CliOverlord extends ServerRunnable root.addFilter(GuiceFilter.class, "/druid-ext/*", null); + RewriteHandler rewriteHandler = WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(); + JettyServerInitUtils.maybeAddHSTSPatternRule(serverConfig, rewriteHandler); + HandlerList handlerList = new HandlerList(); handlerList.setHandlers( new Handler[]{ - WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(), + rewriteHandler, JettyServerInitUtils.getJettyRequestLogHandler(), JettyServerInitUtils.wrapWithDefaultGzipHandler( root, diff --git a/services/src/main/java/org/apache/druid/cli/CoordinatorJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/CoordinatorJettyServerInitializer.java index 1f59901f1d8..dad9856b708 100644 --- a/services/src/main/java/org/apache/druid/cli/CoordinatorJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/CoordinatorJettyServerInitializer.java @@ -35,6 +35,7 @@ import org.apache.druid.server.security.AuthConfig; import org.apache.druid.server.security.AuthenticationUtils; import org.apache.druid.server.security.Authenticator; import org.apache.druid.server.security.AuthenticatorMapper; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerList; @@ -131,10 +132,13 @@ class CoordinatorJettyServerInitializer implements JettyServerInitializer root.addServlet(new ServletHolder(injector.getInstance(OverlordProxyServlet.class)), "/druid/indexer/*"); } + RewriteHandler rewriteHandler = WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(); + JettyServerInitUtils.maybeAddHSTSPatternRule(serverConfig, rewriteHandler); + HandlerList handlerList = new HandlerList(); handlerList.setHandlers( new Handler[]{ - WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(), + rewriteHandler, JettyServerInitUtils.getJettyRequestLogHandler(), JettyServerInitUtils.wrapWithDefaultGzipHandler( root, diff --git a/services/src/main/java/org/apache/druid/cli/MiddleManagerJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/MiddleManagerJettyServerInitializer.java index 6e8f7151dfe..a4e0b0bb66e 100644 --- a/services/src/main/java/org/apache/druid/cli/MiddleManagerJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/MiddleManagerJettyServerInitializer.java @@ -108,6 +108,7 @@ class MiddleManagerJettyServerInitializer implements JettyServerInitializer new DefaultHandler() } ); + JettyServerInitUtils.maybeAddHSTSRewriteHandler(serverConfig, handlerList); server.setHandler(handlerList); } } diff --git a/services/src/main/java/org/apache/druid/cli/QueryJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/QueryJettyServerInitializer.java index b03e320dafa..fb0dff1561a 100644 --- a/services/src/main/java/org/apache/druid/cli/QueryJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/QueryJettyServerInitializer.java @@ -135,6 +135,8 @@ public class QueryJettyServerInitializer implements JettyServerInitializer handlerList.addHandler(handler); } + JettyServerInitUtils.maybeAddHSTSRewriteHandler(serverConfig, handlerList); + // Add Gzip handler at the very end handlerList.addHandler(JettyServerInitUtils.wrapWithDefaultGzipHandler( root, diff --git a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java index e4644225fb7..06531bbe7af 100644 --- a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java @@ -41,6 +41,7 @@ import org.apache.druid.server.security.Authenticator; import org.apache.druid.server.security.AuthenticatorMapper; import org.apache.druid.sql.avatica.DruidAvaticaJsonHandler; import org.apache.druid.sql.avatica.DruidAvaticaProtobufHandler; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerList; @@ -146,10 +147,13 @@ public class RouterJettyServerInitializer implements JettyServerInitializer root.addFilter(GuiceFilter.class, "/druid/router/*", null); root.addFilter(GuiceFilter.class, "/druid-ext/*", null); + RewriteHandler rewriteHandler = WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(); + JettyServerInitUtils.maybeAddHSTSPatternRule(serverConfig, rewriteHandler); + final HandlerList handlerList = new HandlerList(); handlerList.setHandlers( new Handler[]{ - WebConsoleJettyServerInitializer.createWebConsoleRewriteHandler(), + rewriteHandler, JettyServerInitUtils.getJettyRequestLogHandler(), JettyServerInitUtils.wrapWithDefaultGzipHandler( root, diff --git a/services/src/main/java/org/apache/druid/cli/WebConsoleJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/WebConsoleJettyServerInitializer.java index d29e729f73e..bccc47a15da 100644 --- a/services/src/main/java/org/apache/druid/cli/WebConsoleJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/WebConsoleJettyServerInitializer.java @@ -24,7 +24,6 @@ import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.server.security.AuthenticationUtils; import org.eclipse.jetty.rewrite.handler.RedirectPatternRule; import org.eclipse.jetty.rewrite.handler.RewriteHandler; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.resource.Resource; @@ -57,7 +56,7 @@ class WebConsoleJettyServerInitializer AuthenticationUtils.addNoopAuthorizationFilters(root, UNAUTHORIZED_PATHS_FOR_UI); } - static Handler createWebConsoleRewriteHandler() + static RewriteHandler createWebConsoleRewriteHandler() { // redirect all legacy web consoles to current unified web console RewriteHandler rewrite = new RewriteHandler();