From 3d366a1c19f92175eb743f44ec6522ca83a8db2c Mon Sep 17 00:00:00 2001 From: Mykhailo Kuznietsov Date: Tue, 4 Dec 2018 14:56:38 +0200 Subject: [PATCH] Add ability to change CORS configuration on Che Server through env vars (#12046) --- .../che/api/deploy/WsMasterModule.java | 8 ++ .../che/api/deploy/WsMasterServletModule.java | 36 ++++---- .../webapp/WEB-INF/classes/che/che.properties | 13 +++ .../che/api/core/cors/CheCorsFilter.java | 76 ++++------------- .../api/core/cors/CheCorsFilterConfig.java | 84 +++++++++++++++++++ .../helm/che/templates/configmap.yaml | 4 + .../helm/che/templates/deployment.yaml | 20 +++++ .../templates/che-server-template.yaml | 24 ++++++ .../che-machine-configuration.properties | 7 ++ ...AgentCorsAllowedOriginsEnvVarProvider.java | 39 +++++++++ 10 files changed, 227 insertions(+), 84 deletions(-) create mode 100644 core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilterConfig.java create mode 100644 wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceAgentCorsAllowedOriginsEnvVarProvider.java diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 9a5f2e71b3..6e8e766552 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -11,6 +11,7 @@ */ package org.eclipse.che.api.deploy; +import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.inject.matcher.Matchers.subclassesOf; import static org.eclipse.che.inject.Matchers.names; import static org.eclipse.che.multiuser.api.permission.server.SystemDomain.SYSTEM_DOMAIN_ACTIONS; @@ -61,6 +62,7 @@ import org.eclipse.che.api.workspace.server.spi.provision.env.JavaOptsEnvVariabl import org.eclipse.che.api.workspace.server.spi.provision.env.MachineTokenEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.MavenOptsEnvVariableProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.ProjectsRootEnvVariableProvider; +import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceAgentCorsAllowedOriginsEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceAgentJavaOptsEnvVariableProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceIdEnvVarProvider; import org.eclipse.che.api.workspace.server.spi.provision.env.WorkspaceMavenServerJavaOptsEnvVariableProvider; @@ -187,6 +189,12 @@ public class WsMasterModule extends AbstractModule { envVarProviders.addBinding().to(WorkspaceAgentJavaOptsEnvVariableProvider.class); envVarProviders.addBinding().to(WorkspaceMavenServerJavaOptsEnvVariableProvider.class); + // propagate CORS allowed origin evn variable to WS agent only if corresponding env variable + // is defined on master + if (!isNullOrEmpty(System.getenv("CHE_WSAGENT_CORS_ALLOWED__ORIGINS"))) { + envVarProviders.addBinding().to(WorkspaceAgentCorsAllowedOriginsEnvVarProvider.class); + } + bind(org.eclipse.che.api.workspace.server.bootstrap.InstallerService.class); bind(org.eclipse.che.api.workspace.server.event.WorkspaceJsonRpcMessenger.class) .asEagerSingleton(); diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterServletModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterServletModule.java index f7af2b87f6..04978b5793 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterServletModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterServletModule.java @@ -12,10 +12,7 @@ package org.eclipse.che.api.deploy; import com.google.inject.servlet.ServletModule; -import java.util.HashMap; -import java.util.Map; -import javax.inject.Singleton; -import org.apache.catalina.filters.CorsFilter; +import org.eclipse.che.api.core.cors.CheCorsFilter; import org.eclipse.che.commons.logback.filter.RequestIdLoggerFilter; import org.eclipse.che.inject.DynaModule; import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule; @@ -31,25 +28,10 @@ public class WsMasterServletModule extends ServletModule { if (Boolean.valueOf(System.getenv("CHE_TRACING_ENABLED"))) { install(new org.eclipse.che.core.tracing.web.TracingWebModule()); } + if (isCheCorsEnabled()) { + filter("/*").through(CheCorsFilter.class); + } - final Map corsFilterParams = new HashMap<>(); - corsFilterParams.put("cors.allowed.origins", "*"); - corsFilterParams.put( - "cors.allowed.methods", "GET," + "POST," + "HEAD," + "OPTIONS," + "PUT," + "DELETE"); - corsFilterParams.put( - "cors.allowed.headers", - "Content-Type," - + "X-Requested-With," - + "accept," - + "Origin," - + "Access-Control-Request-Method," - + "Access-Control-Request-Headers"); - corsFilterParams.put("cors.support.credentials", "true"); - // preflight cache is available for 10 minutes - corsFilterParams.put("cors.preflight.maxage", "10"); - bind(CorsFilter.class).in(Singleton.class); - - filter("/*").through(CorsFilter.class, corsFilterParams); filter("/*").through(RequestIdLoggerFilter.class); // Matching group SHOULD contain forward slash. @@ -67,6 +49,16 @@ public class WsMasterServletModule extends ServletModule { } } + private boolean isCheCorsEnabled() { + String cheCorsEnabledEnvVar = System.getenv("CHE_CORS_ENABLED"); + if (cheCorsEnabledEnvVar == null) { + // by default CORS should be enabled + return true; + } else { + return Boolean.valueOf(cheCorsEnabledEnvVar); + } + } + private void configureSingleUserMode() { filter("/*").through(org.eclipse.che.api.local.filters.EnvironmentInitializationFilter.class); } diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 3a2b21aee5..26bc032ddd 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -553,3 +553,16 @@ che.core.jsonrpc.processor_max_pool_size=100 ## Port the the http server endpoint that would be exposed with Prometheus metrics che.metrics.port=8087 + +# CORS settings +# CORS filter on WS Master is turned on by default. +# Use environment variable "CHE_CORS_ENABLED=false" to turn it off +# "cors.allowed.origins" indicates which request origins are allowed +che.cors.allowed_origins=* +# "cors.support.credentials" indicates if it allows processing of requests with credentials +# (in cookies, headers, TLS client certificates) +che.cors.allow_credentials=true +# This property is used to provide value for WS Agent CORS allowed origins env variable from WS Master, +# as it allows the automated initialization of preferred CORS configuration, if property value is +# set to WS Master domain. +che.wsagent.cors.allowed_origins= diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilter.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilter.java index d413576153..4cc8d5ae7a 100644 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilter.java +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilter.java @@ -11,46 +11,44 @@ */ package org.eclipse.che.api.core.cors; -import static org.apache.catalina.filters.CorsFilter.DEFAULT_ALLOWED_ORIGINS; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_HEADERS; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_METHODS; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_ORIGINS; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_EXPOSED_HEADERS; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_PREFLIGHT_MAXAGE; -import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS; - import com.google.inject.Singleton; import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; +import javax.inject.Inject; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.catalina.filters.CorsFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The special filter which provides filtering requests in according to settings which are set to - * {@link CorsFilter}. More information about filter and parameters you can find in documentation. - * The class contains business logic which allows to get allowed origin from any endpoint as it is - * used by export workspace. + * {@link CorsFilter}. Uses {@link CheCorsFilterConfig} for providing configuration. * * @author Dmitry Shnurenko + * @author Mykhailo Kuznietsov */ @Singleton public class CheCorsFilter implements Filter { + private static final Logger LOG = LoggerFactory.getLogger(CheCorsFilter.class); + private CorsFilter corsFilter; + @Inject private CheCorsFilterConfig cheCorsFilterConfig; + @Override public void init(FilterConfig filterConfig) throws ServletException { corsFilter = new CorsFilter(); - corsFilter.init(new CheCorsFilterConfig()); + corsFilter.init(cheCorsFilterConfig); + LOG.debug( + "CORS initialized with parameters: 'cors.support.credentials': '{}', 'cors.allowed.origins': '{}'", + cheCorsFilterConfig.getInitParameter("cors.support.credentials"), + cheCorsFilterConfig.getInitParameter("cors.allowed.origins")); } @Override @@ -64,50 +62,4 @@ public class CheCorsFilter implements Filter { public void destroy() { corsFilter.destroy(); } - - private class CheCorsFilterConfig implements FilterConfig { - - private final Map filterParams; - - public CheCorsFilterConfig() { - filterParams = new HashMap<>(); - filterParams.put(PARAM_CORS_ALLOWED_ORIGINS, DEFAULT_ALLOWED_ORIGINS); - filterParams.put( - PARAM_CORS_ALLOWED_METHODS, "GET," + "POST," + "HEAD," + "OPTIONS," + "PUT," + "DELETE"); - filterParams.put( - PARAM_CORS_ALLOWED_HEADERS, - "Content-Type," - + "X-Requested-With," - + "X-Oauth-Token," - + "accept," - + "Origin," - + "Authorization," - + "Access-Control-Request-Method," - + "Access-Control-Request-Headers"); - filterParams.put(PARAM_CORS_EXPOSED_HEADERS, "JAXRS-Body-Provided"); - filterParams.put(PARAM_CORS_SUPPORT_CREDENTIALS, "true"); - // preflight cache is available for 10 minutes - filterParams.put(PARAM_CORS_PREFLIGHT_MAXAGE, "10"); - } - - @Override - public String getFilterName() { - return getClass().getName(); - } - - @Override - public ServletContext getServletContext() { - throw new UnsupportedOperationException("The method does not supported in " + getClass()); - } - - @Override - public String getInitParameter(String key) { - return filterParams.get(key); - } - - @Override - public Enumeration getInitParameterNames() { - throw new UnsupportedOperationException("The method does not supported in " + getClass()); - } - } } diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilterConfig.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilterConfig.java new file mode 100644 index 0000000000..a51e11a76d --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/cors/CheCorsFilterConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.core.cors; + +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_HEADERS; +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_METHODS; +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_ALLOWED_ORIGINS; +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_EXPOSED_HEADERS; +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_PREFLIGHT_MAXAGE; +import static org.apache.catalina.filters.CorsFilter.PARAM_CORS_SUPPORT_CREDENTIALS; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +/** + * Basic configuration for {@link CheCorsFilter}. Allowed origings and credentials support are + * configurable through properties. + * + * @author Mykhailo Kuznietsov + */ +public class CheCorsFilterConfig implements FilterConfig { + + private final Map filterParams; + + @Inject + public CheCorsFilterConfig( + @Named("che.cors.allow_credentials") boolean allowCredentials, + @Named("che.cors.allowed_origins") String allowedOrigins) { + filterParams = new HashMap<>(); + filterParams.put(PARAM_CORS_ALLOWED_ORIGINS, allowedOrigins); + filterParams.put( + PARAM_CORS_ALLOWED_METHODS, "GET," + "POST," + "HEAD," + "OPTIONS," + "PUT," + "DELETE"); + filterParams.put( + PARAM_CORS_ALLOWED_HEADERS, + "Content-Type," + + "X-Requested-With," + + "X-Oauth-Token," + + "accept," + + "Origin," + + "Authorization," + + "Access-Control-Request-Method," + + "Access-Control-Request-Headers"); + filterParams.put(PARAM_CORS_EXPOSED_HEADERS, "JAXRS-Body-Provided"); + filterParams.put(PARAM_CORS_SUPPORT_CREDENTIALS, String.valueOf(allowCredentials)); + // preflight cache is available for 10 minutes + filterParams.put(PARAM_CORS_PREFLIGHT_MAXAGE, "10"); + } + + @Override + public String getFilterName() { + return CheCorsFilter.class.getName(); + } + + @Override + public ServletContext getServletContext() { + throw new UnsupportedOperationException( + "The method is not supported in " + CheCorsFilter.class); + } + + @Override + public String getInitParameter(String key) { + return filterParams.get(key); + } + + @Override + public Enumeration getInitParameterNames() { + throw new UnsupportedOperationException( + "The method is not supported in " + CheCorsFilter.class); + } +} diff --git a/deploy/kubernetes/helm/che/templates/configmap.yaml b/deploy/kubernetes/helm/che/templates/configmap.yaml index f17702f387..467c7f73b2 100644 --- a/deploy/kubernetes/helm/che/templates/configmap.yaml +++ b/deploy/kubernetes/helm/che/templates/configmap.yaml @@ -86,3 +86,7 @@ data: {{- if .Values.workspaceSidecarDefaultRamLimit }} CHE_WORKSPACE_SIDECAR_DEFAULT__MEMORY__LIMIT__MB: {{ .Values.workspaceSidecarDefaultRamLimit }} {{- end }} + CHE_CORS_ENABLED: "true" + CHE_CORS_ALLOW__CREDENTIALS: "true" + CHE_CORS_ALLOWED__ORIGINS: "*" + CHE_WSAGENT_CORS_ALLOWED__ORIGINS: "" diff --git a/deploy/kubernetes/helm/che/templates/deployment.yaml b/deploy/kubernetes/helm/che/templates/deployment.yaml index 4cd4ba2d33..2c6d8132d9 100644 --- a/deploy/kubernetes/helm/che/templates/deployment.yaml +++ b/deploy/kubernetes/helm/che/templates/deployment.yaml @@ -270,6 +270,26 @@ spec: configMapKeyRef: key: CHE_WORKSPACE_NO__PROXY name: che + - name: CHE_CORS_ALLOW__CREDENTIALS + valueFrom: + configMapKeyRef: + key: CHE_CORS_ALLOW__CREDENTIALS + name: che + - name: CHE_CORS_ALLOWED__ORIGINS + valueFrom: + configMapKeyRef: + key: CHE_CORS_ALLOWED__ORIGINS + name: che + - name: CHE_CORS_ENABLED + valueFrom: + configMapKeyRef: + key: CHE_CORS_ENABLED + name: che + - name: CHE_WSAGENT_CORS_ALLOWED__ORIGINS + valueFrom: + configMapKeyRef: + key: CHE_WSAGENT_CORS_ALLOWED__ORIGINS + name: che {{- if .Values.workspaceDefaultRamRequest }} - name: CHE_WORKSPACE_DEFAULT_MEMORY_REQUEST_MB valueFrom: diff --git a/deploy/openshift/templates/che-server-template.yaml b/deploy/openshift/templates/che-server-template.yaml index c5f46abfdc..713af8fed6 100644 --- a/deploy/openshift/templates/che-server-template.yaml +++ b/deploy/openshift/templates/che-server-template.yaml @@ -161,6 +161,14 @@ objects: value: "${CHE_TRACING_ENABLED}" - name: CHE_METRICS_ENABLED value: "false" + - name: CHE_CORS_ENABLED + value: "${CHE_CORS_ENABLED}" + - name: CHE_CORS_ALLOW__CREDENTIALS + value: "${CHE_CORS_ALLOW__CREDENTIALS}" + - name: CHE_CORS_ALLOWED__ORIGINS + value: "${CHE_CORS_ALLOWED__ORIGINS}" + - name: CHE_WSAGENT_CORS_ALLOWED__ORIGINS + value: "${CHE_WSAGENT_CORS_ALLOWED__ORIGINS}" image: ${IMAGE_CHE}:${CHE_VERSION} imagePullPolicy: "${PULL_POLICY}" livenessProbe: @@ -309,6 +317,22 @@ parameters: displayName: Eclipse Che tracing description: Enable or disable tracing in Eclipse Che value: 'false' +- name: CHE_CORS_ENABLED + displayName: CORS filter for WS Master + description: Enable or disable CORS filter for Eclipse Che WS Master + value: 'true' +- name: CHE_CORS_ALLOW__CREDENTIALS + displayName: CORS credentials support for WS Master + description: Allow requests with credentials for CORS filter + value: 'true' +- name: CHE_CORS_ALLOWED__ORIGINS + displayName: CORS allowed origins for WS Master + description: defines allowed origins in requests for CORS filter + value: '*' +- name: CHE_WSAGENT_CORS_ALLOWED__ORIGINS + displayName: CORS allowed origins for WS Agent + description: defines allowed origins in requests for CORS filter, that will be propagated as corresponding env variable on WS Agent. Only if its value is not null or empty + value: '' labels: app: che template: che diff --git a/wsagent/che-wsagent-core/src/main/webapp/WEB-INF/classes/codenvy/che-machine-configuration.properties b/wsagent/che-wsagent-core/src/main/webapp/WEB-INF/classes/codenvy/che-machine-configuration.properties index a6ee00d35e..fa3c0a698f 100644 --- a/wsagent/che-wsagent-core/src/main/webapp/WEB-INF/classes/codenvy/che-machine-configuration.properties +++ b/wsagent/che-wsagent-core/src/main/webapp/WEB-INF/classes/codenvy/che-machine-configuration.properties @@ -62,3 +62,10 @@ workspace.activity.schedule_period_s=60 # Maximum size of the json processing pool # in case if pool size would be exceeded message execution will be rejected che.core.jsonrpc.processor_max_pool_size=100 + +# CORS settings +# "cors.allowed.origins" indicates which request origins are allowed +che.cors.allowed_origins=* +# "cors.support.credentials" indicates if it allows processing of requests with credentials +# (in cookies, headers, TLS client certificates) +che.cors.allow_credentials=true diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceAgentCorsAllowedOriginsEnvVarProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceAgentCorsAllowedOriginsEnvVarProvider.java new file mode 100644 index 0000000000..07b699f241 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/provision/env/WorkspaceAgentCorsAllowedOriginsEnvVarProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.workspace.server.spi.provision.env; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.commons.lang.Pair; + +/** + * Add environment variable that defines allowed origins for {@link CheCorsFilterConfig} of WS Agent + * + * @author Mykhailo Kuznietsov + */ +public class WorkspaceAgentCorsAllowedOriginsEnvVarProvider implements EnvVarProvider { + + private String wsAgentCorsAllowedOrigins; + + @Inject + public WorkspaceAgentCorsAllowedOriginsEnvVarProvider( + @Named("che.wsagent.cors.allowed_origins") String cheWsMasterAllowedOrigins) { + this.wsAgentCorsAllowedOrigins = cheWsMasterAllowedOrigins; + } + + @Override + public Pair get(RuntimeIdentity runtimeIdentity) throws InfrastructureException { + return Pair.of("CHE_CORS_ALLOWED__ORIGINS", wsAgentCorsAllowedOrigins); + } +}