Skip to content

Commit

Permalink
Support single-host mode on the multi-user server (#14335)
Browse files Browse the repository at this point in the history
Support single-host mode on the multi-user server. Note that this depends
changes in the che-jwtproxy component.

* Add a distinction between service name and path base in the external
server exposer to be able to correctly expose a service through a proxy
* the async authentication in loader.js doesn't truncate the path
anymore so that it can correctly locate /jwt/auth even in single-host mode
* enhance the jwt proxy configuration with the ability use custom uri
prefixes for the redirects on auth failure - this can be used to pass in
the information about the external path the proxy is available on even if
hidden behind a path rewriting ingress
* Make sure pathname doesn't contain duplicated slashes when constructing
the auth request.
* Made the cookie path server-strategy sensitive
  • Loading branch information
metlos authored Sep 10, 2019
1 parent c00828c commit 154d668
Show file tree
Hide file tree
Showing 23 changed files with 347 additions and 88 deletions.
8 changes: 3 additions & 5 deletions assembly/assembly-root-war/src/main/webapp/_app/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,8 @@ class Loader {
* @param {string} token
*/
asyncAuthenticate(redirectUrl, token) {
const re = new RegExp(/(https?:\/\/[^\/]+?)(?:$|\/).*/),
// \ / \ /
// scheme host:port
url = redirectUrl.replace(re, "$1" + "/jwt/auth");
redirectUrl = new URL(redirectUrl);
const url = redirectUrl.origin + redirectUrl.pathname.replace("//", "/") + "jwt/auth";
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url);
Expand All @@ -293,7 +291,7 @@ class Loader {
return;
}
if (request.status !== 204) {
const errorMessage = 'Failed to authenticate: "' + this.getRequestErrorMessage(xhr) + '"';
const errorMessage = 'Failed to authenticate: "' + this.getRequestErrorMessage(request) + '"';
reject(new Error(errorMessage));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ che.server.secure_exposer=default
che.server.secure_exposer.jwtproxy.token.issuer=wsmaster
che.server.secure_exposer.jwtproxy.token.ttl=8800h
che.server.secure_exposer.jwtproxy.auth.loader.path=/_app/loader.html
che.server.secure_exposer.jwtproxy.image=eclipse/che-jwtproxy:latest
che.server.secure_exposer.jwtproxy.image=quay.io/eclipse/che-jwtproxy:dbd0578
che.server.secure_exposer.jwtproxy.memory_limit=128mb


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy.COMMON_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.PerWorkspacePVCStrategy.PER_WORKSPACE_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy.UNIQUE_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostIngressServiceExposureStrategy.DEFAULT_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostIngressServiceExposureStrategy.MULTI_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostIngressServiceExposureStrategy.SINGLE_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy.DEFAULT_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY;

import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
Expand Down Expand Up @@ -60,11 +60,11 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.LogsRootEnvVariableProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.IngressAnnotationsProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostIngressServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServiceExposureStrategyProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostIngressServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostIngressServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.DefaultSecureServersFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactoryProvider;
Expand Down Expand Up @@ -117,18 +117,18 @@ protected void configure() {
.addBinding()
.to(KubernetesClientTermination.class);

MapBinder<String, IngressServiceExposureStrategy> ingressStrategies =
MapBinder.newMapBinder(binder(), String.class, IngressServiceExposureStrategy.class);
MapBinder<String, ExternalServiceExposureStrategy> ingressStrategies =
MapBinder.newMapBinder(binder(), String.class, ExternalServiceExposureStrategy.class);
ingressStrategies
.addBinding(MULTI_HOST_STRATEGY)
.to(MultiHostIngressServiceExposureStrategy.class);
.to(MultiHostExternalServiceExposureStrategy.class);
ingressStrategies
.addBinding(SINGLE_HOST_STRATEGY)
.to(SingleHostIngressServiceExposureStrategy.class);
.to(SingleHostExternalServiceExposureStrategy.class);
ingressStrategies
.addBinding(DEFAULT_HOST_STRATEGY)
.to(DefaultHostIngressServiceExposureStrategy.class);
bind(IngressServiceExposureStrategy.class)
.to(DefaultHostExternalServiceExposureStrategy.class);
bind(ExternalServiceExposureStrategy.class)
.toProvider(IngressServiceExposureStrategyProvider.class);

bind(ServersConverter.class).to(new TypeLiteral<ServersConverter<KubernetesEnvironment>>() {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@
* @author Sergii Leshchenko
* @author Guy Daich
*/
public class DefaultHostIngressServiceExposureStrategy implements IngressServiceExposureStrategy {
public class DefaultHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy {

public static final String DEFAULT_HOST_STRATEGY = "default-host";

@Override
public String getIngressHost(String serviceName, ServicePort servicePort) {
public String getExternalHost(String serviceName, ServicePort servicePort) {
return null;
}

@Override
public String getIngressPath(String serviceName, ServicePort servicePort) {
public String getExternalPath(String serviceName, ServicePort servicePort) {
return "/" + serviceName + "/" + servicePort.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ public class ExternalServerExposer<T extends KubernetesEnvironment> {
*/
static final String PATH_TRANSFORM_PATH_CATCH = "%s";

private final IngressServiceExposureStrategy strategy;
private final ExternalServiceExposureStrategy strategy;
private final Map<String, String> ingressAnnotations;
private final String pathTransformFmt;

@Inject
public ExternalServerExposer(
IngressServiceExposureStrategy strategy,
ExternalServiceExposureStrategy strategy,
@Named("infra.kubernetes.ingress.annotations") Map<String, String> annotations,
@Nullable @Named("che.infra.kubernetes.ingress.path_transform") String pathTransformFmt) {
this.strategy = strategy;
Expand Down Expand Up @@ -71,7 +71,7 @@ private Ingress generateIngress(
Map<String, ServerConfig> ingressesServers) {

ExternalServerIngressBuilder ingressBuilder = new ExternalServerIngressBuilder();
String host = strategy.getIngressHost(serviceName, servicePort);
String host = strategy.getExternalHost(serviceName, servicePort);
if (host != null) {
ingressBuilder = ingressBuilder.withHost(host);
}
Expand All @@ -80,7 +80,7 @@ private Ingress generateIngress(
.withPath(
String.format(
pathTransformFmt,
ensureEndsWithSlash(strategy.getIngressPath(serviceName, servicePort))))
ensureEndsWithSlash(strategy.getExternalPath(serviceName, servicePort))))
.withName(getIngressName(serviceName, servicePort))
.withMachineName(machineName)
.withServiceName(serviceName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
* Implementations of this strategy are used by the {@link ExternalServerExposer} to compose an
* Ingress rule that exposes the services.
*/
public interface IngressServiceExposureStrategy {
public interface ExternalServiceExposureStrategy {

/** Returns a host that should be used to expose the service */
@Nullable
String getIngressHost(String serviceName, ServicePort servicePort);
String getExternalHost(String serviceName, ServicePort servicePort);

/** Returns the path on which the service should be exposed */
String getIngressPath(String serviceName, ServicePort servicePort);
String getExternalPath(String serviceName, ServicePort servicePort);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@

@Singleton
public class IngressServiceExposureStrategyProvider
implements Provider<IngressServiceExposureStrategy> {
implements Provider<ExternalServiceExposureStrategy> {

static final String STRATEGY_PROPERTY = "che.infra.kubernetes.server_strategy";
public static final String STRATEGY_PROPERTY = "che.infra.kubernetes.server_strategy";

private final IngressServiceExposureStrategy namingStrategy;
private final ExternalServiceExposureStrategy namingStrategy;

@Inject
public IngressServiceExposureStrategyProvider(
@Named(STRATEGY_PROPERTY) String strategy,
Map<String, IngressServiceExposureStrategy> strategies) {
Map<String, ExternalServiceExposureStrategy> strategies) {

namingStrategy = strategies.get(strategy);

Expand All @@ -42,7 +42,7 @@ public IngressServiceExposureStrategyProvider(
}

@Override
public IngressServiceExposureStrategy get() {
public ExternalServiceExposureStrategy get() {
return namingStrategy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@
* @author Sergii Leshchenko
* @author Guy Daich
*/
public class MultiHostIngressServiceExposureStrategy implements IngressServiceExposureStrategy {
public class MultiHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy {

public static final String MULTI_HOST_STRATEGY = "multi-host";
private static final String INGRESS_DOMAIN_PROPERTY = "che.infra.kubernetes.ingress.domain";

private final String domain;

@Inject
public MultiHostIngressServiceExposureStrategy(
public MultiHostExternalServiceExposureStrategy(
@Named(INGRESS_DOMAIN_PROPERTY) String domain, @Named(STRATEGY_PROPERTY) String strategy) {
if (Strings.isNullOrEmpty(domain) && MULTI_HOST_STRATEGY.equals(strategy)) {
throw new ConfigurationException(
Expand All @@ -65,12 +65,12 @@ public MultiHostIngressServiceExposureStrategy(
}

@Override
public String getIngressHost(String serviceName, ServicePort servicePort) {
public String getExternalHost(String serviceName, ServicePort servicePort) {
return serviceName + "-" + servicePort.getName() + "." + domain;
}

@Override
public String getIngressPath(String serviceName, ServicePort servicePort) {
public String getExternalPath(String serviceName, ServicePort servicePort) {
return "/";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@
* @author Sergii Leshchenko
* @author Guy Daich
*/
public class SingleHostIngressServiceExposureStrategy implements IngressServiceExposureStrategy {
public class SingleHostExternalServiceExposureStrategy implements ExternalServiceExposureStrategy {

public static final String SINGLE_HOST_STRATEGY = "single-host";
private final String cheHost;

@Inject
public SingleHostIngressServiceExposureStrategy(@Named("che.host") String cheHost) {
public SingleHostExternalServiceExposureStrategy(@Named("che.host") String cheHost) {
this.cheHost = cheHost;
}

@Override
public String getIngressHost(String serviceName, ServicePort servicePort) {
public String getExternalHost(String serviceName, ServicePort servicePort) {
return cheHost;
}

@Override
public String getIngressPath(String serviceName, ServicePort servicePort) {
public String getExternalPath(String serviceName, ServicePort servicePort) {
return "/" + serviceName + "/" + servicePort.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.workspace.infrastructure.kubernetes.server.secure.jwtproxy;

import static java.lang.String.format;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.DefaultHostExternalServiceExposureStrategy.DEFAULT_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostExternalServiceExposureStrategy.MULTI_HOST_STRATEGY;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostExternalServiceExposureStrategy.SINGLE_HOST_STRATEGY;

import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.function.BiFunction;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.IngressServiceExposureStrategyProvider;

/**
* The cookie path for the access token cookie is server-strategy dependent. This class represents
* the different strategies for getting the cookie path.
*
* <p>Note that instead of going with full-blown strategy pattern and different implementations of
* some interface and a provider for the currently active strategy (as is done for example with
* {@link
* org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServiceExposureStrategy}),
* this class merely internally uses different functions for different service exposure strategies.
* This is done because the full-blown stragegy pattern implementation felt like over-engineering
* when compared with the simplicity of the functions.
*/
@Singleton
public class CookiePathStrategy {

private final BiFunction<String, ServicePort, String> getCookiePath;

@Inject
public CookiePathStrategy(
@Named(IngressServiceExposureStrategyProvider.STRATEGY_PROPERTY) String serverStrategy) {
switch (serverStrategy) {
case MULTI_HOST_STRATEGY:
getCookiePath = (__, ___) -> "/";
break;
case SINGLE_HOST_STRATEGY:
case DEFAULT_HOST_STRATEGY:
getCookiePath = (serviceName, __) -> serviceName;
break;
default:
throw new IllegalArgumentException(
format("Unsupported server strategy: %s", serverStrategy));
}
}

public String get(String serviceName, ServicePort port) {
return getCookiePath.apply(serviceName, port);
}
}
Loading

0 comments on commit 154d668

Please sign in to comment.