[SecurityHeaders] Fixed issue with Security Headers (#12921)

This commit is contained in:
Mohit Yadav 2023-08-18 17:48:17 +05:30 committed by GitHub
parent 9eb3f516cf
commit 9712337b01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 274 additions and 31 deletions

View File

@ -336,6 +336,13 @@ web:
enabled: ${WEB_CONF_XSS_CSP_ENABLED:-false}
policy: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
reportOnlyPolicy: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
referrer-policy:
enabled: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
option: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
permission-policy:
enabled: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
option: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
changeEventConfig:
omUri: ${OM_URI:- "http://localhost:8585"} #openmetadata in om uri for eg http://localhost:8585

View File

@ -205,6 +205,12 @@ services:
WEB_CONF_XSS_CSP_ENABLED: ${WEB_CONF_XSS_CSP_ENABLED:-false}
WEB_CONF_XSS_CSP_POLICY: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
#Referrer-Policy
WEB_CONF_REFERRER_POLICY_ENABLED: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
WEB_CONF_REFERRER_POLICY_OPTION: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
#Permission-Policy
WEB_CONF_PERMISSION_POLICY_ENABLED: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
WEB_CONF_PERMISSION_POLICY_OPTION: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
depends_on:
elasticsearch:
condition: service_started

View File

@ -205,6 +205,12 @@ services:
WEB_CONF_XSS_CSP_ENABLED: ${WEB_CONF_XSS_CSP_ENABLED:-false}
WEB_CONF_XSS_CSP_POLICY: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
#Referrer-Policy
WEB_CONF_REFERRER_POLICY_ENABLED: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
WEB_CONF_REFERRER_POLICY_OPTION: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
#Permission-Policy
WEB_CONF_PERMISSION_POLICY_ENABLED: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
WEB_CONF_PERMISSION_POLICY_OPTION: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
depends_on:
elasticsearch:
condition: service_started

View File

@ -156,6 +156,12 @@ services:
WEB_CONF_XSS_CSP_ENABLED: ${WEB_CONF_XSS_CSP_ENABLED:-false}
WEB_CONF_XSS_CSP_POLICY: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
#Referrer-Policy
WEB_CONF_REFERRER_POLICY_ENABLED: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
WEB_CONF_REFERRER_POLICY_OPTION: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
#Permission-Policy
WEB_CONF_PERMISSION_POLICY_ENABLED: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
WEB_CONF_PERMISSION_POLICY_OPTION: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
networks:
- app_net

View File

@ -197,6 +197,12 @@ services:
WEB_CONF_XSS_CSP_ENABLED: ${WEB_CONF_XSS_CSP_ENABLED:-false}
WEB_CONF_XSS_CSP_POLICY: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
#Referrer-Policy
WEB_CONF_REFERRER_POLICY_ENABLED: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
WEB_CONF_REFERRER_POLICY_OPTION: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
#Permission-Policy
WEB_CONF_PERMISSION_POLICY_ENABLED: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
WEB_CONF_PERMISSION_POLICY_OPTION: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
depends_on:
elasticsearch:
condition: service_started

View File

@ -196,6 +196,12 @@ services:
WEB_CONF_XSS_CSP_ENABLED: ${WEB_CONF_XSS_CSP_ENABLED:-false}
WEB_CONF_XSS_CSP_POLICY: ${WEB_CONF_XSS_CSP_POLICY:-"default-src 'self'"}
WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY: ${WEB_CONF_XSS_CSP_REPORT_ONLY_POLICY:-""}
#Referrer-Policy
WEB_CONF_REFERRER_POLICY_ENABLED: ${WEB_CONF_REFERRER_POLICY_ENABLED:-false}
WEB_CONF_REFERRER_POLICY_OPTION: ${WEB_CONF_REFERRER_POLICY_OPTION:-"SAME_ORIGIN"}
#Permission-Policy
WEB_CONF_PERMISSION_POLICY_ENABLED: ${WEB_CONF_PERMISSION_POLICY_ENABLED:-false}
WEB_CONF_PERMISSION_POLICY_OPTION: ${WEB_CONF_PERMISSION_POLICY_OPTION:-""}
depends_on:
elasticsearch:
condition: service_started

View File

@ -29,8 +29,6 @@ import io.dropwizard.lifecycle.Managed;
import io.dropwizard.server.DefaultServerFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.web.WebBundle;
import io.dropwizard.web.conf.WebConfiguration;
import io.federecio.dropwizard.swagger.SwaggerBundle;
import io.federecio.dropwizard.swagger.SwaggerBundleConfiguration;
import io.socket.engineio.server.EngineIoServerOptions;
@ -71,6 +69,8 @@ import org.openmetadata.schema.api.configuration.extension.ExtensionConfiguratio
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
import org.openmetadata.schema.api.security.AuthorizerConfiguration;
import org.openmetadata.schema.services.connections.metadata.AuthProvider;
import org.openmetadata.service.config.OMWebBundle;
import org.openmetadata.service.config.OMWebConfiguration;
import org.openmetadata.service.events.EventFilter;
import org.openmetadata.service.events.EventPubSub;
import org.openmetadata.service.events.scheduled.PipelineServiceStatusJobHandler;
@ -213,7 +213,8 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
registerSamlHandlers(catalogConfig, environment);
// Handle Asset Using Servlet
OpenMetadataAssetServlet assetServlet = new OpenMetadataAssetServlet("/assets", "/", "index.html");
OpenMetadataAssetServlet assetServlet =
new OpenMetadataAssetServlet("/assets", "/", "index.html", catalogConfig.getWebConfiguration());
String pathPattern = "/" + '*';
environment.servlets().addServlet("static", assetServlet).addMapping(pathPattern);
@ -312,9 +313,9 @@ public class OpenMetadataApplication extends Application<OpenMetadataApplication
});
bootstrap.addBundle(MicrometerBundleSingleton.getInstance());
bootstrap.addBundle(
new WebBundle<>() {
new OMWebBundle<>() {
@Override
public WebConfiguration getWebConfiguration(final OpenMetadataApplicationConfig configuration) {
public OMWebConfiguration getWebConfiguration(final OpenMetadataApplicationConfig configuration) {
return configuration.getWebConfiguration();
}
});

View File

@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.health.conf.HealthConfiguration;
import io.dropwizard.web.conf.WebConfiguration;
import io.federecio.dropwizard.swagger.SwaggerBundleConfiguration;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@ -35,6 +34,7 @@ import org.openmetadata.schema.api.security.jwt.JWTTokenConfiguration;
import org.openmetadata.schema.email.SmtpSettings;
import org.openmetadata.schema.security.secrets.SecretsManagerConfiguration;
import org.openmetadata.schema.service.configuration.elasticsearch.ElasticSearchConfiguration;
import org.openmetadata.service.config.OMWebConfiguration;
import org.openmetadata.service.migration.MigrationConfiguration;
import org.openmetadata.service.monitoring.EventMonitorConfiguration;
@ -100,7 +100,7 @@ public class OpenMetadataApplicationConfig extends Configuration {
@Valid
@NotNull
@JsonProperty("web")
private WebConfiguration webConfiguration = new WebConfiguration();
private OMWebConfiguration webConfiguration = new OMWebConfiguration();
@JsonProperty("changeEventConfig")
private ChangeEventConfiguration changeEventConfiguration;

View File

@ -0,0 +1,82 @@
package org.openmetadata.service.config;
import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import org.eclipse.jetty.servlets.HeaderFilter;
public abstract class OMWebBundle<T extends Configuration> implements ConfiguredBundle<T> {
protected OMWebBundle() {}
@Override
public void initialize(Bootstrap<?> bootstrap) {}
@Override
public void run(T configuration, Environment environment) throws Exception {
OMWebConfiguration webConfig = this.getWebConfiguration(configuration);
String urlPattern = this.deriveUrlPattern(webConfig.getUriPath());
Map<String, String> headers = new HashMap<>();
// Hsts
if (webConfig.getHstsHeaderFactory() != null) {
headers.putAll(webConfig.getHstsHeaderFactory().build());
}
// Frame Options
headers.putAll(webConfig.getFrameOptionsHeaderFactory().build());
// Content Type Options
headers.putAll(webConfig.getContentTypeOptionsHeaderFactory().build());
// XSS-Protection
headers.putAll(webConfig.getXssProtectionHeaderFactory().build());
// CSP
if (webConfig.getCspHeaderFactory() != null) {
headers.putAll(webConfig.getCspHeaderFactory().build());
}
// Referrer Policy
if (webConfig.getReferrerPolicyHeaderFactory() != null) {
headers.putAll(webConfig.getReferrerPolicyHeaderFactory().build());
}
// Permission Policy
if (webConfig.getPermissionPolicyHeaderFactory() != null) {
headers.putAll(webConfig.getPermissionPolicyHeaderFactory().build());
}
// Other Headers
headers.putAll(webConfig.getHeaders());
this.configureHeaderFilter(environment, webConfig.getUriPath(), urlPattern, headers);
if (webConfig.getCorsFilterFactory() != null) {
webConfig.getCorsFilterFactory().build(environment, urlPattern);
}
}
protected void configureHeaderFilter(
Environment environment, String uriPath, String urlPattern, Map<String, String> headers) {
String headerConfig =
headers.entrySet().stream()
.map(entry -> "set " + entry.getKey() + ": " + entry.getValue())
.collect(Collectors.joining(","));
Map<String, String> filterConfig = Collections.singletonMap("headerConfig", headerConfig);
FilterRegistration.Dynamic filter =
environment.servlets().addFilter("header-filter-" + uriPath, HeaderFilter.class);
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, urlPattern);
filter.setInitParameters(filterConfig);
}
private String deriveUrlPattern(String uri) {
return uri.endsWith("/") ? uri + "*" : uri + "/" + "*";
}
public abstract OMWebConfiguration getWebConfiguration(T var1);
}

View File

@ -0,0 +1,31 @@
package org.openmetadata.service.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.web.conf.WebConfiguration;
public class OMWebConfiguration extends WebConfiguration {
@JsonProperty("referrer-policy")
private ReferrerPolicyHeaderFactory referrerPolicyHeaderFactory;
@JsonProperty("permission-policy")
private PermissionPolicyHeaderFactory permissionPolicyHeaderFactory;
public OMWebConfiguration() {}
public ReferrerPolicyHeaderFactory getReferrerPolicyHeaderFactory() {
return this.referrerPolicyHeaderFactory;
}
public void setReferrerPolicyHeaderFactory(ReferrerPolicyHeaderFactory referrerPolicyHeaderFactory) {
this.referrerPolicyHeaderFactory = referrerPolicyHeaderFactory;
}
public PermissionPolicyHeaderFactory getPermissionPolicyHeaderFactory() {
return this.permissionPolicyHeaderFactory;
}
public void setPermissionPolicyHeaderFactory(PermissionPolicyHeaderFactory permissionPolicyHeaderFactory) {
this.permissionPolicyHeaderFactory = permissionPolicyHeaderFactory;
}
}

View File

@ -0,0 +1,28 @@
package org.openmetadata.service.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.web.conf.HeaderFactory;
import java.util.Collections;
import java.util.Map;
public class PermissionPolicyHeaderFactory extends HeaderFactory {
public static final String PERMISSION_POLICY_HEADER = "Permissions-Policy";
@JsonProperty("option")
private String option;
public PermissionPolicyHeaderFactory() {}
public String getOption() {
return this.option;
}
public void setOption(String option) {
this.option = option;
}
@Override
protected Map<String, String> buildHeaders() {
return Collections.singletonMap(PERMISSION_POLICY_HEADER, this.option);
}
}

View File

@ -0,0 +1,49 @@
package org.openmetadata.service.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.web.conf.HeaderFactory;
import java.util.Collections;
import java.util.Map;
public class ReferrerPolicyHeaderFactory extends HeaderFactory {
public static final String REFERRER_POLICY_HEADER = "Referrer-Policy";
@JsonProperty private ReferrerPolicyOption option;
public ReferrerPolicyHeaderFactory() {
this.option = ReferrerPolicyOption.NO_REFERRER;
}
public ReferrerPolicyOption getOption() {
return this.option;
}
public void setOption(ReferrerPolicyOption option) {
this.option = option;
}
@Override
public Map<String, String> buildHeaders() {
return Collections.singletonMap(REFERRER_POLICY_HEADER, this.option.getValue());
}
public enum ReferrerPolicyOption {
NO_REFERRER("no-referrer"),
NO_REFERRER_WHEN_DOWNGRADE("no-referrer-when-downgrade"),
ORIGIN("origin"),
ORIGIN_WHEN_CROSS_ORIGIN("origin-when-cross-origin"),
SAME_ORIGIN("same-origin"),
STRICT_ORIGIN("strict-origin"),
STRICT_ORIGIN_WHEN_CROSS_ORIGIN("strict-origin-when-cross-origin"),
UNSAFE_URL("unsafe-url");
private final String value;
ReferrerPolicyOption(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
}

View File

@ -1,22 +1,21 @@
package org.openmetadata.service.exception;
import io.dropwizard.web.conf.WebConfiguration;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.openmetadata.service.config.OMWebConfiguration;
@Slf4j
public class OMErrorPageHandler extends ErrorPageErrorHandler {
private final WebConfiguration webConfiguration;
private final OMWebConfiguration webConfiguration;
public OMErrorPageHandler(WebConfiguration webConfiguration) {
public OMErrorPageHandler(OMWebConfiguration webConfiguration) {
this.webConfiguration = webConfiguration;
}
@ -29,25 +28,7 @@ public class OMErrorPageHandler extends ErrorPageErrorHandler {
@Override
public void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException {
String cacheControl = this.getCacheControl();
if (cacheControl != null) {
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), cacheControl);
}
// Attach Response Header from OM
// Hsts
webConfiguration.getHstsHeaderFactory().build().forEach(response::setHeader);
// Frame Options
webConfiguration.getFrameOptionsHeaderFactory().build().forEach(response::setHeader);
// Content Option
webConfiguration.getContentTypeOptionsHeaderFactory().build().forEach(response::setHeader);
// Xss Protections
webConfiguration.getXssProtectionHeaderFactory().build().forEach(response::setHeader);
setSecurityHeader(this.webConfiguration, response);
String errorPage = ((ErrorPageMapper) this).getErrorPage(request);
ContextHandler.Context context = baseRequest.getErrorContext();
Dispatcher errorDispatcher =
@ -76,4 +57,31 @@ public class OMErrorPageHandler extends ErrorPageErrorHandler {
baseRequest.setHandled(true);
}
}
public static void setSecurityHeader(OMWebConfiguration webConfiguration, HttpServletResponse response) {
// Attach Response Header from OM
// Hsts
webConfiguration.getHstsHeaderFactory().build().forEach(response::setHeader);
// Frame Options
webConfiguration.getFrameOptionsHeaderFactory().build().forEach(response::setHeader);
// Content Option
webConfiguration.getContentTypeOptionsHeaderFactory().build().forEach(response::setHeader);
// Xss Protections
webConfiguration.getXssProtectionHeaderFactory().build().forEach(response::setHeader);
// CSP
webConfiguration.getCspHeaderFactory().build().forEach(response::setHeader);
// Referrer Policy
webConfiguration.getReferrerPolicyHeaderFactory().build().forEach(response::setHeader);
// Policy Permission
webConfiguration.getPermissionPolicyHeaderFactory().build().forEach(response::setHeader);
// Additional Headers
webConfiguration.getHeaders().forEach(response::setHeader);
}
}

View File

@ -13,6 +13,8 @@
package org.openmetadata.service.socket;
import static org.openmetadata.service.exception.OMErrorPageHandler.setSecurityHeader;
import io.dropwizard.servlets.assets.AssetServlet;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -20,15 +22,20 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.Nullable;
import org.openmetadata.service.config.OMWebConfiguration;
public class OpenMetadataAssetServlet extends AssetServlet {
private final OMWebConfiguration webConfiguration;
public OpenMetadataAssetServlet(String resourcePath, String uriPath, @Nullable String indexFile) {
public OpenMetadataAssetServlet(
String resourcePath, String uriPath, @Nullable String indexFile, OMWebConfiguration webConf) {
super(resourcePath, uriPath, indexFile, "text/html", StandardCharsets.UTF_8);
this.webConfiguration = webConf;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
setSecurityHeader(webConfiguration, resp);
super.doGet(req, resp);
if (!resp.isCommitted() && (resp.getStatus() == 404)) {
resp.sendError(404);