diff --git a/agents/exec/installer/src/main/resources/installers/1.0.0/org.eclipse.che.exec.json b/agents/exec/installer/src/main/resources/installers/1.0.0/org.eclipse.che.exec.json index 26373e4495e..fa71a8925d6 100644 --- a/agents/exec/installer/src/main/resources/installers/1.0.0/org.eclipse.che.exec.json +++ b/agents/exec/installer/src/main/resources/installers/1.0.0/org.eclipse.che.exec.json @@ -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" + } } } } diff --git a/agents/exec/installer/src/main/resources/installers/1.0.1/org.eclipse.che.exec.json b/agents/exec/installer/src/main/resources/installers/1.0.1/org.eclipse.che.exec.json index 8f7e3e03844..e08d9f35ea9 100644 --- a/agents/exec/installer/src/main/resources/installers/1.0.1/org.eclipse.che.exec.json +++ b/agents/exec/installer/src/main/resources/installers/1.0.1/org.eclipse.che.exec.json @@ -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" + } } } } diff --git a/agents/terminal/src/main/resources/installers/1.0.0/org.eclipse.che.terminal.json b/agents/terminal/src/main/resources/installers/1.0.0/org.eclipse.che.terminal.json index 120f212ee99..58fdadfd519 100644 --- a/agents/terminal/src/main/resources/installers/1.0.0/org.eclipse.che.terminal.json +++ b/agents/terminal/src/main/resources/installers/1.0.0/org.eclipse.che.terminal.json @@ -9,7 +9,10 @@ "terminal": { "port": "4411/tcp", "protocol": "ws", - "path" : "/pty" + "path" : "/pty", + "attributes": { + "secure": "true" + } } } } diff --git a/agents/terminal/src/main/resources/installers/1.0.1/org.eclipse.che.terminal.json b/agents/terminal/src/main/resources/installers/1.0.1/org.eclipse.che.terminal.json index 0890cb3961d..cfd12111fc9 100644 --- a/agents/terminal/src/main/resources/installers/1.0.1/org.eclipse.che.terminal.json +++ b/agents/terminal/src/main/resources/installers/1.0.1/org.eclipse.che.terminal.json @@ -9,7 +9,10 @@ "terminal": { "port": "4411/tcp", "protocol": "ws", - "path" : "/pty" + "path" : "/pty", + "attributes": { + "secure": "true" + } } } } diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 51816422dd2..189aa6be3cc 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -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 diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ServerConfig.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ServerConfig.java index 732322a7e4f..a53b4a4191e 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ServerConfig.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/config/ServerConfig.java @@ -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. * diff --git a/infrastructures/kubernetes/pom.xml b/infrastructures/kubernetes/pom.xml index 30a2ffed8d6..a0082ace96b 100644 --- a/infrastructures/kubernetes/pom.xml +++ b/infrastructures/kubernetes/pom.xml @@ -122,6 +122,10 @@ org.eclipse.che.infrastructure.docker docker-environment + + org.eclipse.che.multiuser + che-multiuser-machine-authentication + org.eclipse.persistence javax.persistence @@ -215,6 +219,15 @@ + + com.mycila + license-maven-plugin + + + src/test/resources/jwtproxy-confg.yaml + + + diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java index 66d2e35edd4..da8a4845d04 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java @@ -51,6 +51,10 @@ 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 @@ protected void configure() { 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 @@ protected void configure() { MapBinder wsNext = MapBinder.newMapBinder(binder(), String.class, WorkspaceNextApplier.class); wsNext.addBinding(KubernetesEnvironment.TYPE).to(KubernetesWorkspaceNextApplier.class); + + bind(new TypeLiteral>() {}) + .toProvider( + new TypeLiteral>() {}); + + MapBinder> + secureServerExposerFactories = + MapBinder.newMapBinder( + binder(), + new TypeLiteral() {}, + new TypeLiteral>() {}); + + secureServerExposerFactories + .addBinding("default") + .to(new TypeLiteral>() {}); + + install( + new FactoryModuleBuilder() + .build( + new TypeLiteral>() {})); + secureServerExposerFactories + .addBinding("jwtproxy") + .to(new TypeLiteral>() {}); } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntime.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntime.java index 9756eac4c32..3e9d337ba18 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntime.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntime.java @@ -17,6 +17,7 @@ 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 @@ protected void startMachines() throws InfrastructureException { namespace.secrets().create(secret); } + for (ConfigMap configMap : k8sEnv.getConfigMaps().values()) { + namespace.configMaps().create(configMap); + } + List createdServices = new ArrayList<>(); for (Service service : k8sEnv.getServices().values()) { createdServices.add(namespace.services().create(service)); diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironment.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironment.java index f0c6ac61501..cba6be0b7f9 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironment.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironment.java @@ -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 ingresses; private final Map persistentVolumeClaims; private final Map secrets; + private final Map configMaps; public KubernetesEnvironment(KubernetesEnvironment k8sEnv) { this( @@ -48,7 +50,8 @@ public KubernetesEnvironment(KubernetesEnvironment k8sEnv) { k8sEnv.getServices(), k8sEnv.getIngresses(), k8sEnv.getPersistentVolumeClaims(), - k8sEnv.getSecrets()); + k8sEnv.getSecrets(), + k8sEnv.getConfigMaps()); } public static Builder builder() { @@ -63,13 +66,15 @@ protected KubernetesEnvironment( Map services, Map ingresses, Map persistentVolumeClaims, - Map secrets) { + Map secrets, + Map 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 Map getSecrets() { return secrets; } + /** Returns config maps that should be created when environment starts. */ + public Map getConfigMaps() { + return configMaps; + } + public static class Builder { protected InternalRecipe internalRecipe; protected final Map machines = new HashMap<>(); @@ -106,6 +116,7 @@ public static class Builder { protected final Map ingresses = new HashMap<>(); protected final Map pvcs = new HashMap<>(); protected final Map secrets = new HashMap<>(); + protected final Map configMaps = new HashMap<>(); protected Builder() {} @@ -149,9 +160,14 @@ public Builder setSecrets(Map secrets) { return this; } + public Builder setConfigMaps(Map 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); } } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactory.java index c397fe534f4..534c5b1d152 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactory.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactory.java @@ -15,6 +15,7 @@ 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 @@ protected KubernetesEnvironment doCreate( 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 @@ protected KubernetesEnvironment doCreate( 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 @@ protected KubernetesEnvironment doCreate( 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 @@ protected KubernetesEnvironment doCreate( .setIngresses(new HashMap<>()) .setPersistentVolumeClaims(new HashMap<>()) .setSecrets(new HashMap<>()) + .setConfigMaps(new HashMap<>()) .build(); envValidator.validate(k8sEnv); diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesConfigsMaps.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesConfigsMaps.java new file mode 100644 index 00000000000..0c03a6f2c50 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesConfigsMaps.java @@ -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); + } + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespace.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespace.java index 5d651eeb093..e51d86d053e 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespace.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespace.java @@ -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 @@ protected 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 @@ protected KubernetesNamespace( this.pvcs = pvcs; this.ingresses = kubernetesIngresses; this.secrets = secrets; + this.configMaps = configMaps; } public KubernetesNamespace( @@ -95,6 +99,7 @@ public 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 KubernetesSecrets secrets() { 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); } /** diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecrets.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecrets.java index 3cbbde52510..d745bf92b4a 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecrets.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesSecrets.java @@ -20,8 +20,8 @@ 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 */ diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java index c9799175dfd..57fcaa4aa60 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java @@ -25,6 +25,8 @@ 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 @@ public class ServersConverter implements ConfigurationProvisioner { + private final SecureServerExposerFactory secureServerExposerFactory; private final ExternalServerExposerStrategy externalServerExposerStrategy; @Inject - public ServersConverter(ExternalServerExposerStrategy externalServerExposerStrategy) { + public ServersConverter( + SecureServerExposerFactory secureServerExposerFactory, + ExternalServerExposerStrategy externalServerExposerStrategy) { + this.secureServerExposerFactory = secureServerExposerFactory; this.externalServerExposerStrategy = externalServerExposerStrategy; } @Override public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureException { + SecureServerExposer secureServerExposer = secureServerExposerFactory.create(identity); for (Pod podConfig : k8sEnv.getPods().values()) { final PodSpec podSpec = podConfig.getSpec(); @@ -57,7 +64,12 @@ public void provision(T k8sEnv, RuntimeIdentity identity) throws InfrastructureE if (!machineConfig.getServers().isEmpty()) { KubernetesServerExposer kubernetesServerExposer = new KubernetesServerExposer<>( - externalServerExposerStrategy, machineName, podConfig, containerConfig, k8sEnv); + externalServerExposerStrategy, + secureServerExposer, + machineName, + podConfig, + containerConfig, + k8sEnv); kubernetesServerExposer.expose(machineConfig.getServers()); } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java index be1769c5ee4..e0bea4a59f0 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java @@ -39,6 +39,7 @@ 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 { public static final String SERVER_PREFIX = "server"; private final ExternalServerExposerStrategy externalServerExposer; + private final SecureServerExposer secureServerExposer; private final String machineName; private final Container container; private final Pod pod; @@ -109,11 +111,13 @@ public class KubernetesServerExposer { public KubernetesServerExposer( ExternalServerExposerStrategy externalServerExposer, + SecureServerExposer 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 KubernetesServerExposer( public void expose(Map servers) throws InfrastructureException { Map internalServers = new HashMap<>(); Map externalServers = new HashMap<>(); + Map 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 void expose(Map servers) throws Infrastru externalServerExposer.expose( k8sEnv, machineName, serviceName, servicePort, matchedExternalServers); } + + // expose service port related secure servers if exist + Map matchedSecureServers = match(secureServers, servicePort); + if (!matchedSecureServers.isEmpty()) { + secureServerExposer.expose( + k8sEnv, machineName, serviceName, servicePort, matchedSecureServers); + } } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/DefaultSecureServersFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/DefaultSecureServersFactory.java new file mode 100644 index 00000000000..7d815c1e6dc --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/DefaultSecureServersFactory.java @@ -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 + implements SecureServerExposerFactory { + private final ExternalServerExposerStrategy exposerStrategy; + + @Inject + public DefaultSecureServersFactory(ExternalServerExposerStrategy exposerStrategy) { + this.exposerStrategy = exposerStrategy; + } + + @Override + public SecureServerExposer create(RuntimeIdentity runtimeId) { + return new DefaultSecureServerExposer(); + } + + private class DefaultSecureServerExposer implements SecureServerExposer { + @Override + public void expose( + T k8sEnv, + String machineName, + String serviceName, + ServicePort servicePort, + Map secureServers) { + exposerStrategy.expose(k8sEnv, machineName, serviceName, servicePort, secureServers); + } + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposer.java new file mode 100644 index 00000000000..67b1ce058d5 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposer.java @@ -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. + * + *

Note that ONE {@link SecureServerExposer} instance should be used for one workspace start. + * + * @author Sergii Leshchenko + */ +public interface SecureServerExposer { + + /** + * 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 secureServers) + throws InfrastructureException; +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactory.java new file mode 100644 index 00000000000..72e2eee9738 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactory.java @@ -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. + * + *

Note that ONE {@link SecureServerExposer} instance should be used for one workspace start. + * + * @author Sergii Leshchenko + */ +public interface SecureServerExposerFactory { + SecureServerExposer create(RuntimeIdentity runtimeId); +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProvider.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProvider.java new file mode 100644 index 00000000000..a50796d304f --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProvider.java @@ -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 + implements Provider> { + + private final boolean agentsAuthEnabled; + private final String serverExposer; + + private final DefaultSecureServersFactory defaultSecureServersFactory; + private final Map> factories; + + @Inject + public SecureServerExposerFactoryProvider( + @Named("che.agents.auth_enabled") boolean agentsAuthEnabled, + @Named("che.server.secure_exposer") String serverExposer, + DefaultSecureServersFactory defaultSecureServersFactory, + Map> 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 get() { + if (!agentsAuthEnabled) { + // return default secure server exposer because no need to protect servers with authentication + return defaultSecureServersFactory; + } + + SecureServerExposerFactory serverExposerFactory = factories.get(serverExposer); + if (serverExposerFactory == null) { + throw new ConfigurationException( + "Unknown secure servers exposer is configured '" + serverExposer + "'. "); + } + + return serverExposerFactory; + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilder.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilder.java new file mode 100644 index 00000000000..d580bd88de3 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilder.java @@ -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 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; + } + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisioner.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisioner.java new file mode 100644 index 00000000000..3a4f5e9b7d0 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisioner.java @@ -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. + * + *

Exposing includes the following operation: + * + *

    + *
  • Putting Machine configuration into Kubernetes environment if absent; + *
  • Putting JwtProxy pod with one container if absent; + *
  • Putting JwtProxy service that will expose added JWTProxy pod if absent; + *
  • Putting JwtProxy ConfigMap that contains public key and jwtproxy config in yaml format if + * absent; + *
  • Updating JwtProxy Service to expose port for secure server; + *
  • Updating jwtproxy configuration in config map by adding the corresponding verifier proxy + * there; + *
+ * + * @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 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(); + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposer.java new file mode 100644 index 00000000000..3c7aa75d798 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposer.java @@ -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. + * + *

To expose secure servers it provisions JwtProxy objects into environment with {@link + * JwtProxyProvisioner}. Then JwtProxy service port is made public accessible by {@link + * ExternalServerExposerStrategy}. + * + *

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 + implements SecureServerExposer { + + private final ExternalServerExposerStrategy exposerStrategy; + private final JwtProxyProvisioner proxyProvisioner; + + @VisibleForTesting + JwtProxySecureServerExposer( + JwtProxyProvisioner jwtProxyProvisioner, ExternalServerExposerStrategy exposerStrategy) { + this.exposerStrategy = exposerStrategy; + this.proxyProvisioner = jwtProxyProvisioner; + } + + @Inject + public JwtProxySecureServerExposer( + @Assisted RuntimeIdentity identity, + SignatureKeyManager signatureKeyManager, + ExternalServerExposerStrategy exposerStrategy) { + this.exposerStrategy = exposerStrategy; + + proxyProvisioner = new JwtProxyProvisioner(identity, signatureKeyManager); + } + + @Override + public void expose( + T k8sEnv, + String machineName, + String serviceName, + ServicePort servicePort, + Map secureServers) + throws InfrastructureException { + ServicePort exposedServicePort = + proxyProvisioner.expose( + k8sEnv, + serviceName, + servicePort.getTargetPort().getIntVal(), + servicePort.getProtocol()); + + exposerStrategy.expose( + k8sEnv, machineName, proxyProvisioner.getServiceName(), exposedServicePort, secureServers); + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerFactory.java new file mode 100644 index 00000000000..47d45ac15ac --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerFactory.java @@ -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 + extends SecureServerExposerFactory { + @Override + JwtProxySecureServerExposer create(RuntimeIdentity identity); +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntimeTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntimeTest.java index eb049a85fa9..f85428cfcc1 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntimeTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInternalRuntimeTest.java @@ -41,6 +41,7 @@ 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.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 void setup() throws Exception { 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 void setup() throws Exception { @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()); diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactoryTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactoryTest.java index 3e2576cd163..f0597212ff5 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactoryTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/environment/KubernetesEnvironmentFactoryTest.java @@ -18,6 +18,8 @@ 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 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 void ignoreSecretsWhenRecipeContainsThem() throws Exception { 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 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; diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceTest.java index 6336717f621..c6c80ad9f83 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceTest.java @@ -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 void setUp() throws Exception { services, pvcs, ingresses, - secrets); + secrets, + configMaps); } @Test @@ -163,6 +165,7 @@ public void testKubernetesNamespaceCleaningUp() throws Exception { verify(services).delete(); verify(deployments).delete(); verify(secrets).delete(); + verify(configMaps).delete(); } @Test diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java index 9fcefa3f9fb..a68b79558d6 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java @@ -37,6 +37,7 @@ 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 @@ public class KubernetesServerExposerTest { @Mock private ExternalServerExposerStrategy externalServerExposerStrategy; + @Mock private SecureServerExposer secureServerExposer; private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final Map INTERNAL_SERVER_ATTRIBUTE_MAP = singletonMap(ServerConfig.INTERNAL_SERVER_ATTRIBUTE, Boolean.TRUE.toString()); + private static final Map 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 void setUp() throws Exception { 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 void shouldExposeContainerPortAndCreateServiceForInternalServer() throws } @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 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 @@ private void assertThatExternalServersAreExposed( Integer port, Map 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 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 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 @@ private void assertThatExternalServersAreExposed( 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 @@ private void assertThatInternalServerIsExposed( 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 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 @@ private void assertThatInternalServerIsExposed( p -> p.getContainerPort().equals(port) && p.getProtocol().equals(portProtocol.toUpperCase()))); - // ensure that service is created + } + private Service findContainerRelatedService() { Service service = null; for (Entry entry : kubernetesEnvironment.getServices().entrySet()) { if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) { @@ -385,9 +425,10 @@ private void assertThatInternalServerIsExposed( break; } } - assertNotNull(service); + return service; + } - // ensure that required service port is exposed + private ServicePort assertThatServicePortIsExposed(Integer port, Service service) { Optional servicePortOpt = service .getSpec() @@ -400,13 +441,6 @@ private void assertThatInternalServerIsExposed( 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 servers = serviceAnnotations.servers(); - ServerConfig serverConfig = servers.get(serverNameRegex); - assertEquals(serverConfig, expected); + return servicePort; } } diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProviderTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProviderTest.java new file mode 100644 index 00000000000..f9e494d3595 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/SecureServerExposerFactoryProviderTest.java @@ -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 defaultSecureServersFactory; + + @Mock private SecureServerExposerFactory customSecureServerExposer; + + private Map> factories = + new HashMap<>(); + + @Test + public void shouldReturnDefaultSecureServerExposerWhenAgentAuthIsDisabled() { + // given + SecureServerExposerFactoryProvider factoryProvider = + new SecureServerExposerFactoryProvider<>( + false, "custom", defaultSecureServersFactory, factories); + + // when + SecureServerExposerFactory factory = factoryProvider.get(); + + // then + assertSame(factory, defaultSecureServersFactory); + } + + @Test + public void shouldReturnConfiguredSecureServerExposerWhenAgentAuthIsEnabled() { + // given + factories.put("custom", customSecureServerExposer); + SecureServerExposerFactoryProvider factoryProvider = + new SecureServerExposerFactoryProvider<>( + true, "custom", defaultSecureServersFactory, factories); + + // when + SecureServerExposerFactory factory = factoryProvider.get(); + + // then + assertSame(factory, customSecureServerExposer); + } + + @Test(expectedExceptions = ConfigurationException.class) + public void shouldThrowAnExceptionIfConfiguredSecureServerWasNotFound() { + // given + SecureServerExposerFactoryProvider factoryProvider = + new SecureServerExposerFactoryProvider<>( + true, "non-existing", defaultSecureServersFactory, factories); + + // when + factoryProvider.get(); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilderTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilderTest.java new file mode 100644 index 00000000000..0334798a41f --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyConfigBuilderTest.java @@ -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"))); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisionerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisionerTest.java new file mode 100644 index 00000000000..405caa4edc5 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxyProvisionerTest.java @@ -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); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerTest.java new file mode 100644 index 00000000000..ba19af057a9 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/secure/jwtproxy/JwtProxySecureServerExposerTest.java @@ -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 externalServerExposer; + + private JwtProxySecureServerExposer 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 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); + } +} diff --git a/infrastructures/kubernetes/src/test/resources/jwtproxy-confg.yaml b/infrastructures/kubernetes/src/test/resources/jwtproxy-confg.yaml new file mode 100644 index 00000000000..a6f83c5c240 --- /dev/null +++ b/infrastructures/kubernetes/src/test/resources/jwtproxy-confg.yaml @@ -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 diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java index 99bdc580c9d..4fc8a6d23eb 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java @@ -42,6 +42,10 @@ 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 @@ protected void configure() { MapBinder wsNext = MapBinder.newMapBinder(binder(), String.class, WorkspaceNextApplier.class); wsNext.addBinding(OpenShiftEnvironment.TYPE).to(KubernetesWorkspaceNextApplier.class); + + bind(new TypeLiteral>() {}) + .toProvider(new TypeLiteral>() {}); + + MapBinder> + secureServerExposerFactories = + MapBinder.newMapBinder( + binder(), + new TypeLiteral() {}, + new TypeLiteral>() {}); + + secureServerExposerFactories + .addBinding("default") + .to(new TypeLiteral>() {}); + + install( + new FactoryModuleBuilder() + .build(new TypeLiteral>() {})); + secureServerExposerFactories + .addBinding("jwtproxy") + .to(new TypeLiteral>() {}); } } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java index bec9c6390f3..13a3f8d627c 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntime.java @@ -12,6 +12,7 @@ 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 @@ protected void startMachines() throws InfrastructureException { project.secrets().create(secret); } + for (ConfigMap configMap : osEnv.getConfigMaps().values()) { + project.configMaps().create(configMap); + } + List createdServices = new ArrayList<>(); for (Service service : osEnv.getServices().values()) { createdServices.add(project.services().create(service)); diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironment.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironment.java index 610cc110d48..2d485fcfdbb 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironment.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironment.java @@ -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 OpenShiftEnvironment( Map ingresses, Map pvcs, Map secrets, + Map configMaps, Map 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 Builder setSecrets(Map secrets) { return this; } + @Override + public Builder setConfigMaps(Map configMaps) { + this.configMaps.putAll(configMaps); + return this; + } + public Builder setRoutes(Map route) { this.routes.putAll(route); return this; @@ -124,7 +132,16 @@ public Builder setRoutes(Map route) { 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); } } } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactory.java index 30b04899174..316f4249aa7 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactory.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactory.java @@ -15,6 +15,7 @@ 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()) .setSecrets(new HashMap<>()) + .setConfigMaps(new HashMap<>()) .setRoutes(new HashMap<>()) .build(); diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProject.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProject.java index 11ec7bf9ea1..0a514420256 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProject.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProject.java @@ -18,6 +18,7 @@ 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 OpenShiftRoutes routes() { /** 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) diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntimeTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntimeTest.java index 1908a0f3b9b..37a4a74bc9f 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntimeTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInternalRuntimeTest.java @@ -29,6 +29,7 @@ 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.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 void setup() throws Exception { 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 void setup() throws Exception { 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 void shouldStartMachines() throws Exception { 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()); diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactoryTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactoryTest.java index cec7268221c..035b44db985 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactoryTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftEnvironmentFactoryTest.java @@ -18,6 +18,8 @@ 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 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.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 void ignorePVCsWhenRecipeContainsThem() throws Exception { 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 void ignoreSecretsWhenRecipeContainsThem() throws Exception { 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 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; diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectTest.java index e0165658ef2..14762c09cb8 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectTest.java @@ -32,6 +32,7 @@ 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 void setUp() throws Exception { routes, pvcs, ingresses, - secrets); + secrets, + configsMaps); } @Test @@ -133,6 +136,7 @@ public void testOpenShiftProjectCleaningUp() throws Exception { verify(services).delete(); verify(deployments).delete(); verify(secrets).delete(); + verify(configsMaps).delete(); } @Test diff --git a/wsagent/agent/src/main/resources/installers/1.0.0/org.eclipse.che.ws-agent.json b/wsagent/agent/src/main/resources/installers/1.0.0/org.eclipse.che.ws-agent.json index 92fdd63013f..2df0eecf510 100644 --- a/wsagent/agent/src/main/resources/installers/1.0.0/org.eclipse.che.ws-agent.json +++ b/wsagent/agent/src/main/resources/installers/1.0.0/org.eclipse.che.ws-agent.json @@ -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" + } } } } diff --git a/wsagent/agent/src/main/resources/installers/1.0.1/org.eclipse.che.ws-agent.json b/wsagent/agent/src/main/resources/installers/1.0.1/org.eclipse.che.ws-agent.json index 2b1398b50ca..5b5d33f0a86 100644 --- a/wsagent/agent/src/main/resources/installers/1.0.1/org.eclipse.che.ws-agent.json +++ b/wsagent/agent/src/main/resources/installers/1.0.1/org.eclipse.che.ws-agent.json @@ -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", diff --git a/wsagent/agent/src/main/resources/installers/1.0.2/org.eclipse.che.ws-agent.json b/wsagent/agent/src/main/resources/installers/1.0.2/org.eclipse.che.ws-agent.json index 8ee354599ba..b48e5cd0c07 100644 --- a/wsagent/agent/src/main/resources/installers/1.0.2/org.eclipse.che.ws-agent.json +++ b/wsagent/agent/src/main/resources/installers/1.0.2/org.eclipse.che.ws-agent.json @@ -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", diff --git a/wsagent/agent/src/main/resources/installers/1.0.3/org.eclipse.che.ws-agent.json b/wsagent/agent/src/main/resources/installers/1.0.3/org.eclipse.che.ws-agent.json index 2d40a32df9e..f4cac844b90 100644 --- a/wsagent/agent/src/main/resources/installers/1.0.3/org.eclipse.che.ws-agent.json +++ b/wsagent/agent/src/main/resources/installers/1.0.3/org.eclipse.che.ws-agent.json @@ -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",