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();