Merge pull request #10252 from sleshchenko/jwtproxy-refactor

Added an ability to authenticate requests to servers with jwtproxy
6.19.x
Sergii Leshchenko 2018-07-12 14:52:32 +03:00 committed by GitHub
commit cb562b9795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1473 additions and 86 deletions

View File

@ -9,12 +9,18 @@
"exec-agent/http": {
"port": "4412/tcp",
"protocol": "http",
"path" : "/process"
"path" : "/process",
"attributes": {
"secure": "true"
}
},
"exec-agent/ws": {
"port": "4412/tcp",
"protocol": "ws",
"path": "/connect"
"path": "/connect",
"attributes": {
"secure": "true"
}
}
}
}

View File

@ -9,12 +9,18 @@
"exec-agent/http": {
"port": "4412/tcp",
"protocol": "http",
"path" : "/process"
"path" : "/process",
"attributes": {
"secure": "true"
}
},
"exec-agent/ws": {
"port": "4412/tcp",
"protocol": "ws",
"path": "/connect"
"path": "/connect",
"attributes": {
"secure": "true"
}
}
}
}

View File

@ -9,7 +9,10 @@
"terminal": {
"port": "4411/tcp",
"protocol": "ws",
"path" : "/pty"
"path" : "/pty",
"attributes": {
"secure": "true"
}
}
}
}

View File

@ -9,7 +9,10 @@
"terminal": {
"port": "4411/tcp",
"protocol": "ws",
"path" : "/pty"
"path" : "/pty",
"attributes": {
"secure": "true"
}
}
}
}

View File

@ -489,3 +489,11 @@ che.singleport.wildcard_domain.ipless=false
# Workspace.Next feature API endpoint. Should be a valid HTTP URL that includes protocol, port etc.
# In case Workspace.Next is not needed value 'NULL' should be used
che.workspace.feature.api=NULL
# Configures in which way secure servers will be protected with authentication.
# Suitable values:
# - 'default': no additionally authentication system will be enabled.
# So, servers should authenticate requests themselves.
# - 'jwtproxy': jwtproxy will authenticate requests.
# So, servers will receive only authenticated ones.
che.server.secure_exposer=default

View File

@ -28,6 +28,14 @@ public interface ServerConfig {
*/
String INTERNAL_SERVER_ATTRIBUTE = "internal";
/**
* {@link ServerConfig} and {@link Server} attribute name which can identify server as secure or
* non-secure. Requests to secure servers will be authenticated and must contain machine token.
* Attribute value {@code true} makes a server secure, any other value or lack of the attribute
* makes the server non-secure.
*/
String SECURE_SERVER_ATTRIBUTE = "secure";
/**
* Port used by server.
*

View File

@ -122,6 +122,10 @@
<groupId>org.eclipse.che.infrastructure.docker</groupId>
<artifactId>docker-environment</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
@ -215,6 +219,15 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>src/test/resources/jwtproxy-confg.yaml</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -51,6 +51,10 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.Exter
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategyProvider;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.MultiHostIngressExternalServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.SingleHostIngressExternalServerExposer;
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;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxySecureServerExposerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.wsnext.KubernetesWorkspaceNextApplier;
/** @author Sergii Leshchenko */
@ -70,6 +74,7 @@ public class KubernetesInfraModule extends AbstractModule {
install(new FactoryModuleBuilder().build(KubernetesRuntimeFactory.class));
install(new FactoryModuleBuilder().build(KubernetesBootstrapperFactory.class));
install(new FactoryModuleBuilder().build(StartSynchronizerFactory.class));
bind(WorkspacePVCCleaner.class).asEagerSingleton();
bind(RemoveNamespaceOnWorkspaceRemove.class).asEagerSingleton();
@ -119,5 +124,28 @@ public class KubernetesInfraModule extends AbstractModule {
MapBinder<String, WorkspaceNextApplier> wsNext =
MapBinder.newMapBinder(binder(), String.class, WorkspaceNextApplier.class);
wsNext.addBinding(KubernetesEnvironment.TYPE).to(KubernetesWorkspaceNextApplier.class);
bind(new TypeLiteral<SecureServerExposerFactory<KubernetesEnvironment>>() {})
.toProvider(
new TypeLiteral<SecureServerExposerFactoryProvider<KubernetesEnvironment>>() {});
MapBinder<String, SecureServerExposerFactory<KubernetesEnvironment>>
secureServerExposerFactories =
MapBinder.newMapBinder(
binder(),
new TypeLiteral<String>() {},
new TypeLiteral<SecureServerExposerFactory<KubernetesEnvironment>>() {});
secureServerExposerFactories
.addBinding("default")
.to(new TypeLiteral<DefaultSecureServersFactory<KubernetesEnvironment>>() {});
install(
new FactoryModuleBuilder()
.build(
new TypeLiteral<JwtProxySecureServerExposerFactory<KubernetesEnvironment>>() {}));
secureServerExposerFactories
.addBinding("jwtproxy")
.to(new TypeLiteral<JwtProxySecureServerExposerFactory<KubernetesEnvironment>>() {});
}
}

View File

@ -17,6 +17,7 @@ import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.POD_
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.assistedinject.Assisted;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Pod;
@ -470,6 +471,10 @@ public class KubernetesInternalRuntime<
namespace.secrets().create(secret);
}
for (ConfigMap configMap : k8sEnv.getConfigMaps().values()) {
namespace.configMaps().create(configMap);
}
List<Service> createdServices = new ArrayList<>();
for (Service service : k8sEnv.getServices().values()) {
createdServices.add(namespace.services().create(service));

View File

@ -10,6 +10,7 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.environment;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
@ -38,6 +39,7 @@ public class KubernetesEnvironment extends InternalEnvironment {
private final Map<String, Ingress> ingresses;
private final Map<String, PersistentVolumeClaim> persistentVolumeClaims;
private final Map<String, Secret> secrets;
private final Map<String, ConfigMap> configMaps;
public KubernetesEnvironment(KubernetesEnvironment k8sEnv) {
this(
@ -48,7 +50,8 @@ public class KubernetesEnvironment extends InternalEnvironment {
k8sEnv.getServices(),
k8sEnv.getIngresses(),
k8sEnv.getPersistentVolumeClaims(),
k8sEnv.getSecrets());
k8sEnv.getSecrets(),
k8sEnv.getConfigMaps());
}
public static Builder builder() {
@ -63,13 +66,15 @@ public class KubernetesEnvironment extends InternalEnvironment {
Map<String, Service> services,
Map<String, Ingress> ingresses,
Map<String, PersistentVolumeClaim> persistentVolumeClaims,
Map<String, Secret> secrets) {
Map<String, Secret> secrets,
Map<String, ConfigMap> configMaps) {
super(internalRecipe, machines, warnings);
this.pods = pods;
this.services = services;
this.ingresses = ingresses;
this.persistentVolumeClaims = persistentVolumeClaims;
this.secrets = secrets;
this.configMaps = configMaps;
}
/** Returns pods that should be created when environment starts. */
@ -97,6 +102,11 @@ public class KubernetesEnvironment extends InternalEnvironment {
return secrets;
}
/** Returns config maps that should be created when environment starts. */
public Map<String, ConfigMap> getConfigMaps() {
return configMaps;
}
public static class Builder {
protected InternalRecipe internalRecipe;
protected final Map<String, InternalMachineConfig> machines = new HashMap<>();
@ -106,6 +116,7 @@ public class KubernetesEnvironment extends InternalEnvironment {
protected final Map<String, Ingress> ingresses = new HashMap<>();
protected final Map<String, PersistentVolumeClaim> pvcs = new HashMap<>();
protected final Map<String, Secret> secrets = new HashMap<>();
protected final Map<String, ConfigMap> configMaps = new HashMap<>();
protected Builder() {}
@ -149,9 +160,14 @@ public class KubernetesEnvironment extends InternalEnvironment {
return this;
}
public Builder setConfigMaps(Map<String, ConfigMap> configMaps) {
this.configMaps.putAll(configMaps);
return this;
}
public KubernetesEnvironment build() {
return new KubernetesEnvironment(
internalRecipe, machines, warnings, pods, services, ingresses, pvcs, secrets);
internalRecipe, machines, warnings, pods, services, ingresses, pvcs, secrets, configMaps);
}
}
}

View File

@ -15,6 +15,7 @@ import static java.lang.String.format;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
@ -67,6 +68,10 @@ public class KubernetesEnvironmentFactory
static final String SECRET_IGNORED_WARNING_MESSAGE =
"Secrets specified in Kubernetes recipe are ignored.";
static final int CONFIG_MAP_IGNORED_WARNING_CODE = 4103;
static final String CONFIG_MAP_IGNORED_WARNING_MESSAGE =
"Config maps specified in Kubernetes recipe are ignored.";
private final KubernetesClientFactory clientFactory;
private final KubernetesEnvironmentValidator envValidator;
private final String defaultMachineMemorySizeAttribute;
@ -121,6 +126,7 @@ public class KubernetesEnvironmentFactory
boolean isAnyIngressPresent = false;
boolean isAnyPVCPresent = false;
boolean isAnySecretPresent = false;
boolean isAnyConfigMapPresent = false;
for (HasMetadata object : list.getItems()) {
if (object instanceof Pod) {
Pod pod = (Pod) object;
@ -134,6 +140,8 @@ public class KubernetesEnvironmentFactory
isAnyPVCPresent = true;
} else if (object instanceof Secret) {
isAnySecretPresent = true;
} else if (object instanceof ConfigMap) {
isAnyConfigMapPresent = true;
} else {
throw new ValidationException(
format("Found unknown object type '%s'", object.getMetadata()));
@ -153,6 +161,11 @@ public class KubernetesEnvironmentFactory
warnings.add(new WarningImpl(SECRET_IGNORED_WARNING_CODE, SECRET_IGNORED_WARNING_MESSAGE));
}
if (isAnyConfigMapPresent) {
warnings.add(
new WarningImpl(CONFIG_MAP_IGNORED_WARNING_CODE, CONFIG_MAP_IGNORED_WARNING_MESSAGE));
}
addRamLimitAttribute(machines, pods.values());
KubernetesEnvironment k8sEnv =
@ -165,6 +178,7 @@ public class KubernetesEnvironmentFactory
.setIngresses(new HashMap<>())
.setPersistentVolumeClaims(new HashMap<>())
.setSecrets(new HashMap<>())
.setConfigMaps(new HashMap<>())
.build();
envValidator.validate(k8sEnv);

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_WORKSPACE_ID_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.putLabel;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.client.KubernetesClientException;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
/**
* Defines an internal API for managing {@link ConfigMap} instances in {@link
* KubernetesConfigsMaps#namespace predefined namespace}.
*
* @author Sergii Leshchenko
*/
public class KubernetesConfigsMaps {
private final String namespace;
private final String workspaceId;
private final KubernetesClientFactory clientFactory;
KubernetesConfigsMaps(
String namespace, String workspaceId, KubernetesClientFactory clientFactory) {
this.namespace = namespace;
this.workspaceId = workspaceId;
this.clientFactory = clientFactory;
}
/**
* Creates specified config map.
*
* @param configMap config map to create
* @throws InfrastructureException when any exception occurs
*/
public void create(ConfigMap configMap) throws InfrastructureException {
putLabel(configMap, CHE_WORKSPACE_ID_LABEL, workspaceId);
try {
clientFactory.create(workspaceId).configMaps().inNamespace(namespace).create(configMap);
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
}
/**
* Deletes all existing secrets.
*
* @throws InfrastructureException when any exception occurs
*/
public void delete() throws InfrastructureException {
try {
clientFactory
.create(workspaceId)
.configMaps()
.inNamespace(namespace)
.withLabel(CHE_WORKSPACE_ID_LABEL, workspaceId)
.delete();
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
}
}

View File

@ -11,6 +11,7 @@
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.DoneableServiceAccount;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
@ -64,6 +65,7 @@ public class KubernetesNamespace {
private final KubernetesIngresses ingresses;
private final KubernetesClientFactory clientFactory;
private final KubernetesSecrets secrets;
private final KubernetesConfigsMaps configMaps;
@VisibleForTesting
protected KubernetesNamespace(
@ -74,7 +76,8 @@ public class KubernetesNamespace {
KubernetesServices services,
KubernetesPersistentVolumeClaims pvcs,
KubernetesIngresses kubernetesIngresses,
KubernetesSecrets secrets) {
KubernetesSecrets secrets,
KubernetesConfigsMaps configMaps) {
this.clientFactory = clientFactory;
this.workspaceId = workspaceId;
this.name = name;
@ -83,6 +86,7 @@ public class KubernetesNamespace {
this.pvcs = pvcs;
this.ingresses = kubernetesIngresses;
this.secrets = secrets;
this.configMaps = configMaps;
}
public KubernetesNamespace(
@ -95,6 +99,7 @@ public class KubernetesNamespace {
this.pvcs = new KubernetesPersistentVolumeClaims(name, workspaceId, clientFactory);
this.ingresses = new KubernetesIngresses(name, workspaceId, clientFactory);
this.secrets = new KubernetesSecrets(name, workspaceId, clientFactory);
this.configMaps = new KubernetesConfigsMaps(name, workspaceId, clientFactory);
}
/**
@ -146,9 +151,19 @@ public class KubernetesNamespace {
return secrets;
}
/** Returns object for managing {@link ConfigMap} instances inside namespace. */
public KubernetesConfigsMaps configMaps() {
return configMaps;
}
/** Removes all object except persistent volume claims inside namespace. */
public void cleanUp() throws InfrastructureException {
doRemove(ingresses::delete, services::delete, deployments::delete, secrets::delete);
doRemove(
ingresses::delete,
services::delete,
deployments::delete,
secrets::delete,
configMaps::delete);
}
/**

View File

@ -20,8 +20,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFacto
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
/**
* Defines an internal API for managing {@link Secret} instances in {@link KubernetesPods#namespace
* predefined namespace}.
* Defines an internal API for managing {@link Secret} instances in {@link
* KubernetesSecrets#namespace predefined namespace}.
*
* @author Sergii Leshchenko
*/

View File

@ -25,6 +25,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.environment.Kubernete
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory;
/**
* Converts {@link ServerConfig} to Kubernetes related objects to add a server into Kubernetes
@ -39,15 +41,20 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.Exter
public class ServersConverter<T extends KubernetesEnvironment>
implements ConfigurationProvisioner<T> {
private final SecureServerExposerFactory<T> secureServerExposerFactory;
private final ExternalServerExposerStrategy<T> externalServerExposerStrategy;
@Inject
public ServersConverter(ExternalServerExposerStrategy<T> externalServerExposerStrategy) {
public ServersConverter(
SecureServerExposerFactory<T> secureServerExposerFactory,
ExternalServerExposerStrategy<T> externalServerExposerStrategy) {
this.secureServerExposerFactory = secureServerExposerFactory;
this.externalServerExposerStrategy = externalServerExposerStrategy;
}
@Override
public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException {
SecureServerExposer<T> secureServerExposer = secureServerExposerFactory.create(identity);
for (Pod podConfig : k8sEnv.getPods().values()) {
final PodSpec podSpec = podConfig.getSpec();
@ -57,7 +64,12 @@ public class ServersConverter<T extends KubernetesEnvironment>
if (!machineConfig.getServers().isEmpty()) {
KubernetesServerExposer kubernetesServerExposer =
new KubernetesServerExposer<>(
externalServerExposerStrategy, machineName, podConfig, containerConfig, k8sEnv);
externalServerExposerStrategy,
secureServerExposer,
machineName,
podConfig,
containerConfig,
k8sEnv);
kubernetesServerExposer.expose(machineConfig.getServers());
}
}

View File

@ -39,6 +39,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.Constants;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
/**
* Helps to modify {@link KubernetesEnvironment} to make servers that are configured by {@link
@ -102,6 +103,7 @@ public class KubernetesServerExposer<T extends KubernetesEnvironment> {
public static final String SERVER_PREFIX = "server";
private final ExternalServerExposerStrategy<T> externalServerExposer;
private final SecureServerExposer<T> secureServerExposer;
private final String machineName;
private final Container container;
private final Pod pod;
@ -109,11 +111,13 @@ public class KubernetesServerExposer<T extends KubernetesEnvironment> {
public KubernetesServerExposer(
ExternalServerExposerStrategy<T> externalServerExposer,
SecureServerExposer<T> secureServerExposer,
String machineName,
Pod pod,
Container container,
T k8sEnv) {
this.externalServerExposer = externalServerExposer;
this.secureServerExposer = secureServerExposer;
this.machineName = machineName;
this.pod = pod;
this.container = container;
@ -133,13 +137,21 @@ public class KubernetesServerExposer<T extends KubernetesEnvironment> {
public void expose(Map<String, ? extends ServerConfig> servers) throws InfrastructureException {
Map<String, ServerConfig> internalServers = new HashMap<>();
Map<String, ServerConfig> externalServers = new HashMap<>();
Map<String, ServerConfig> secureServers = new HashMap<>();
servers.forEach(
(key, value) -> {
if ("true".equals(value.getAttributes().get(INTERNAL_SERVER_ATTRIBUTE))) {
// Server is internal. It doesn't make sense to make an it secure since
// it is available only within workspace servers
internalServers.put(key, value);
} else {
externalServers.put(key, value);
// Server is external. Check if it should be secure or not
if ("true".equals(value.getAttributes().get(ServerConfig.SECURE_SERVER_ATTRIBUTE))) {
secureServers.put(key, value);
} else {
externalServers.put(key, value);
}
}
});
@ -163,6 +175,13 @@ public class KubernetesServerExposer<T extends KubernetesEnvironment> {
externalServerExposer.expose(
k8sEnv, machineName, serviceName, servicePort, matchedExternalServers);
}
// expose service port related secure servers if exist
Map<String, ServerConfig> matchedSecureServers = match(secureServers, servicePort);
if (!matchedSecureServers.isEmpty()) {
secureServerExposer.expose(
k8sEnv, machineName, serviceName, servicePort, matchedSecureServers);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure;
import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
/**
* Default implementation of {@link SecureServerExposerFactory} that creates instances of {@link
* SecureServerExposer} that exposes secure servers as usual external server without setting
* authentication layer.
*
* @author Sergii Leshchenko
*/
public class DefaultSecureServersFactory<T extends KubernetesEnvironment>
implements SecureServerExposerFactory<T> {
private final ExternalServerExposerStrategy<T> exposerStrategy;
@Inject
public DefaultSecureServersFactory(ExternalServerExposerStrategy<T> exposerStrategy) {
this.exposerStrategy = exposerStrategy;
}
@Override
public SecureServerExposer<T> create(RuntimeIdentity runtimeId) {
return new DefaultSecureServerExposer();
}
private class DefaultSecureServerExposer implements SecureServerExposer<T> {
@Override
public void expose(
T k8sEnv,
String machineName,
String serviceName,
ServicePort servicePort,
Map<String, ServerConfig> secureServers) {
exposerStrategy.expose(k8sEnv, machineName, serviceName, servicePort, secureServers);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure;
import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
/**
* Modifies the specified Kubernetes environment to expose secure servers.
*
* <p>Note that ONE {@link SecureServerExposer} instance should be used for one workspace start.
*
* @author Sergii Leshchenko
*/
public interface SecureServerExposer<T extends KubernetesEnvironment> {
/**
* Modifies the specified Kubernetes environment to expose secure servers.
*
* @param k8sEnv Kubernetes environment that should be modified.
* @param machineName machine name to which secure servers belong to
* @param serviceName service name that exposes secure servers
* @param servicePort service port that exposes secure servers
* @param secureServers secure servers to expose
* @throws InfrastructureException when any exception occurs during servers exposing
*/
void expose(
T k8sEnv,
String machineName,
String serviceName,
ServicePort servicePort,
Map<String, ServerConfig> secureServers)
throws InfrastructureException;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
/**
* Helps to create {@link SecureServerExposer} instances.
*
* <p>Note that ONE {@link SecureServerExposer} instance should be used for one workspace start.
*
* @author Sergii Leshchenko
*/
public interface SecureServerExposerFactory<T extends KubernetesEnvironment> {
SecureServerExposer<T> create(RuntimeIdentity runtimeId);
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
/** @author Sergii Leshchenko */
public class SecureServerExposerFactoryProvider<T extends KubernetesEnvironment>
implements Provider<SecureServerExposerFactory<T>> {
private final boolean agentsAuthEnabled;
private final String serverExposer;
private final DefaultSecureServersFactory<T> defaultSecureServersFactory;
private final Map<String, SecureServerExposerFactory<T>> factories;
@Inject
public SecureServerExposerFactoryProvider(
@Named("che.agents.auth_enabled") boolean agentsAuthEnabled,
@Named("che.server.secure_exposer") String serverExposer,
DefaultSecureServersFactory<T> defaultSecureServersFactory,
Map<String, SecureServerExposerFactory<T>> factories) {
this.agentsAuthEnabled = agentsAuthEnabled;
this.serverExposer = serverExposer;
this.defaultSecureServersFactory = defaultSecureServersFactory;
this.factories = factories;
}
/**
* Creates instance of {@link SecureServerExposerFactory} that will expose secure servers for
* runtime with the specified runtime identity.
*/
@Override
public SecureServerExposerFactory<T> get() {
if (!agentsAuthEnabled) {
// return default secure server exposer because no need to protect servers with authentication
return defaultSecureServersFactory;
}
SecureServerExposerFactory<T> serverExposerFactory = factories.get(serverExposer);
if (serverExposerFactory == null) {
throw new ConfigurationException(
"Unknown secure servers exposer is configured '" + serverExposer + "'. ");
}
return serverExposerFactory;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FOLDER;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_PUBLIC_KEY_FILE;
import java.util.ArrayList;
import java.util.List;
/**
* Helps to build JWTProxy config with several verifier proxies.
*
* @author Sergii Leshchenko
*/
public class JwtProxyConfigBuilder {
private final List<VerifierProxy> verifierProxies = new ArrayList<>();
private final String workspaceId;
public JwtProxyConfigBuilder(String workspaceId) {
this.workspaceId = workspaceId;
}
public void addVerifierProxy(Integer listenPort, String upstream) {
verifierProxies.add(new VerifierProxy(listenPort, upstream));
}
public String build() {
StringBuilder configBuilder = new StringBuilder();
configBuilder.append("jwtproxy:\n" + " verifier_proxies:\n");
for (VerifierProxy verifierProxy : verifierProxies) {
configBuilder.append(
String.format(
" - listen_addr: :%s\n" // :4471
+ " verifier:\n"
+ " upstream: %s/\n" // http://localhost:4401
+ " audience: http://%s\n"
+ " max_skew: 1m\n"
+ " max_ttl: 3h\n"
+ " key_server:\n"
+ " type: preshared\n"
+ " options:\n"
+ " issuer: wsmaster\n"
+ " key_id: mykey\n"
+ " public_key_path: "
+ JWT_PROXY_CONFIG_FOLDER
+ "/"
+ JWT_PROXY_PUBLIC_KEY_FILE
+ "\n"
+ " claims_verifiers:\n"
+ " - type: static\n"
+ " options:\n"
+ " iss: wsmaster\n"
+ " nonce_storage:\n"
+ " type: void\n",
verifierProxy.listenPort,
verifierProxy.upstream,
workspaceId));
}
configBuilder.append(" signer_proxy:\n" + " enabled: false\n");
return configBuilder.toString();
}
private class VerifierProxy {
private Integer listenPort;
private String upstream;
VerifierProxy(Integer listenPort, String upstream) {
this.listenPort = listenPort;
this.upstream = upstream;
}
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.eclipse.che.commons.lang.NameGenerator.generate;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServicePort;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.config.MachineConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.ServerServiceBuilder;
/**
* Modifies Kubernetes environment to expose the specified service port via JWTProxy.
*
* <p>Exposing includes the following operation:
*
* <ul>
* <li>Putting Machine configuration into Kubernetes environment if absent;
* <li>Putting JwtProxy pod with one container if absent;
* <li>Putting JwtProxy service that will expose added JWTProxy pod if absent;
* <li>Putting JwtProxy ConfigMap that contains public key and jwtproxy config in yaml format if
* absent;
* <li>Updating JwtProxy Service to expose port for secure server;
* <li>Updating jwtproxy configuration in config map by adding the corresponding verifier proxy
* there;
* </ul>
*
* @see JwtProxyConfigBuilder
* @see SignatureKeyManager
* @author Sergii Leshchenko
*/
public class JwtProxyProvisioner {
static final int FIRST_AVAILABLE_PORT = 4400;
static final int JWT_PROXY_MEMORY_LIMIT_BYTES = 128 * 1024 * 1024; // 128mb
static final String PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----\n";
static final String PUBLIC_KEY_FOOTER = "\n-----END PUBLIC KEY-----";
static final String JWTPROXY_IMAGE = "ksmster/jwtproxy";
static final String JWT_PROXY_CONFIG_FILE = "config.yaml";
static final String JWT_PROXY_MACHINE_NAME = "che-jwtproxy";
static final String JWT_PROXY_POD_NAME = JWT_PROXY_MACHINE_NAME;
static final String JWT_PROXY_CONFIG_FOLDER = "/config";
static final String JWT_PROXY_PUBLIC_KEY_FILE = "mykey.pub";
private final SignatureKeyManager signatureKeyManager;
private final RuntimeIdentity identity;
private final JwtProxyConfigBuilder proxyConfigBuilder;
private final String serviceName;
private int availablePort;
public JwtProxyProvisioner(RuntimeIdentity identity, SignatureKeyManager signatureKeyManager) {
this.signatureKeyManager = signatureKeyManager;
this.identity = identity;
this.proxyConfigBuilder = new JwtProxyConfigBuilder(identity.getWorkspaceId());
this.serviceName = generate(SERVER_PREFIX, SERVER_UNIQUE_PART_SIZE) + "-jwtproxy";
this.availablePort = FIRST_AVAILABLE_PORT;
}
/**
* Modifies Kubernetes environment to expose the specified service port via JWTProxy.
*
* @param k8sEnv Kubernetes environment to modify
* @param backendServiceName service name that will be exposed
* @param backendServicePort service port that will be exposed
* @param protocol protocol that will be used for exposed port
* @return JWTProxy service port that expose the specified one
* @throws InfrastructureException if any exception occurs during port exposing
*/
public ServicePort expose(
KubernetesEnvironment k8sEnv,
String backendServiceName,
int backendServicePort,
String protocol)
throws InfrastructureException {
ensureJwtProxyInjected(k8sEnv);
int listenPort = availablePort++;
proxyConfigBuilder.addVerifierProxy(
listenPort, "http://" + backendServiceName + ":" + backendServicePort);
k8sEnv
.getConfigMaps()
.get(getConfigMapName())
.getData()
.put(JWT_PROXY_CONFIG_FILE, proxyConfigBuilder.build());
ServicePort exposedPort =
new ServicePortBuilder()
.withName("server-" + listenPort)
.withPort(listenPort)
.withProtocol(protocol)
.withNewTargetPort(listenPort)
.build();
k8sEnv.getServices().get(getServiceName()).getSpec().getPorts().add(exposedPort);
return exposedPort;
}
/** Returns service name that exposed JWTProxy Pod. */
public String getServiceName() {
return serviceName;
}
/** Returns config map name that will be mounted into JWTProxy Pod. */
@VisibleForTesting
String getConfigMapName() {
return "jwtproxy-config-" + identity.getWorkspaceId();
}
private void ensureJwtProxyInjected(KubernetesEnvironment k8sEnv) throws InfrastructureException {
if (!k8sEnv.getMachines().containsKey(JWT_PROXY_MACHINE_NAME)) {
k8sEnv.getMachines().put(JWT_PROXY_MACHINE_NAME, createJwtProxyMachine());
k8sEnv.getPods().put(JWT_PROXY_POD_NAME, createJwtProxyPod(identity));
KeyPair keyPair = signatureKeyManager.getKeyPair();
if (keyPair == null) {
throw new InternalInfrastructureException(
"Key pair for machine authentication does not exist");
}
Map<String, String> initConfigMapData = new HashMap<>();
initConfigMapData.put(
JWT_PROXY_PUBLIC_KEY_FILE,
PUBLIC_KEY_HEADER
+ java.util.Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())
+ PUBLIC_KEY_FOOTER);
initConfigMapData.put(JWT_PROXY_CONFIG_FILE, proxyConfigBuilder.build());
ConfigMap jwtProxyConfigMap =
new ConfigMapBuilder()
.withNewMetadata()
.withName(getConfigMapName())
.endMetadata()
.withData(initConfigMapData)
.build();
k8sEnv.getConfigMaps().put(jwtProxyConfigMap.getMetadata().getName(), jwtProxyConfigMap);
Service jwtProxyService =
new ServerServiceBuilder()
.withName(serviceName)
.withSelectorEntry(CHE_ORIGINAL_NAME_LABEL, JWT_PROXY_MACHINE_NAME)
.withMachineName(JWT_PROXY_MACHINE_NAME)
.withPorts(emptyList())
.build();
k8sEnv.getServices().put(jwtProxyService.getMetadata().getName(), jwtProxyService);
}
}
private InternalMachineConfig createJwtProxyMachine() {
return new InternalMachineConfig(
null,
emptyMap(),
emptyMap(),
ImmutableMap.of(
MachineConfig.MEMORY_LIMIT_ATTRIBUTE, Integer.toString(JWT_PROXY_MEMORY_LIMIT_BYTES)),
null);
}
private Pod createJwtProxyPod(RuntimeIdentity identity) {
return new PodBuilder()
.withNewMetadata()
.withName(JWT_PROXY_POD_NAME)
.withAnnotations(
ImmutableMap.of(
"org.eclipse.che.container.verifier.machine_name", JWT_PROXY_MACHINE_NAME))
.endMetadata()
.withNewSpec()
.withContainers(
new ContainerBuilder()
.withName("verifier")
.withImage(JWTPROXY_IMAGE)
.withVolumeMounts(
new VolumeMount(
JWT_PROXY_CONFIG_FOLDER + "/", "jwtproxy-config-volume", false, null))
.withArgs("-config", JWT_PROXY_CONFIG_FOLDER + "/" + JWT_PROXY_CONFIG_FILE)
.build())
.withVolumes(
new VolumeBuilder()
.withName("jwtproxy-config-volume")
.withNewConfigMap()
.withName("jwtproxy-config-" + identity.getWorkspaceId())
.endConfigMap()
.build())
.endSpec()
.build();
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.assistedinject.Assisted;
import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
/**
* Exposes secure servers with JWTProxy.
*
* <p>To expose secure servers it provisions JwtProxy objects into environment with {@link
* JwtProxyProvisioner}. Then JwtProxy service port is made public accessible by {@link
* ExternalServerExposerStrategy<T>}.
*
* <p>In this way, requests to exposed secure servers will be routed via JwtProxy pod that is added
* one per workspace. And it will be impossible to requests secure servers if there is no machine
* token in request.
*
* @see JwtProxyProvisioner
* @author Sergii Leshchenko
*/
public class JwtProxySecureServerExposer<T extends KubernetesEnvironment>
implements SecureServerExposer<T> {
private final ExternalServerExposerStrategy<T> exposerStrategy;
private final JwtProxyProvisioner proxyProvisioner;
@VisibleForTesting
JwtProxySecureServerExposer(
JwtProxyProvisioner jwtProxyProvisioner, ExternalServerExposerStrategy<T> exposerStrategy) {
this.exposerStrategy = exposerStrategy;
this.proxyProvisioner = jwtProxyProvisioner;
}
@Inject
public JwtProxySecureServerExposer(
@Assisted RuntimeIdentity identity,
SignatureKeyManager signatureKeyManager,
ExternalServerExposerStrategy<T> exposerStrategy) {
this.exposerStrategy = exposerStrategy;
proxyProvisioner = new JwtProxyProvisioner(identity, signatureKeyManager);
}
@Override
public void expose(
T k8sEnv,
String machineName,
String serviceName,
ServicePort servicePort,
Map<String, ServerConfig> secureServers)
throws InfrastructureException {
ServicePort exposedServicePort =
proxyProvisioner.expose(
k8sEnv,
serviceName,
servicePort.getTargetPort().getIntVal(),
servicePort.getProtocol());
exposerStrategy.expose(
k8sEnv, machineName, proxyProvisioner.getServiceName(), exposedServicePort, secureServers);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposerFactory;
/**
* Helps to create {@link JwtProxySecureServerExposerFactory} with fields injected from DI
* container.
*
* @author Sergii Leshchenko
*/
public interface JwtProxySecureServerExposerFactory<T extends KubernetesEnvironment>
extends SecureServerExposerFactory<T> {
@Override
JwtProxySecureServerExposer<T> create(RuntimeIdentity identity);
}

View File

@ -41,6 +41,7 @@ import static org.testng.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerPort;
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
@ -109,6 +110,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesMachi
import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesRuntimeState;
import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesRuntimeState.RuntimeId;
import org.eclipse.che.workspace.infrastructure.kubernetes.model.KubernetesServerImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
@ -172,6 +174,7 @@ public class KubernetesInternalRuntimeTest {
@Mock private KubernetesServices services;
@Mock private KubernetesIngresses ingresses;
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configMaps;
@Mock private KubernetesDeployments deployments;
@Mock private KubernetesBootstrapper bootstrapper;
@Mock private WorkspaceVolumesStrategy volumesStrategy;
@ -256,6 +259,7 @@ public class KubernetesInternalRuntimeTest {
when(namespace.ingresses()).thenReturn(ingresses);
when(namespace.deployments()).thenReturn(deployments);
when(namespace.secrets()).thenReturn(secrets);
when(namespace.configMaps()).thenReturn(configMaps);
when(bootstrapperFactory.create(any(), anyList(), any(), any(), any()))
.thenReturn(bootstrapper);
doReturn(
@ -288,12 +292,15 @@ public class KubernetesInternalRuntimeTest {
@Test
public void startsKubernetesEnvironment() throws Exception {
when(k8sEnv.getSecrets()).thenReturn(ImmutableMap.of("secret", new Secret()));
when(k8sEnv.getConfigMaps()).thenReturn(ImmutableMap.of("configMap", new ConfigMap()));
internalRuntime.internalStart(emptyMap());
verify(deployments).deploy(any());
verify(ingresses).create(any());
verify(services).create(any());
verify(secrets).create(any());
verify(configMaps).create(any());
verify(namespace.deployments(), times(2)).watchEvents(any());
verify(bootstrapper, times(2)).bootstrapAsync();
verify(eventService, times(4)).publish(any());

View File

@ -18,6 +18,8 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.MACHINE_NAME_ANNOTATION_FMT;
import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory.CONFIG_MAP_IGNORED_WARNING_CODE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory.CONFIG_MAP_IGNORED_WARNING_MESSAGE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory.INGRESSES_IGNORED_WARNING_CODE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory.INGRESSES_IGNORED_WARNING_MESSAGE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory.PVC_IGNORED_WARNING_CODE;
@ -33,6 +35,7 @@ import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.DoneableKubernetesList;
import io.fabric8.kubernetes.api.model.HasMetadata;
@ -150,13 +153,28 @@ public class KubernetesEnvironmentFactoryTest {
final KubernetesEnvironment parsed =
k8sEnvironmentFactory.doCreate(internalRecipe, emptyMap(), emptyList());
assertTrue(parsed.getPersistentVolumeClaims().isEmpty());
assertTrue(parsed.getSecrets().isEmpty());
assertEquals(parsed.getWarnings().size(), 1);
assertEquals(
parsed.getWarnings().get(0),
new WarningImpl(SECRET_IGNORED_WARNING_CODE, SECRET_IGNORED_WARNING_MESSAGE));
}
@Test
public void ignoreConfigMapsWhenRecipeContainsThem() throws Exception {
final List<HasMetadata> recipeObjects = singletonList(new ConfigMap());
when(validatedObjects.getItems()).thenReturn(recipeObjects);
final KubernetesEnvironment parsed =
k8sEnvironmentFactory.doCreate(internalRecipe, emptyMap(), emptyList());
assertTrue(parsed.getConfigMaps().isEmpty());
assertEquals(parsed.getWarnings().size(), 1);
assertEquals(
parsed.getWarnings().get(0),
new WarningImpl(CONFIG_MAP_IGNORED_WARNING_CODE, CONFIG_MAP_IGNORED_WARNING_MESSAGE));
}
@Test
public void testSetsRamLimitAttributeFromKubernetesResource() throws Exception {
final long firstMachineRamLimit = 3072;

View File

@ -68,6 +68,7 @@ public class KubernetesNamespaceTest {
@Mock private KubernetesIngresses ingresses;
@Mock private KubernetesPersistentVolumeClaims pvcs;
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configMaps;
@Mock private KubernetesClientFactory clientFactory;
@Mock private KubernetesClient kubernetesClient;
@Mock private NonNamespaceOperation namespaceOperation;
@ -125,7 +126,8 @@ public class KubernetesNamespaceTest {
services,
pvcs,
ingresses,
secrets);
secrets,
configMaps);
}
@Test
@ -163,6 +165,7 @@ public class KubernetesNamespaceTest {
verify(services).delete();
verify(deployments).delete();
verify(secrets).delete();
verify(configMaps).delete();
}
@Test

View File

@ -37,6 +37,7 @@ import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.SecureServerExposer;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
@ -52,11 +53,15 @@ import org.testng.annotations.Test;
public class KubernetesServerExposerTest {
@Mock private ExternalServerExposerStrategy<KubernetesEnvironment> externalServerExposerStrategy;
@Mock private SecureServerExposer<KubernetesEnvironment> secureServerExposer;
private static final Map<String, String> ATTRIBUTES_MAP = singletonMap("key", "value");
private static final Map<String, String> INTERNAL_SERVER_ATTRIBUTE_MAP =
singletonMap(ServerConfig.INTERNAL_SERVER_ATTRIBUTE, Boolean.TRUE.toString());
private static final Map<String, String> SECURE_SERVER_ATTRIBUTE_MAP =
singletonMap(ServerConfig.SECURE_SERVER_ATTRIBUTE, Boolean.TRUE.toString());
private static final Pattern SERVER_PREFIX_REGEX =
Pattern.compile('^' + SERVER_PREFIX + "[A-z0-9]{" + SERVER_UNIQUE_PART_SIZE + "}-pod-main$");
private static final String MACHINE_NAME = "pod/main";
@ -82,7 +87,12 @@ public class KubernetesServerExposerTest {
KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build();
this.serverExposer =
new KubernetesServerExposer<>(
externalServerExposerStrategy, MACHINE_NAME, pod, container, kubernetesEnvironment);
externalServerExposerStrategy,
secureServerExposer,
MACHINE_NAME,
pod,
container,
kubernetesEnvironment);
}
@Test
@ -270,33 +280,36 @@ public class KubernetesServerExposerTest {
}
@Test
public void shouldExposeInternalAndExternalServers() throws Exception {
public void shouldExposeInternalAndExternalAndSecureServers() throws Exception {
// given
ServerConfigImpl secureServerConfig =
new ServerConfigImpl("8282/tcp", "http", "/api", SECURE_SERVER_ATTRIBUTE_MAP);
ServerConfigImpl internalServerConfig =
new ServerConfigImpl("8080/tcp", "http", "/api", INTERNAL_SERVER_ATTRIBUTE_MAP);
ServerConfigImpl externalServerConfig =
new ServerConfigImpl("9090/tcp", "http", "/api", ATTRIBUTES_MAP);
Map<String, ServerConfigImpl> serversToExpose =
ImmutableMap.of("int-server", internalServerConfig, "ext-server", externalServerConfig);
ImmutableMap.of(
"int-server",
internalServerConfig,
"ext-server",
externalServerConfig,
"secure-server",
secureServerConfig);
// when
serverExposer.expose(serversToExpose);
// then
assertThatInternalServerIsExposed(
MACHINE_NAME,
"int-server",
"tcp",
8080,
new ServerConfigImpl(internalServerConfig).withAttributes(INTERNAL_SERVER_ATTRIBUTE_MAP));
MACHINE_NAME, "int-server", "tcp", 8080, new ServerConfigImpl(internalServerConfig));
assertThatExternalServerIsExposed(
MACHINE_NAME,
"tcp",
9090,
"ext-server",
new ServerConfigImpl(externalServerConfig).withAttributes(ATTRIBUTES_MAP));
MACHINE_NAME, "tcp", 9090, "ext-server", new ServerConfigImpl(externalServerConfig));
assertThatSecureServerIsExposed(
MACHINE_NAME, "tcp", 8282, "secure-server", new ServerConfigImpl(secureServerConfig));
}
@SuppressWarnings("SameParameterValue")
private void assertThatExternalServerIsExposed(
String machineName,
String portProtocol,
@ -314,38 +327,14 @@ public class KubernetesServerExposerTest {
Integer port,
Map<String, ServerConfig> expectedServers) {
// then
assertTrue(
container
.getPorts()
.stream()
.anyMatch(
p ->
p.getContainerPort().equals(port)
&& p.getProtocol().equals(portProtocol.toUpperCase())));
assertThatContainerPortIsExposed(portProtocol, port);
// ensure that service is created
Service service = null;
for (Entry<String, Service> entry : kubernetesEnvironment.getServices().entrySet()) {
if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) {
service = entry.getValue();
break;
}
}
Service service = findContainerRelatedService();
assertNotNull(service);
// ensure that required service port is exposed
Optional<ServicePort> servicePortOpt =
service
.getSpec()
.getPorts()
.stream()
.filter(p -> p.getTargetPort().getIntVal().equals(port))
.findAny();
assertTrue(servicePortOpt.isPresent());
ServicePort servicePort = servicePortOpt.get();
assertEquals(servicePort.getTargetPort().getIntVal(), port);
assertEquals(servicePort.getPort(), port);
assertEquals(servicePort.getName(), SERVER_PREFIX + "-" + port);
ServicePort servicePort = assertThatServicePortIsExposed(port, service);
Annotations.Deserializer serviceAnnotations =
Annotations.newDeserializer(service.getMetadata().getAnnotations());
@ -360,6 +349,37 @@ public class KubernetesServerExposerTest {
expectedServers);
}
@SuppressWarnings("SameParameterValue")
private void assertThatSecureServerIsExposed(
String machineName,
String portProtocol,
Integer port,
String serverName,
ServerConfig serverConfig)
throws Exception {
// then
assertThatContainerPortIsExposed(portProtocol, port);
// ensure that service is created
Service service = findContainerRelatedService();
assertNotNull(service);
// ensure that required service port is exposed
ServicePort servicePort = assertThatServicePortIsExposed(port, service);
Annotations.Deserializer serviceAnnotations =
Annotations.newDeserializer(service.getMetadata().getAnnotations());
assertEquals(serviceAnnotations.machineName(), machineName);
verify(secureServerExposer)
.expose(
kubernetesEnvironment,
machineName,
service.getMetadata().getName(),
servicePort,
ImmutableMap.of(serverName, serverConfig));
}
@SuppressWarnings("SameParameterValue")
private void assertThatInternalServerIsExposed(
String machineName,
@ -367,7 +387,26 @@ public class KubernetesServerExposerTest {
String portProtocol,
Integer port,
ServerConfigImpl expected) {
// then
assertThatContainerPortIsExposed(portProtocol, port);
// ensure that service is created
Service service = findContainerRelatedService();
assertNotNull(service);
// ensure that required service port is exposed
assertThatServicePortIsExposed(port, service);
Annotations.Deserializer serviceAnnotations =
Annotations.newDeserializer(service.getMetadata().getAnnotations());
assertEquals(serviceAnnotations.machineName(), machineName);
Map<String, ServerConfigImpl> servers = serviceAnnotations.servers();
ServerConfig serverConfig = servers.get(serverNameRegex);
assertEquals(serverConfig, expected);
}
private void assertThatContainerPortIsExposed(String portProtocol, Integer port) {
assertTrue(
container
.getPorts()
@ -376,8 +415,9 @@ public class KubernetesServerExposerTest {
p ->
p.getContainerPort().equals(port)
&& p.getProtocol().equals(portProtocol.toUpperCase())));
// ensure that service is created
}
private Service findContainerRelatedService() {
Service service = null;
for (Entry<String, Service> entry : kubernetesEnvironment.getServices().entrySet()) {
if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) {
@ -385,9 +425,10 @@ public class KubernetesServerExposerTest {
break;
}
}
assertNotNull(service);
return service;
}
// ensure that required service port is exposed
private ServicePort assertThatServicePortIsExposed(Integer port, Service service) {
Optional<ServicePort> servicePortOpt =
service
.getSpec()
@ -400,13 +441,6 @@ public class KubernetesServerExposerTest {
assertEquals(servicePort.getTargetPort().getIntVal(), port);
assertEquals(servicePort.getPort(), port);
assertEquals(servicePort.getName(), SERVER_PREFIX + "-" + port);
Annotations.Deserializer serviceAnnotations =
Annotations.newDeserializer(service.getMetadata().getAnnotations());
assertEquals(serviceAnnotations.machineName(), machineName);
Map<String, ServerConfigImpl> servers = serviceAnnotations.servers();
ServerConfig serverConfig = servers.get(serverNameRegex);
assertEquals(serverConfig, expected);
return servicePort;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure;
import static org.testng.Assert.assertSame;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/** @author Sergii Leshchenko */
@Listeners(MockitoTestNGListener.class)
public class SecureServerExposerFactoryProviderTest {
@Mock private DefaultSecureServersFactory<KubernetesEnvironment> defaultSecureServersFactory;
@Mock private SecureServerExposerFactory<KubernetesEnvironment> customSecureServerExposer;
private Map<String, SecureServerExposerFactory<KubernetesEnvironment>> factories =
new HashMap<>();
@Test
public void shouldReturnDefaultSecureServerExposerWhenAgentAuthIsDisabled() {
// given
SecureServerExposerFactoryProvider<KubernetesEnvironment> factoryProvider =
new SecureServerExposerFactoryProvider<>(
false, "custom", defaultSecureServersFactory, factories);
// when
SecureServerExposerFactory<KubernetesEnvironment> factory = factoryProvider.get();
// then
assertSame(factory, defaultSecureServersFactory);
}
@Test
public void shouldReturnConfiguredSecureServerExposerWhenAgentAuthIsEnabled() {
// given
factories.put("custom", customSecureServerExposer);
SecureServerExposerFactoryProvider<KubernetesEnvironment> factoryProvider =
new SecureServerExposerFactoryProvider<>(
true, "custom", defaultSecureServersFactory, factories);
// when
SecureServerExposerFactory<KubernetesEnvironment> factory = factoryProvider.get();
// then
assertSame(factory, customSecureServerExposer);
}
@Test(expectedExceptions = ConfigurationException.class)
public void shouldThrowAnExceptionIfConfiguredSecureServerWasNotFound() {
// given
SecureServerExposerFactoryProvider<KubernetesEnvironment> factoryProvider =
new SecureServerExposerFactoryProvider<>(
true, "non-existing", defaultSecureServersFactory, factories);
// when
factoryProvider.get();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.reporters.Files;
/**
* Tests {@link JwtProxyConfigBuilder}.
*
* @author Sergii Leshchenko
*/
public class JwtProxyConfigBuilderTest {
private JwtProxyConfigBuilder jwtProxyConfigBuilder;
@BeforeMethod
public void setUp() {
jwtProxyConfigBuilder = new JwtProxyConfigBuilder("workspace123");
}
@Test
public void shouldBuildJwtProxyConfigInYamlFormat() throws Exception {
// given
jwtProxyConfigBuilder.addVerifierProxy(8080, "http://tomcat:8080");
jwtProxyConfigBuilder.addVerifierProxy(4101, "ws://terminal:4101");
// when
String jwtProxyConfigYaml = jwtProxyConfigBuilder.build();
// then
assertEquals(
jwtProxyConfigYaml,
Files.readFile(getClass().getClassLoader().getResourceAsStream("jwtproxy-confg.yaml")));
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_UNIQUE_PART_SIZE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_CONFIG_FILE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.JWT_PROXY_PUBLIC_KEY_FILE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_FOOTER;
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_HEADER;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Service;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Base64;
import java.util.regex.Pattern;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Tests {@link JwtProxyProvisioner}.
*
* @author Sergii Leshchenko
*/
@Listeners(MockitoTestNGListener.class)
public class JwtProxyProvisionerTest {
private static final String WORKSPACE_ID = "workspace123";
private static final Pattern JWTPROXY_SERVICE_NAME_PATTERN =
Pattern.compile(SERVER_PREFIX + "\\w{" + SERVER_UNIQUE_PART_SIZE + "}-jwtproxy");
private final RuntimeIdentity runtimeId =
new RuntimeIdentityImpl(WORKSPACE_ID, "env123", "owner123");
@Mock private SignatureKeyManager signatureKeyManager;
private KeyPair keyPair;
@Mock private PublicKey publicKey;
private JwtProxyProvisioner jwtProxyProvisioner;
private KubernetesEnvironment k8sEnv;
@BeforeMethod
public void setUp() {
keyPair = new KeyPair(publicKey, null);
when(signatureKeyManager.getKeyPair()).thenReturn(keyPair);
when(publicKey.getEncoded()).thenReturn("publickey".getBytes());
jwtProxyProvisioner = new JwtProxyProvisioner(runtimeId, signatureKeyManager);
k8sEnv = KubernetesEnvironment.builder().build();
}
@Test
public void shouldReturnGeneratedJwtProxyServiceName() {
// when
String jwtProxyServiceName = jwtProxyProvisioner.getServiceName();
// then
assertTrue(JWTPROXY_SERVICE_NAME_PATTERN.matcher(jwtProxyServiceName).matches());
}
@Test
public void shouldReturnGeneratedJwtProxyConfigMapName() {
// when
String jwtProxyConfigMap = jwtProxyProvisioner.getConfigMapName();
// then
assertEquals(jwtProxyConfigMap, "jwtproxy-config-" + WORKSPACE_ID);
}
@Test
public void shouldProvisionJwtProxyRelatedObjectsIntoKubernetesEnvironment() throws Exception {
// when
jwtProxyProvisioner.expose(k8sEnv, "terminal", 4401, "TCP");
// then
InternalMachineConfig jwtProxyMachine =
k8sEnv.getMachines().get(JwtProxyProvisioner.JWT_PROXY_MACHINE_NAME);
assertNotNull(jwtProxyMachine);
ConfigMap configMap = k8sEnv.getConfigMaps().get(jwtProxyProvisioner.getConfigMapName());
assertNotNull(configMap);
assertEquals(
configMap.getData().get(JWT_PROXY_PUBLIC_KEY_FILE),
PUBLIC_KEY_HEADER
+ Base64.getEncoder().encodeToString("publickey".getBytes())
+ PUBLIC_KEY_FOOTER);
assertNotNull(configMap.getData().get(JWT_PROXY_CONFIG_FILE));
Pod jwtProxyPod = k8sEnv.getPods().get("che-jwtproxy");
assertNotNull(jwtProxyPod);
Service jwtProxyService = k8sEnv.getServices().get(jwtProxyProvisioner.getServiceName());
assertNotNull(jwtProxyService);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.ServicePort;
import java.util.Map;
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.external.ExternalServerExposerStrategy;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Tests {@link JwtProxySecureServerExposer}
*
* @author Sergii Leshchenko
*/
@Listeners(MockitoTestNGListener.class)
public class JwtProxySecureServerExposerTest {
private static final String MACHINE_SERVICE_NAME = "service123";
private static final String MACHINE_NAME = "machine123";
public static final String JWT_PROXY_SERVICE_NAME = "jwtProxyServiceName";
@Mock private KubernetesEnvironment k8sEnv;
@Mock private JwtProxyProvisioner jwtProxyProvisioner;
@Mock private ExternalServerExposerStrategy<KubernetesEnvironment> externalServerExposer;
private JwtProxySecureServerExposer<KubernetesEnvironment> secureServerExposer;
@BeforeMethod
public void setUp() {
secureServerExposer =
new JwtProxySecureServerExposer<>(jwtProxyProvisioner, externalServerExposer);
}
@Test
public void shouldExposeSecureServersWithNewJwtProxyServicePort() throws Exception {
// given
ServicePort machineServicePort = new ServicePort();
machineServicePort.setTargetPort(new IntOrString(8080));
machineServicePort.setProtocol("TCP");
Map<String, ServerConfig> servers =
ImmutableMap.of(
"server1",
new ServerConfigImpl("8080/tcp", "http", "/api", ImmutableMap.of("secure", "true")),
"server2",
new ServerConfigImpl("8080/tcp", "ws", "/connect", ImmutableMap.of("secure", "true")));
ServicePort jwtProxyServicePort = new ServicePort();
doReturn(jwtProxyServicePort)
.when(jwtProxyProvisioner)
.expose(any(), anyString(), anyInt(), anyString());
when(jwtProxyProvisioner.getServiceName()).thenReturn(JWT_PROXY_SERVICE_NAME);
// when
secureServerExposer.expose(
k8sEnv, MACHINE_NAME, MACHINE_SERVICE_NAME, machineServicePort, servers);
// then
verify(jwtProxyProvisioner).expose(k8sEnv, MACHINE_SERVICE_NAME, 8080, "TCP");
verify(externalServerExposer)
.expose(k8sEnv, MACHINE_NAME, JWT_PROXY_SERVICE_NAME, jwtProxyServicePort, servers);
}
}

View File

@ -0,0 +1,40 @@
jwtproxy:
verifier_proxies:
- listen_addr: :8080
verifier:
upstream: http://tomcat:8080/
audience: http://workspace123
max_skew: 1m
max_ttl: 3h
key_server:
type: preshared
options:
issuer: wsmaster
key_id: mykey
public_key_path: /config/mykey.pub
claims_verifiers:
- type: static
options:
iss: wsmaster
nonce_storage:
type: void
- listen_addr: :4101
verifier:
upstream: ws://terminal:4101/
audience: http://workspace123
max_skew: 1m
max_ttl: 3h
key_server:
type: preshared
options:
issuer: wsmaster
key_id: mykey
public_key_path: /config/mykey.pub
claims_verifiers:
- type: static
options:
iss: wsmaster
nonce_storage:
type: void
signer_proxy:
enabled: false

View File

@ -42,6 +42,10 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesC
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.external.ExternalServerExposerStrategy;
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;
import org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxySecureServerExposerFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.wsnext.KubernetesWorkspaceNextApplier;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory;
@ -97,5 +101,26 @@ public class OpenShiftInfraModule extends AbstractModule {
MapBinder<String, WorkspaceNextApplier> wsNext =
MapBinder.newMapBinder(binder(), String.class, WorkspaceNextApplier.class);
wsNext.addBinding(OpenShiftEnvironment.TYPE).to(KubernetesWorkspaceNextApplier.class);
bind(new TypeLiteral<SecureServerExposerFactory<OpenShiftEnvironment>>() {})
.toProvider(new TypeLiteral<SecureServerExposerFactoryProvider<OpenShiftEnvironment>>() {});
MapBinder<String, SecureServerExposerFactory<OpenShiftEnvironment>>
secureServerExposerFactories =
MapBinder.newMapBinder(
binder(),
new TypeLiteral<String>() {},
new TypeLiteral<SecureServerExposerFactory<OpenShiftEnvironment>>() {});
secureServerExposerFactories
.addBinding("default")
.to(new TypeLiteral<DefaultSecureServersFactory<OpenShiftEnvironment>>() {});
install(
new FactoryModuleBuilder()
.build(new TypeLiteral<JwtProxySecureServerExposerFactory<OpenShiftEnvironment>>() {}));
secureServerExposerFactories
.addBinding("jwtproxy")
.to(new TypeLiteral<JwtProxySecureServerExposerFactory<OpenShiftEnvironment>>() {});
}
}

View File

@ -12,6 +12,7 @@ package org.eclipse.che.workspace.infrastructure.openshift;
import com.google.common.collect.ImmutableSet;
import com.google.inject.assistedinject.Assisted;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Service;
@ -98,6 +99,10 @@ public class OpenShiftInternalRuntime extends KubernetesInternalRuntime<OpenShif
project.secrets().create(secret);
}
for (ConfigMap configMap : osEnv.getConfigMaps().values()) {
project.configMaps().create(configMap);
}
List<Service> createdServices = new ArrayList<>();
for (Service service : osEnv.getServices().values()) {
createdServices.add(project.services().create(service));

View File

@ -10,6 +10,7 @@
*/
package org.eclipse.che.workspace.infrastructure.openshift.environment;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
@ -54,8 +55,9 @@ public class OpenShiftEnvironment extends KubernetesEnvironment {
Map<String, Ingress> ingresses,
Map<String, PersistentVolumeClaim> pvcs,
Map<String, Secret> secrets,
Map<String, ConfigMap> configMaps,
Map<String, Route> routes) {
super(internalRecipe, machines, warnings, pods, services, ingresses, pvcs, secrets);
super(internalRecipe, machines, warnings, pods, services, ingresses, pvcs, secrets, configMaps);
this.routes = routes;
}
@ -117,6 +119,12 @@ public class OpenShiftEnvironment extends KubernetesEnvironment {
return this;
}
@Override
public Builder setConfigMaps(Map<String, ConfigMap> configMaps) {
this.configMaps.putAll(configMaps);
return this;
}
public Builder setRoutes(Map<String, Route> route) {
this.routes.putAll(route);
return this;
@ -124,7 +132,16 @@ public class OpenShiftEnvironment extends KubernetesEnvironment {
public OpenShiftEnvironment build() {
return new OpenShiftEnvironment(
internalRecipe, machines, warnings, pods, services, ingresses, pvcs, secrets, routes);
internalRecipe,
machines,
warnings,
pods,
services,
ingresses,
pvcs,
secrets,
configMaps,
routes);
}
}
}

View File

@ -15,6 +15,7 @@ import static java.lang.String.format;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
import com.google.common.annotations.VisibleForTesting;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
@ -68,6 +69,10 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
static final String SECRET_IGNORED_WARNING_MESSAGE =
"Secrets specified in OpenShift recipe are ignored.";
static final int CONFIG_MAP_IGNORED_WARNING_CODE = 4103;
static final String CONFIG_MAP_IGNORED_WARNING_MESSAGE =
"Config maps specified in Kubernetes recipe are ignored.";
private final OpenShiftClientFactory clientFactory;
private final KubernetesEnvironmentValidator envValidator;
private final String defaultMachineMemorySizeAttribute;
@ -122,6 +127,7 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
boolean isAnyRoutePresent = false;
boolean isAnyPVCPresent = false;
boolean isAnySecretPresent = false;
boolean isAnyConfigMapPresent = false;
for (HasMetadata object : list.getItems()) {
if (object instanceof DeploymentConfig) {
throw new ValidationException("Supporting of deployment configs is not implemented yet.");
@ -137,6 +143,8 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
isAnyPVCPresent = true;
} else if (object instanceof Secret) {
isAnySecretPresent = true;
} else if (object instanceof ConfigMap) {
isAnyConfigMapPresent = true;
} else {
throw new ValidationException(
format("Found unknown object type '%s'", object.getMetadata()));
@ -155,6 +163,11 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
warnings.add(new WarningImpl(SECRET_IGNORED_WARNING_CODE, SECRET_IGNORED_WARNING_MESSAGE));
}
if (isAnyConfigMapPresent) {
warnings.add(
new WarningImpl(CONFIG_MAP_IGNORED_WARNING_CODE, CONFIG_MAP_IGNORED_WARNING_MESSAGE));
}
addRamLimitAttribute(machines, pods.values());
OpenShiftEnvironment osEnv =
@ -166,6 +179,7 @@ public class OpenShiftEnvironmentFactory extends InternalEnvironmentFactory<Open
.setServices(services)
.setPersistentVolumeClaims(new HashMap<>())
.setSecrets(new HashMap<>())
.setConfigMaps(new HashMap<>())
.setRoutes(new HashMap<>())
.build();

View File

@ -18,6 +18,7 @@ import io.fabric8.openshift.api.model.Route;
import io.fabric8.openshift.client.OpenShiftClient;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace;
@ -46,8 +47,18 @@ public class OpenShiftProject extends KubernetesNamespace {
OpenShiftRoutes routes,
KubernetesPersistentVolumeClaims pvcs,
KubernetesIngresses ingresses,
KubernetesSecrets secrets) {
super(clientFactory, workspaceId, name, deployments, services, pvcs, ingresses, secrets);
KubernetesSecrets secrets,
KubernetesConfigsMaps configMaps) {
super(
clientFactory,
workspaceId,
name,
deployments,
services,
pvcs,
ingresses,
secrets,
configMaps);
this.clientFactory = clientFactory;
this.routes = routes;
}
@ -84,7 +95,12 @@ public class OpenShiftProject extends KubernetesNamespace {
/** Removes all object except persistent volume claims inside project. */
public void cleanUp() throws InfrastructureException {
doRemove(routes::delete, services()::delete, deployments()::delete, secrets()::delete);
doRemove(
routes::delete,
services()::delete,
deployments()::delete,
secrets()::delete,
configMaps()::delete);
}
private void create(String projectName, KubernetesClient kubeClient, OpenShiftClient ocClient)

View File

@ -29,6 +29,7 @@ import static org.testng.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerPort;
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
@ -70,6 +71,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.bootstrapper.Kubernet
import org.eclipse.che.workspace.infrastructure.kubernetes.bootstrapper.KubernetesBootstrapperFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesMachineCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.KubernetesRuntimeStateCache;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesServices;
@ -125,6 +127,7 @@ public class OpenShiftInternalRuntimeTest {
@Mock private OpenShiftProject project;
@Mock private KubernetesServices services;
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configMaps;
@Mock private OpenShiftRoutes routes;
@Mock private KubernetesDeployments deployments;
@Mock private KubernetesBootstrapper bootstrapper;
@ -195,6 +198,7 @@ public class OpenShiftInternalRuntimeTest {
when(project.services()).thenReturn(services);
when(project.routes()).thenReturn(routes);
when(project.secrets()).thenReturn(secrets);
when(project.configMaps()).thenReturn(configMaps);
when(project.deployments()).thenReturn(deployments);
when(bootstrapperFactory.create(any(), anyList(), any(), any(), any()))
.thenReturn(bootstrapper);
@ -218,6 +222,7 @@ public class OpenShiftInternalRuntimeTest {
when(osEnv.getRoutes()).thenReturn(allRoutes);
when(osEnv.getPods()).thenReturn(allPods);
when(osEnv.getSecrets()).thenReturn(ImmutableMap.of("secret", new Secret()));
when(osEnv.getConfigMaps()).thenReturn(ImmutableMap.of("configMap", new ConfigMap()));
}
@Test
@ -234,6 +239,7 @@ public class OpenShiftInternalRuntimeTest {
verify(routes).create(any());
verify(services).create(any());
verify(secrets).create(any());
verify(configMaps).create(any());
verify(project.deployments(), times(2)).watchEvents(any());
verify(eventService, times(2)).publish(any());

View File

@ -18,6 +18,8 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.MACHINE_NAME_ANNOTATION_FMT;
import static org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory.CONFIG_MAP_IGNORED_WARNING_CODE;
import static org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory.CONFIG_MAP_IGNORED_WARNING_MESSAGE;
import static org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory.PVC_IGNORED_WARNING_CODE;
import static org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory.PVC_IGNORED_WARNING_MESSAGE;
import static org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory.ROUTES_IGNORED_WARNING_MESSAGE;
@ -33,6 +35,7 @@ import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.DoneableKubernetesList;
import io.fabric8.kubernetes.api.model.HasMetadata;
@ -59,6 +62,7 @@ import org.eclipse.che.api.workspace.server.model.impl.WarningImpl;
import org.eclipse.che.api.workspace.server.spi.environment.InternalEnvironment;
import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig;
import org.eclipse.che.api.workspace.server.spi.environment.InternalRecipe;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentValidator;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.mockito.Mock;
@ -137,7 +141,7 @@ public class OpenShiftEnvironmentFactoryTest {
final OpenShiftEnvironment parsed =
osEnvironmentFactory.doCreate(internalRecipe, emptyMap(), emptyList());
assertTrue(parsed.getRoutes().isEmpty());
assertTrue(parsed.getPersistentVolumeClaims().isEmpty());
assertEquals(parsed.getWarnings().size(), 1);
assertEquals(
parsed.getWarnings().get(0),
@ -152,13 +156,28 @@ public class OpenShiftEnvironmentFactoryTest {
final OpenShiftEnvironment parsed =
osEnvironmentFactory.doCreate(internalRecipe, emptyMap(), emptyList());
assertTrue(parsed.getRoutes().isEmpty());
assertTrue(parsed.getSecrets().isEmpty());
assertEquals(parsed.getWarnings().size(), 1);
assertEquals(
parsed.getWarnings().get(0),
new WarningImpl(SECRET_IGNORED_WARNING_CODE, SECRET_IGNORED_WARNING_MESSAGE));
}
@Test
public void ignoreConfigMapsWhenRecipeContainsThem() throws Exception {
final List<HasMetadata> recipeObjects = singletonList(new ConfigMap());
when(validatedObjects.getItems()).thenReturn(recipeObjects);
final KubernetesEnvironment parsed =
osEnvironmentFactory.doCreate(internalRecipe, emptyMap(), emptyList());
assertTrue(parsed.getConfigMaps().isEmpty());
assertEquals(parsed.getWarnings().size(), 1);
assertEquals(
parsed.getWarnings().get(0),
new WarningImpl(CONFIG_MAP_IGNORED_WARNING_CODE, CONFIG_MAP_IGNORED_WARNING_MESSAGE));
}
@Test
public void testSetsRamLimitAttributeFromOpenShiftResource() throws Exception {
final long firstMachineRamLimit = 3072 * BYTES_IN_MB;

View File

@ -32,6 +32,7 @@ import io.fabric8.openshift.api.model.ProjectRequestFluent.MetadataNested;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.openshift.client.dsl.ProjectRequestOperation;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesDeployments;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesIngresses;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesPersistentVolumeClaims;
@ -61,6 +62,7 @@ public class OpenShiftProjectTest {
@Mock private KubernetesPersistentVolumeClaims pvcs;
@Mock private KubernetesIngresses ingresses;
@Mock private KubernetesSecrets secrets;
@Mock private KubernetesConfigsMaps configsMaps;
@Mock private OpenShiftClientFactory clientFactory;
@Mock private OpenShiftClient openShiftClient;
@Mock private KubernetesClient kubernetesClient;
@ -93,7 +95,8 @@ public class OpenShiftProjectTest {
routes,
pvcs,
ingresses,
secrets);
secrets,
configsMaps);
}
@Test
@ -133,6 +136,7 @@ public class OpenShiftProjectTest {
verify(services).delete();
verify(deployments).delete();
verify(secrets).delete();
verify(configsMaps).delete();
}
@Test

View File

@ -12,12 +12,18 @@
"wsagent/http": {
"port": "4401/tcp",
"protocol": "http",
"path" : "/api"
"path" : "/api",
"attributes": {
"secure": "true"
}
},
"wsagent/ws": {
"port": "4401/tcp",
"protocol": "ws",
"path" : "/wsagent"
"path" : "/wsagent",
"attributes": {
"secure": "true"
}
}
}
}

View File

@ -12,12 +12,18 @@
"wsagent/http": {
"port": "4401/tcp",
"protocol": "http",
"path" : "/api"
"path" : "/api",
"attributes": {
"secure": "true"
}
},
"wsagent/ws": {
"port": "4401/tcp",
"protocol": "ws",
"path" : "/wsagent"
"path" : "/wsagent",
"attributes": {
"secure": "true"
}
},
"wsagent-debug": {
"port": "4403/tcp",

View File

@ -12,12 +12,18 @@
"wsagent/http": {
"port": "4401/tcp",
"protocol": "http",
"path" : "/api"
"path" : "/api",
"attributes": {
"secure": "true"
}
},
"wsagent/ws": {
"port": "4401/tcp",
"protocol": "ws",
"path" : "/wsagent"
"path" : "/wsagent",
"attributes": {
"secure": "true"
}
},
"wsagent-debug": {
"port": "4403/tcp",

View File

@ -9,12 +9,18 @@
"wsagent/http": {
"port": "4401/tcp",
"protocol": "http",
"path" : "/api"
"path" : "/api",
"attributes": {
"secure": "true"
}
},
"wsagent/ws": {
"port": "4401/tcp",
"protocol": "ws",
"path" : "/wsagent"
"path" : "/wsagent",
"attributes": {
"secure": "true"
}
},
"wsagent-debug": {
"port": "4403/tcp",