Add an option to enable HSTS in druid services (#13489)

* Add an option to enable HSTS

* Fix code and add docs

* Deduplicate headers

* unused import

* Fix spelling
This commit is contained in:
Abhishek Agarwal 2023-01-10 22:31:51 +05:30 committed by GitHub
parent 2503095296
commit 17936e2920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 76 additions and 11 deletions

View File

@ -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

View File

@ -131,6 +131,10 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-proxy</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-rewrite</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>

View File

@ -67,7 +67,8 @@ public class ServerConfig
@NotNull List<String> 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 +
'}';
}

View File

@ -163,7 +163,8 @@ public class CliIndexerServerModule implements Module
oldConfig.getAllowedHttpMethods(),
oldConfig.isShowDetailedJettyErrors(),
oldConfig.getErrorResponseTransformStrategy(),
oldConfig.getContentSecurityPolicy()
oldConfig.getContentSecurityPolicy(),
oldConfig.isEnableHSTS()
);
}
}

View File

@ -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");
}
}

View File

@ -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<String> STANDARD_HEADERS = ImmutableSet.of("Cache-Control", "Content-Security-Policy");
private static final Set<String> 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;

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -108,6 +108,7 @@ class MiddleManagerJettyServerInitializer implements JettyServerInitializer
new DefaultHandler()
}
);
JettyServerInitUtils.maybeAddHSTSRewriteHandler(serverConfig, handlerList);
server.setHandler(handlerList);
}
}

View File

@ -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,

View File

@ -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,

View File

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