feat: native auth on Kubernetes (#171)

Signed-off-by: Michal Vala <mvala@redhat.com>
pull/186/head^2
Michal Vala 2021-11-25 14:48:55 +01:00 committed by GitHub
parent f289ec619f
commit 388a5183be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 2128 additions and 998 deletions

View File

@ -35,6 +35,10 @@
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
@ -275,6 +279,10 @@
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication</artifactId> <artifactId>che-multiuser-machine-authentication</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-oidc</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-devfile</artifactId> <artifactId>che-multiuser-permission-devfile</artifactId>
@ -454,6 +462,7 @@
<dep>com.google.guava:guava</dep> <dep>com.google.guava:guava</dep>
<dep>org.everrest:everrest-core</dep> <dep>org.everrest:everrest-core</dep>
<dep>io.jsonwebtoken:jjwt-jackson</dep> <dep>io.jsonwebtoken:jjwt-jackson</dep>
<dep>io.jsonwebtoken:jjwt-impl</dep>
</ignoredUnusedDeclaredDependencies> </ignoredUnusedDeclaredDependencies>
</configuration> </configuration>
</execution> </execution>

View File

@ -15,6 +15,7 @@ import static com.google.inject.matcher.Matchers.subclassesOf;
import static org.eclipse.che.inject.Matchers.names; import static org.eclipse.che.inject.Matchers.names;
import static org.eclipse.che.multiuser.api.permission.server.SystemDomain.SYSTEM_DOMAIN_ACTIONS; import static org.eclipse.che.multiuser.api.permission.server.SystemDomain.SYSTEM_DOMAIN_ACTIONS;
import com.auth0.jwk.JwkProvider;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.assistedinject.FactoryModuleBuilder;
@ -22,7 +23,7 @@ import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.impl.DefaultJwtParser; import io.jsonwebtoken.SigningKeyResolver;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -81,7 +82,6 @@ import org.eclipse.che.commons.observability.deploy.ExecutorWrapperModule;
import org.eclipse.che.core.db.DBTermination; import org.eclipse.che.core.db.DBTermination;
import org.eclipse.che.core.db.schema.SchemaInitializer; import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.eclipse.che.core.tracing.metrics.TracingMetricsModule; import org.eclipse.che.core.tracing.metrics.TracingMetricsModule;
import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.inject.DynaModule; import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.api.authentication.commons.token.ChainedTokenExtractor; import org.eclipse.che.multiuser.api.authentication.commons.token.ChainedTokenExtractor;
import org.eclipse.che.multiuser.api.authentication.commons.token.HeaderRequestTokenExtractor; import org.eclipse.che.multiuser.api.authentication.commons.token.HeaderRequestTokenExtractor;
@ -93,6 +93,11 @@ import org.eclipse.che.multiuser.api.workspace.activity.MultiUserWorkspaceActivi
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakModule; import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakModule;
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakUserRemoverModule; import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakUserRemoverModule;
import org.eclipse.che.multiuser.machine.authentication.server.MachineAuthModule; import org.eclipse.che.multiuser.machine.authentication.server.MachineAuthModule;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.eclipse.che.multiuser.oidc.OIDCInfoProvider;
import org.eclipse.che.multiuser.oidc.OIDCJwkProvider;
import org.eclipse.che.multiuser.oidc.OIDCJwtParserProvider;
import org.eclipse.che.multiuser.oidc.OIDCSigningKeyResolver;
import org.eclipse.che.multiuser.organization.api.OrganizationApiModule; import org.eclipse.che.multiuser.organization.api.OrganizationApiModule;
import org.eclipse.che.multiuser.organization.api.OrganizationJpaModule; import org.eclipse.che.multiuser.organization.api.OrganizationJpaModule;
import org.eclipse.che.multiuser.permission.user.UserServicePermissionsFilter; import org.eclipse.che.multiuser.permission.user.UserServicePermissionsFilter;
@ -335,18 +340,10 @@ public class WsMasterModule extends AbstractModule {
.to(org.eclipse.che.api.workspace.server.DefaultWorkspaceStatusCache.class); .to(org.eclipse.che.api.workspace.server.DefaultWorkspaceStatusCache.class);
} }
if (OpenShiftInfrastructure.NAME.equals(infrastructure)) { if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) { bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class);
bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class); } else if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
} else { bind(KubernetesClientConfigFactory.class).to(KeycloakProviderConfigFactory.class);
bind(KubernetesClientConfigFactory.class).to(KeycloakProviderConfigFactory.class);
}
}
if (KubernetesInfrastructure.NAME.equals(infrastructure)
&& Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
throw new ConfigurationException(
"Native user mode is not supported on Kubernetes. It is supported only on OpenShift.");
} }
persistenceProperties.put( persistenceProperties.put(
@ -395,11 +392,16 @@ public class WsMasterModule extends AbstractModule {
install(new OrganizationJpaModule()); install(new OrganizationJpaModule());
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) { if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class);
if (KubernetesInfrastructure.NAME.equals(infrastructure)) {
bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton();
bind(SigningKeyResolver.class).to(OIDCSigningKeyResolver.class);
bind(JwtParser.class).toProvider(OIDCJwtParserProvider.class);
bind(JwkProvider.class).toProvider(OIDCJwkProvider.class);
}
bind(TokenValidator.class).to(NotImplementedTokenValidator.class); bind(TokenValidator.class).to(NotImplementedTokenValidator.class);
bind(JwtParser.class).to(DefaultJwtParser.class);
bind(ProfileDao.class).to(JpaProfileDao.class); bind(ProfileDao.class).to(JpaProfileDao.class);
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class); bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class);
bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class);
} else { } else {
install(new KeycloakModule()); install(new KeycloakModule());
install(new KeycloakUserRemoverModule()); install(new KeycloakUserRemoverModule());

View File

@ -19,6 +19,8 @@ import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.inject.DynaModule; import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule; import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule;
import org.eclipse.che.multiuser.machine.authentication.server.MachineLoginFilter; import org.eclipse.che.multiuser.machine.authentication.server.MachineLoginFilter;
import org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructure;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfrastructure; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftInfrastructure;
import org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth.OpenshiftTokenInitializationFilter; import org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth.OpenshiftTokenInitializationFilter;
import org.everrest.guice.servlet.GuiceEverrestServlet; import org.everrest.guice.servlet.GuiceEverrestServlet;
@ -78,6 +80,8 @@ public class WsMasterServletModule extends ServletModule {
final String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE"); final String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE");
if (OpenShiftInfrastructure.NAME.equals(infrastructure)) { if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
filter("/*").through(OpenshiftTokenInitializationFilter.class); filter("/*").through(OpenshiftTokenInitializationFilter.class);
} else if (KubernetesInfrastructure.NAME.equals(infrastructure)) {
filter("/*").through(OidcTokenInitializationFilter.class);
} else { } else {
throw new ConfigurationException("Native user mode is currently supported on on OpenShift."); throw new ConfigurationException("Native user mode is currently supported on on OpenShift.");
} }

View File

@ -342,6 +342,9 @@ che.infra.kubernetes.service_account_name=NULL
# This property deprecates `che.infra.kubernetes.cluster_role_name`. # This property deprecates `che.infra.kubernetes.cluster_role_name`.
che.infra.kubernetes.workspace_sa_cluster_roles=NULL che.infra.kubernetes.workspace_sa_cluster_roles=NULL
# Cluster roles to assign to user in his namespace
che.infra.kubernetes.user_cluster_roles=NULL
# Defines wait time that limits the Kubernetes workspace start time. # Defines wait time that limits the Kubernetes workspace start time.
che.infra.kubernetes.workspace_start_timeout_min=8 che.infra.kubernetes.workspace_start_timeout_min=8
@ -545,12 +548,6 @@ che.infra.kubernetes.trusted_ca.mount_path=/public-certs
# See the `che.infra.kubernetes.trusted_ca.dest_configmap` property. # See the `che.infra.kubernetes.trusted_ca.dest_configmap` property.
che.infra.kubernetes.trusted_ca.dest_configmap_labels= che.infra.kubernetes.trusted_ca.dest_configmap_labels=
# Enables the `/unsupported/k8s` endpoint to resolve calls on Kubernetes infrastructure.
# Provides direct access to the underlying infrastructure REST API.
# This results in huge privilege escalation.
# It impacts only Kubernetes infrastructure. Therefore it implies no security risk on OpenShift with OAuth.
# Do not enable this, unless you understand the risks.
che.infra.kubernetes.enable_unsupported_k8s=false
### OpenShift Infra parameters ### OpenShift Infra parameters

View File

@ -93,16 +93,31 @@ che.limits.organization.workspaces.run.count=-1
# See: link:https://www.keycloak.org/docs/latest/server_admin/#openshift-4[OpenShift identity provider] # See: link:https://www.keycloak.org/docs/latest/server_admin/#openshift-4[OpenShift identity provider]
che.infra.openshift.oauth_identity_provider=NULL che.infra.openshift.oauth_identity_provider=NULL
### OIDC configuration
# Url to OIDC identity provider server
# Can be set to NULL only if `che.oidc.oidcProvider` is used
che.oidc.auth_server_url=http://${CHE_HOST}:5050/auth
# Internal network service Url to OIDC identity provider server
che.oidc.auth_internal_server_url=NULL
# The number of seconds to tolerate for clock skew when verifying `exp` or `nbf` claims.
che.oidc.allowed_clock_skew_sec=3
# Username claim to be used as user display name when parsing JWT token
# if not defined the fallback value is 'preferred_username' in Keycloak installations and
# `name` in Dex installations.
che.oidc.username_claim=NULL
# Base URL of an alternate OIDC provider that provides
# a discovery endpoint as detailed in the following specification
# link:https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Obtaining OpenID Provider Configuration Information]
# Deprecated, use `che.oidc.auth_server_url` and `che.oidc.auth_internal_server_url` instead.
che.oidc.oidc_provider=NULL
### Keycloak configuration ### Keycloak configuration
# Url to keycloak identity provider server
# Can be set to NULL only if `che.keycloak.oidcProvider`
# is used
che.keycloak.auth_server_url=http://${CHE_HOST}:5050/auth
# Internal network service Url to keycloak identity provider server
che.keycloak.auth_internal_server_url=NULL
# Keycloak realm is used to authenticate users # Keycloak realm is used to authenticate users
# Can be set to NULL only if `che.keycloak.oidcProvider` # Can be set to NULL only if `che.keycloak.oidcProvider`
# is used # is used
@ -117,9 +132,6 @@ che.keycloak.oso.endpoint=NULL
# URL to access Github OAuth tokens # URL to access Github OAuth tokens
che.keycloak.github.endpoint=NULL che.keycloak.github.endpoint=NULL
# The number of seconds to tolerate for clock skew when verifying `exp` or `nbf` claims.
che.keycloak.allowed_clock_skew_sec=3
# Use the OIDC optional `nonce` feature to increase security. # Use the OIDC optional `nonce` feature to increase security.
che.keycloak.use_nonce=true che.keycloak.use_nonce=true
@ -130,21 +142,11 @@ che.keycloak.use_nonce=true
# if an alternate `oidc_provider` is used # if an alternate `oidc_provider` is used
che.keycloak.js_adapter_url=NULL che.keycloak.js_adapter_url=NULL
# Base URL of an alternate OIDC provider that provides
# a discovery endpoint as detailed in the following specification
# link:https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Obtaining OpenID Provider Configuration Information]
che.keycloak.oidc_provider=NULL
# Set to true when using an alternate OIDC provider that # Set to true when using an alternate OIDC provider that
# only supports fixed redirect Urls # only supports fixed redirect Urls
# This property is ignored when `che.keycloak.oidc_provider` is NULL # This property is ignored when `che.keycloak.oidc_provider` is NULL
che.keycloak.use_fixed_redirect_urls=false che.keycloak.use_fixed_redirect_urls=false
# Username claim to be used as user display name
# when parsing JWT token
# if not defined the fallback value is 'preferred_username'
che.keycloak.username_claim=NULL
# Configuration of OAuth Authentication Service that can be used in "embedded" or "delegated" mode. # Configuration of OAuth Authentication Service that can be used in "embedded" or "delegated" mode.
# If set to "embedded", then the service work as a wrapper to Che's OAuthAuthenticator ( as in Single User mode). # If set to "embedded", then the service work as a wrapper to Che's OAuthAuthenticator ( as in Single User mode).
# If set to "delegated", then the service will use Keycloak IdentityProvider mechanism. # If set to "delegated", then the service will use Keycloak IdentityProvider mechanism.

View File

@ -41,3 +41,9 @@ che.infra.kubernetes.trusted_ca.dest_configmap=che.infra.openshift.trusted_ca_bu
che.infra.kubernetes.trusted_ca.mount_path=che.infra.openshift.trusted_ca_bundles_mount_path che.infra.kubernetes.trusted_ca.mount_path=che.infra.openshift.trusted_ca_bundles_mount_path
che.infra.openshift.trusted_ca.dest_configmap_labels=che.infra.openshift.trusted_ca_bundles_config_map_labels che.infra.openshift.trusted_ca.dest_configmap_labels=che.infra.openshift.trusted_ca_bundles_config_map_labels
che.integration.bitbucket.server_endpoints=bitbucket.server.endpoints che.integration.bitbucket.server_endpoints=bitbucket.server.endpoints
che.oidc.auth_server_url=che.keycloak.auth_server_url
che.oidc.auth_internal_server_url=che.keycloak.auth_internal_server_url
che.oidc.allowed_clock_skew_sec=che.keycloak.allowed_clock_skew_sec
che.oidc.username_claim=che.keycloak.username_claim
che.oidc.oidc_provider=che.keycloak.oidc_provider

View File

@ -220,6 +220,11 @@
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>io.fabric8</groupId> <groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId> <artifactId>kubernetes-server-mock</artifactId>

View File

@ -31,6 +31,7 @@ public class CheServerKubernetesClientFactory extends KubernetesClientFactory {
@Inject @Inject
public CheServerKubernetesClientFactory( public CheServerKubernetesClientFactory(
KubernetesClientConfigFactory configBuilder,
@Nullable @Named("che.infra.kubernetes.master_url") String masterUrl, @Nullable @Named("che.infra.kubernetes.master_url") String masterUrl,
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts, @Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts,
@Named("che.infra.kubernetes.client.http.async_requests.max") int maxConcurrentRequests, @Named("che.infra.kubernetes.client.http.async_requests.max") int maxConcurrentRequests,
@ -41,6 +42,7 @@ public class CheServerKubernetesClientFactory extends KubernetesClientFactory {
int connectionPoolKeepAlive, int connectionPoolKeepAlive,
EventListener eventListener) { EventListener eventListener) {
super( super(
configBuilder,
masterUrl, masterUrl,
doTrustCerts, doTrustCerts,
maxConcurrentRequests, maxConcurrentRequests,

View File

@ -14,6 +14,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes;
import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.isNullOrEmpty;
import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty; import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty;
import io.fabric8.kubernetes.client.BaseKubernetesClient;
import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.DefaultKubernetesClient;
@ -55,10 +56,13 @@ public class KubernetesClientFactory {
* Default Kubernetes {@link Config} that will be the base configuration to create per-workspace * Default Kubernetes {@link Config} that will be the base configuration to create per-workspace
* configurations. * configurations.
*/ */
private Config defaultConfig; private final Config defaultConfig;
protected final KubernetesClientConfigFactory configBuilder;
@Inject @Inject
public KubernetesClientFactory( public KubernetesClientFactory(
KubernetesClientConfigFactory configBuilder,
@Nullable @Named("che.infra.kubernetes.master_url") String masterUrl, @Nullable @Named("che.infra.kubernetes.master_url") String masterUrl,
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts, @Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts,
@Named("che.infra.kubernetes.client.http.async_requests.max") int maxConcurrentRequests, @Named("che.infra.kubernetes.client.http.async_requests.max") int maxConcurrentRequests,
@ -68,6 +72,7 @@ public class KubernetesClientFactory {
@Named("che.infra.kubernetes.client.http.connection_pool.keep_alive_min") @Named("che.infra.kubernetes.client.http.connection_pool.keep_alive_min")
int connectionPoolKeepAlive, int connectionPoolKeepAlive,
EventListener eventListener) { EventListener eventListener) {
this.configBuilder = configBuilder;
this.defaultConfig = buildDefaultConfig(masterUrl, doTrustCerts); this.defaultConfig = buildDefaultConfig(masterUrl, doTrustCerts);
OkHttpClient temporary = HttpClientUtils.createHttpClient(defaultConfig); OkHttpClient temporary = HttpClientUtils.createHttpClient(defaultConfig);
OkHttpClient.Builder builder = temporary.newBuilder(); OkHttpClient.Builder builder = temporary.newBuilder();
@ -166,7 +171,12 @@ public class KubernetesClientFactory {
* infromation * infromation
*/ */
public OkHttpClient getAuthenticatedHttpClient() throws InfrastructureException { public OkHttpClient getAuthenticatedHttpClient() throws InfrastructureException {
return create(getDefaultConfig()).getHttpClient(); if (!configBuilder.isPersonalized()) {
throw new InfrastructureException(
"Not able to construct impersonating Kubernetes API client.");
}
// Ensure to get OkHttpClient with all necessary interceptors.
return create(buildConfig(getDefaultConfig(), null)).getHttpClient();
} }
/** /**
@ -200,7 +210,7 @@ public class KubernetesClientFactory {
*/ */
protected Config buildConfig(Config config, @Nullable String workspaceId) protected Config buildConfig(Config config, @Nullable String workspaceId)
throws InfrastructureException { throws InfrastructureException {
return config; return configBuilder.buildConfig(config, workspaceId);
} }
protected Interceptor buildKubernetesInterceptor(Config config) { protected Interceptor buildKubernetesInterceptor(Config config) {
@ -234,7 +244,7 @@ public class KubernetesClientFactory {
* authenticate with the credentials (user/password or Oauth token) contained in the {@code * authenticate with the credentials (user/password or Oauth token) contained in the {@code
* config} parameter. * config} parameter.
*/ */
private DefaultKubernetesClient create(Config config) { protected BaseKubernetesClient<?> create(Config config) {
OkHttpClient clientHttpClient = OkHttpClient clientHttpClient =
httpClient.newBuilder().authenticator(Authenticator.NONE).build(); httpClient.newBuilder().authenticator(Authenticator.NONE).build();
OkHttpClient.Builder builder = clientHttpClient.newBuilder(); OkHttpClient.Builder builder = clientHttpClient.newBuilder();

View File

@ -48,9 +48,13 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDev
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPermissionConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.WorkspaceServiceAccountConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.PerWorkspacePVCStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.PerWorkspacePVCStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy;
@ -101,8 +105,15 @@ public class KubernetesInfraModule extends AbstractModule {
workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class); workspaceAttributeValidators.addBinding().to(K8sInfraNamespaceWsAttributeValidator.class);
workspaceAttributeValidators.addBinding().to(AsyncStorageModeValidator.class); workspaceAttributeValidators.addBinding().to(AsyncStorageModeValidator.class);
// order matters here!
// We first need to grant permissions to user, only then we can run other configurators with
// user's client.
Multibinder<NamespaceConfigurator> namespaceConfigurators = Multibinder<NamespaceConfigurator> namespaceConfigurators =
Multibinder.newSetBinder(binder(), NamespaceConfigurator.class); Multibinder.newSetBinder(binder(), NamespaceConfigurator.class);
namespaceConfigurators.addBinding().to(UserPermissionConfigurator.class);
namespaceConfigurators.addBinding().to(CredentialsSecretConfigurator.class);
namespaceConfigurators.addBinding().to(PreferencesConfigMapConfigurator.class);
namespaceConfigurators.addBinding().to(WorkspaceServiceAccountConfigurator.class);
namespaceConfigurators.addBinding().to(UserProfileConfigurator.class); namespaceConfigurators.addBinding().to(UserProfileConfigurator.class);
namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class); namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class);

View File

@ -16,7 +16,6 @@ import java.net.URI;
import java.util.Optional; import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; 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.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException; import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
@ -47,8 +46,7 @@ public class KubernetesRuntimeContext<T extends KubernetesEnvironment> extends R
KubernetesRuntimeStateCache runtimeStatuses, KubernetesRuntimeStateCache runtimeStatuses,
@Assisted T kubernetesEnvironment, @Assisted T kubernetesEnvironment,
@Assisted RuntimeIdentity identity, @Assisted RuntimeIdentity identity,
@Assisted RuntimeInfrastructure infrastructure) @Assisted RuntimeInfrastructure infrastructure) {
throws ValidationException, InfrastructureException {
super(kubernetesEnvironment, identity, infrastructure); super(kubernetesEnvironment, identity, infrastructure);
this.namespaceFactory = namespaceFactory; this.namespaceFactory = namespaceFactory;
this.runtimeFactory = runtimeFactory; this.runtimeFactory = runtimeFactory;

View File

@ -145,7 +145,7 @@ public class KubernetesNamespace {
*/ */
void prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations) void prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations)
throws InfrastructureException { throws InfrastructureException {
KubernetesClient client = clientFactory.create(workspaceId); KubernetesClient client = cheSAClientFactory.create(workspaceId);
Namespace namespace = get(name, client); Namespace namespace = get(name, client);
if (namespace == null) { if (namespace == null) {

View File

@ -20,24 +20,17 @@ import static java.util.Collections.singletonList;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE; import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.METADATA_NAME_MAX_LENGTH; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.METADATA_NAME_MAX_LENGTH;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.Sets; import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.KubernetesClientException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -68,6 +61,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesCl
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -103,25 +97,23 @@ public class KubernetesNamespaceFactory {
protected final Map<String, String> namespaceLabels; protected final Map<String, String> namespaceLabels;
protected final Map<String, String> namespaceAnnotations; protected final Map<String, String> namespaceAnnotations;
private final String serviceAccountName;
private final Set<String> clusterRoleNames;
private final KubernetesClientFactory clientFactory; private final KubernetesClientFactory clientFactory;
private final KubernetesClientFactory cheClientFactory; private final KubernetesClientFactory cheClientFactory;
private final boolean namespaceCreationAllowed; private final boolean namespaceCreationAllowed;
private final UserManager userManager; private final UserManager userManager;
private final PreferenceManager preferenceManager; private final PreferenceManager preferenceManager;
protected final Set<NamespaceConfigurator> namespaceConfigurators;
protected final KubernetesSharedPool sharedPool; protected final KubernetesSharedPool sharedPool;
@Inject @Inject
public KubernetesNamespaceFactory( public KubernetesNamespaceFactory(
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
@Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames,
@Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName,
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed, @Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
@Named("che.infra.kubernetes.namespace.label") boolean labelNamespaces, @Named("che.infra.kubernetes.namespace.label") boolean labelNamespaces,
@Named("che.infra.kubernetes.namespace.annotate") boolean annotateNamespaces, @Named("che.infra.kubernetes.namespace.annotate") boolean annotateNamespaces,
@Named("che.infra.kubernetes.namespace.labels") String namespaceLabels, @Named("che.infra.kubernetes.namespace.labels") String namespaceLabels,
@Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations, @Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations,
Set<NamespaceConfigurator> namespaceConfigurators,
KubernetesClientFactory clientFactory, KubernetesClientFactory clientFactory,
CheServerKubernetesClientFactory cheClientFactory, CheServerKubernetesClientFactory cheClientFactory,
UserManager userManager, UserManager userManager,
@ -130,7 +122,6 @@ public class KubernetesNamespaceFactory {
throws ConfigurationException { throws ConfigurationException {
this.namespaceCreationAllowed = namespaceCreationAllowed; this.namespaceCreationAllowed = namespaceCreationAllowed;
this.userManager = userManager; this.userManager = userManager;
this.serviceAccountName = serviceAccountName;
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.cheClientFactory = cheClientFactory; this.cheClientFactory = cheClientFactory;
this.defaultNamespaceName = defaultNamespaceName; this.defaultNamespaceName = defaultNamespaceName;
@ -138,6 +129,7 @@ public class KubernetesNamespaceFactory {
this.sharedPool = sharedPool; this.sharedPool = sharedPool;
this.labelNamespaces = labelNamespaces; this.labelNamespaces = labelNamespaces;
this.annotateNamespaces = annotateNamespaces; this.annotateNamespaces = annotateNamespaces;
this.namespaceConfigurators = ImmutableSet.copyOf(namespaceConfigurators);
//noinspection UnstableApiUsage //noinspection UnstableApiUsage
Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("="); Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("=");
@ -162,14 +154,6 @@ public class KubernetesNamespaceFactory {
+ " The current value is: `%s`.", + " The current value is: `%s`.",
Joiner.on(" or ").join(REQUIRED_NAMESPACE_NAME_PLACEHOLDERS), defaultNamespaceName)); Joiner.on(" or ").join(REQUIRED_NAMESPACE_NAME_PLACEHOLDERS), defaultNamespaceName));
} }
if (!isNullOrEmpty(clusterRoleNames)) {
this.clusterRoleNames =
Sets.newHashSet(
Splitter.on(",").trimResults().omitEmptyStrings().split(clusterRoleNames));
} else {
this.clusterRoleNames = Collections.emptySet();
}
} }
/** /**
@ -260,7 +244,7 @@ public class KubernetesNamespaceFactory {
public Optional<KubernetesNamespaceMeta> fetchNamespace(String name) public Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
throws InfrastructureException { throws InfrastructureException {
try { try {
Namespace namespace = clientFactory.create().namespaces().withName(name).get(); Namespace namespace = cheClientFactory.create().namespaces().withName(name).get();
if (namespace == null) { if (namespace == null) {
return Optional.empty(); return Optional.empty();
} else { } else {
@ -336,8 +320,10 @@ public class KubernetesNamespaceFactory {
public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException { public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
KubernetesNamespace namespace = get(identity); KubernetesNamespace namespace = get(identity);
var subject = EnvironmentContext.getCurrent().getSubject();
NamespaceResolutionContext resolutionCtx = NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject()); new NamespaceResolutionContext(
identity.getWorkspaceId(), subject.getUserId(), subject.getUserName());
Map<String, String> namespaceAnnotationsEvaluated = Map<String, String> namespaceAnnotationsEvaluated =
evaluateAnnotationPlaceholders(resolutionCtx); evaluateAnnotationPlaceholders(resolutionCtx);
@ -346,44 +332,7 @@ public class KubernetesNamespaceFactory {
labelNamespaces ? namespaceLabels : emptyMap(), labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap()); annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
if (namespace configureNamespace(resolutionCtx, namespace.getName());
.secrets()
.get()
.stream()
.noneMatch(s -> s.getMetadata().getName().equals(CREDENTIALS_SECRET_NAME))) {
Secret secret =
new SecretBuilder()
.withType("opaque")
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.endMetadata()
.build();
clientFactory
.create()
.secrets()
.inNamespace(identity.getInfrastructureNamespace())
.create(secret);
}
if (namespace.configMaps().get(PREFERENCES_CONFIGMAP_NAME).isEmpty()) {
ConfigMap configMap =
new ConfigMapBuilder()
.withNewMetadata()
.withName(PREFERENCES_CONFIGMAP_NAME)
.endMetadata()
.build();
clientFactory
.create()
.configMaps()
.inNamespace(identity.getInfrastructureNamespace())
.create(configMap);
}
if (!isNullOrEmpty(serviceAccountName)) {
KubernetesWorkspaceServiceAccount workspaceServiceAccount =
doCreateServiceAccount(namespace.getWorkspaceId(), namespace.getName());
workspaceServiceAccount.prepare();
}
return namespace; return namespace;
} }
@ -590,7 +539,7 @@ public class KubernetesNamespaceFactory {
NamespaceResolutionContext namespaceCtx) throws InfrastructureException { NamespaceResolutionContext namespaceCtx) throws InfrastructureException {
try { try {
List<Namespace> workspaceNamespaces = List<Namespace> workspaceNamespaces =
clientFactory.create().namespaces().withLabels(namespaceLabels).list().getItems(); cheClientFactory.create().namespaces().withLabels(namespaceLabels).list().getItems();
if (!workspaceNamespaces.isEmpty()) { if (!workspaceNamespaces.isEmpty()) {
Map<String, String> evaluatedAnnotations = evaluateAnnotationPlaceholders(namespaceCtx); Map<String, String> evaluatedAnnotations = evaluateAnnotationPlaceholders(namespaceCtx);
return workspaceNamespaces return workspaceNamespaces
@ -617,6 +566,14 @@ public class KubernetesNamespaceFactory {
} }
} }
protected void configureNamespace(
NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
for (NamespaceConfigurator configurator : namespaceConfigurators) {
configurator.configure(namespaceResolutionContext, namespaceName);
}
}
/** /**
* Evaluate placeholder in `che.infra.kubernetes.namespace.annotations` property with given {@link * Evaluate placeholder in `che.infra.kubernetes.namespace.annotations` property with given {@link
* NamespaceResolutionContext}. * NamespaceResolutionContext}.
@ -657,31 +614,6 @@ public class KubernetesNamespaceFactory {
} }
} }
protected boolean checkNamespaceExists(String namespaceName) throws InfrastructureException {
try {
return clientFactory.create().namespaces().withName(namespaceName).get() != null;
} catch (KubernetesClientException e) {
if (e.getCode() == 403) {
// 403 means that the project does not exist
// or a user really is not permitted to access it which is Che Server misconfiguration
return false;
} else {
throw new InfrastructureException(
format(
"Error occurred while trying to fetch the namespace '%s'. Cause: %s",
namespaceName, e.getMessage()),
e);
}
}
}
protected String evalPlaceholders(String namespace, Subject currentUser, String workspaceId) {
return evalPlaceholders(
namespace,
new NamespaceResolutionContext(
workspaceId, currentUser.getUserId(), currentUser.getUserName()));
}
protected String evalPlaceholders(String namespace, NamespaceResolutionContext ctx) { protected String evalPlaceholders(String namespace, NamespaceResolutionContext ctx) {
checkArgument(!isNullOrEmpty(namespace)); checkArgument(!isNullOrEmpty(namespace));
String evaluated = namespace; String evaluated = namespace;
@ -710,7 +642,7 @@ public class KubernetesNamespaceFactory {
preferences.put(NAMESPACE_TEMPLATE_ATTRIBUTE, defaultNamespaceName); preferences.put(NAMESPACE_TEMPLATE_ATTRIBUTE, defaultNamespaceName);
preferenceManager.update(owner, preferences); preferenceManager.update(owner, preferences);
} catch (ServerException e) { } catch (ServerException e) {
LOG.error(e.getMessage(), e); LOG.error("Failed storing namespace name in user properties.", e);
} }
} }
@ -743,6 +675,7 @@ public class KubernetesNamespaceFactory {
String normalizeNamespaceName(String namespaceName) { String normalizeNamespaceName(String namespaceName) {
namespaceName = namespaceName =
namespaceName namespaceName
.toLowerCase()
.replaceAll("[^-a-zA-Z0-9]", "-") // replace invalid chars with '-' .replaceAll("[^-a-zA-Z0-9]", "-") // replace invalid chars with '-'
.replaceAll("-+", "-") // replace multiple '-' with single ones .replaceAll("-+", "-") // replace multiple '-' with single ones
.replaceAll("^-|-$", ""); // trim dashes at beginning/end of the string .replaceAll("^-|-$", ""); // trim dashes at beginning/end of the string
@ -755,19 +688,4 @@ public class KubernetesNamespaceFactory {
namespaceName.length(), namespaceName.length(),
METADATA_NAME_MAX_LENGTH)); // limit length to METADATA_NAME_MAX_LENGTH METADATA_NAME_MAX_LENGTH)); // limit length to METADATA_NAME_MAX_LENGTH
} }
@VisibleForTesting
KubernetesWorkspaceServiceAccount doCreateServiceAccount(
String workspaceId, String namespaceName) {
return new KubernetesWorkspaceServiceAccount(
workspaceId, namespaceName, serviceAccountName, getClusterRoleNames(), clientFactory);
}
protected String getServiceAccountName() {
return serviceAccountName;
}
protected Set<String> getClusterRoleNames() {
return clusterRoleNames;
}
} }

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
/**
* This {@link NamespaceConfigurator} ensures that Secret {@link
* org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount#CREDENTIALS_SECRET_NAME}
* is present in the Workspace namespace.
*/
@Singleton
public class CredentialsSecretConfigurator implements NamespaceConfigurator {
private final KubernetesClientFactory clientFactory;
@Inject
public CredentialsSecretConfigurator(KubernetesClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
var client = clientFactory.create();
if (client.secrets().inNamespace(namespaceName).withName(CREDENTIALS_SECRET_NAME).get()
== null) {
Secret secret =
new SecretBuilder()
.withType("opaque")
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.endMetadata()
.build();
client.secrets().inNamespace(namespaceName).create(secret);
}
}
}

View File

@ -30,6 +30,6 @@ public interface NamespaceConfigurator {
* @param namespaceResolutionContext users namespace context * @param namespaceResolutionContext users namespace context
* @throws InfrastructureException when any error occurs * @throws InfrastructureException when any error occurs
*/ */
public void configure(NamespaceResolutionContext namespaceResolutionContext) void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException; throws InfrastructureException;
} }

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import javax.inject.Inject;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
/**
* This {@link NamespaceConfigurator} ensures that ConfigMap {@link
* org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount#PREFERENCES_CONFIGMAP_NAME}
* is present in the Workspace namespace.
*/
public class PreferencesConfigMapConfigurator implements NamespaceConfigurator {
private final KubernetesClientFactory clientFactory;
@Inject
public PreferencesConfigMapConfigurator(KubernetesClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
var client = clientFactory.create();
if (client.configMaps().inNamespace(namespaceName).withName(PREFERENCES_CONFIGMAP_NAME).get()
== null) {
ConfigMap configMap =
new ConfigMapBuilder()
.withNewMetadata()
.withName(PREFERENCES_CONFIGMAP_NAME)
.endMetadata()
.build();
client.configMaps().inNamespace(namespaceName).create(configMap);
}
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import java.util.Collections;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
/**
* This {@link NamespaceConfigurator} ensures that User has assigned configured ClusterRoles from
* `che.infra.kubernetes.user_cluster_roles` property. These are assigned with RoleBindings in
* user's namespace.
*/
@Singleton
public class UserPermissionConfigurator implements NamespaceConfigurator {
private final Set<String> userClusterRoles;
private final KubernetesClientFactory clientFactory;
@Inject
public UserPermissionConfigurator(
@Nullable @Named("che.infra.kubernetes.user_cluster_roles") String userClusterRoles,
CheServerKubernetesClientFactory cheClientFactory) {
this.clientFactory = cheClientFactory;
if (!isNullOrEmpty(userClusterRoles)) {
this.userClusterRoles =
Sets.newHashSet(
Splitter.on(",").trimResults().omitEmptyStrings().split(userClusterRoles));
} else {
this.userClusterRoles = Collections.emptySet();
}
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
if (!userClusterRoles.isEmpty()) {
bindRoles(
clientFactory.create(),
namespaceName,
namespaceResolutionContext.getUserName(),
userClusterRoles);
}
}
private void bindRoles(
KubernetesClient client, String namespaceName, String username, Set<String> clusterRoles) {
for (String clusterRole : clusterRoles) {
client
.rbac()
.roleBindings()
.inNamespace(namespaceName)
.createOrReplace(
new RoleBindingBuilder()
.withNewMetadata()
.withName(clusterRole)
.endMetadata()
.addToSubjects(
new io.fabric8.kubernetes.api.model.rbac.Subject(
"rbac.authorization.k8s.io", "User", username, namespaceName))
.withNewRoleRef()
.withApiGroup("rbac.authorization.k8s.io")
.withKind("ClusterRole")
.withName(clusterRole)
.endRoleRef()
.build());
}
}
}

View File

@ -23,6 +23,7 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.model.user.User;
@ -31,7 +32,6 @@ import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
/** /**
* Creates {@link Secret} with user preferences. This serves as a way for DevWorkspaces to acquire * Creates {@link Secret} with user preferences. This serves as a way for DevWorkspaces to acquire
@ -39,39 +39,36 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesN
* *
* @author Pavol Baran * @author Pavol Baran
*/ */
@Singleton
public class UserPreferencesConfigurator implements NamespaceConfigurator { public class UserPreferencesConfigurator implements NamespaceConfigurator {
private static final String USER_PREFERENCES_SECRET_NAME = "user-preferences"; private static final String USER_PREFERENCES_SECRET_NAME = "user-preferences";
private static final String USER_PREFERENCES_SECRET_MOUNT_PATH = "/config/user/preferences"; private static final String USER_PREFERENCES_SECRET_MOUNT_PATH = "/config/user/preferences";
private static final int PREFERENCE_NAME_MAX_LENGTH = 253; private static final int PREFERENCE_NAME_MAX_LENGTH = 253;
private final KubernetesNamespaceFactory namespaceFactory;
private final KubernetesClientFactory clientFactory; private final KubernetesClientFactory clientFactory;
private final UserManager userManager; private final UserManager userManager;
private final PreferenceManager preferenceManager; private final PreferenceManager preferenceManager;
@Inject @Inject
public UserPreferencesConfigurator( public UserPreferencesConfigurator(
KubernetesNamespaceFactory namespaceFactory,
KubernetesClientFactory clientFactory, KubernetesClientFactory clientFactory,
UserManager userManager, UserManager userManager,
PreferenceManager preferenceManager) { PreferenceManager preferenceManager) {
this.namespaceFactory = namespaceFactory;
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.userManager = userManager; this.userManager = userManager;
this.preferenceManager = preferenceManager; this.preferenceManager = preferenceManager;
} }
@Override @Override
public void configure(NamespaceResolutionContext namespaceResolutionContext) public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException { throws InfrastructureException {
Secret userPreferencesSecret = preparePreferencesSecret(namespaceResolutionContext); Secret userPreferencesSecret = preparePreferencesSecret(namespaceResolutionContext);
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
try { try {
clientFactory clientFactory
.create() .create()
.secrets() .secrets()
.inNamespace(namespace) .inNamespace(namespaceName)
.createOrReplace(userPreferencesSecret); .createOrReplace(userPreferencesSecret);
} catch (KubernetesClientException e) { } catch (KubernetesClientException e) {
throw new InfrastructureException( throw new InfrastructureException(

View File

@ -22,6 +22,7 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.model.user.User;
@ -29,7 +30,6 @@ import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
/** /**
* Creates {@link Secret} with user profile information such as his id, name and email. This serves * Creates {@link Secret} with user profile information such as his id, name and email. This serves
@ -37,31 +37,30 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesN
* *
* @author Pavol Baran * @author Pavol Baran
*/ */
@Singleton
public class UserProfileConfigurator implements NamespaceConfigurator { public class UserProfileConfigurator implements NamespaceConfigurator {
private static final String USER_PROFILE_SECRET_NAME = "user-profile"; private static final String USER_PROFILE_SECRET_NAME = "user-profile";
private static final String USER_PROFILE_SECRET_MOUNT_PATH = "/config/user/profile"; private static final String USER_PROFILE_SECRET_MOUNT_PATH = "/config/user/profile";
private final KubernetesNamespaceFactory namespaceFactory;
private final KubernetesClientFactory clientFactory; private final KubernetesClientFactory clientFactory;
private final UserManager userManager; private final UserManager userManager;
@Inject @Inject
public UserProfileConfigurator( public UserProfileConfigurator(KubernetesClientFactory clientFactory, UserManager userManager) {
KubernetesNamespaceFactory namespaceFactory,
KubernetesClientFactory clientFactory,
UserManager userManager) {
this.namespaceFactory = namespaceFactory;
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.userManager = userManager; this.userManager = userManager;
} }
@Override @Override
public void configure(NamespaceResolutionContext namespaceResolutionContext) public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException { throws InfrastructureException {
Secret userProfileSecret = prepareProfileSecret(namespaceResolutionContext); Secret userProfileSecret = prepareProfileSecret(namespaceResolutionContext);
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
try { try {
clientFactory.create().secrets().inNamespace(namespace).createOrReplace(userProfileSecret); clientFactory
.create()
.secrets()
.inNamespace(namespaceName)
.createOrReplace(userProfileSecret);
} catch (KubernetesClientException e) { } catch (KubernetesClientException e) {
throw new InfrastructureException( throw new InfrastructureException(
"Error occurred while trying to create user profile secret.", e); "Error occurred while trying to create user profile secret.", e);

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount;
/**
* This {@link NamespaceConfigurator} ensures that workspace ServiceAccount with proper ClusterRole
* is set in Workspace namespace.
*/
@Singleton
public class WorkspaceServiceAccountConfigurator implements NamespaceConfigurator {
private final KubernetesClientFactory clientFactory;
private final String serviceAccountName;
private final Set<String> clusterRoleNames;
@Inject
public WorkspaceServiceAccountConfigurator(
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
@Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames,
KubernetesClientFactory clientFactory) {
this.clientFactory = clientFactory;
this.serviceAccountName = serviceAccountName;
if (!isNullOrEmpty(clusterRoleNames)) {
this.clusterRoleNames =
Sets.newHashSet(
Splitter.on(",").trimResults().omitEmptyStrings().split(clusterRoleNames));
} else {
this.clusterRoleNames = Collections.emptySet();
}
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
if (!isNullOrEmpty(serviceAccountName)) {
KubernetesWorkspaceServiceAccount workspaceServiceAccount =
doCreateServiceAccount(namespaceResolutionContext.getWorkspaceId(), namespaceName);
workspaceServiceAccount.prepare();
}
}
@VisibleForTesting
public KubernetesWorkspaceServiceAccount doCreateServiceAccount(
String workspaceId, String namespaceName) {
return new KubernetesWorkspaceServiceAccount(
workspaceId, namespaceName, serviceAccountName, clusterRoleNames, clientFactory);
}
}

View File

@ -12,7 +12,6 @@
package org.eclipse.che.workspace.infrastructure.kubernetes.provision; package org.eclipse.che.workspace.infrastructure.kubernetes.provision;
import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.Namespace;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl; import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
@ -30,17 +29,13 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurato
*/ */
public class NamespaceProvisioner { public class NamespaceProvisioner {
private final KubernetesNamespaceFactory namespaceFactory; private final KubernetesNamespaceFactory namespaceFactory;
private final Set<NamespaceConfigurator> namespaceConfigurators;
@Inject @Inject
public NamespaceProvisioner( public NamespaceProvisioner(KubernetesNamespaceFactory namespaceFactory) {
KubernetesNamespaceFactory namespaceFactory,
Set<NamespaceConfigurator> namespaceConfigurators) {
this.namespaceFactory = namespaceFactory; this.namespaceFactory = namespaceFactory;
this.namespaceConfigurators = namespaceConfigurators;
} }
/** Tests for this method are in KubernetesFactoryTest. */ /** Tests for this method are in KubernetesNamespaceFactoryTest. */
public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext) public KubernetesNamespaceMeta provision(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException { throws InfrastructureException {
KubernetesNamespace namespace = KubernetesNamespace namespace =
@ -51,21 +46,9 @@ public class NamespaceProvisioner {
namespaceResolutionContext.getUserId(), namespaceResolutionContext.getUserId(),
namespaceFactory.evaluateNamespaceName(namespaceResolutionContext))); namespaceFactory.evaluateNamespaceName(namespaceResolutionContext)));
KubernetesNamespaceMeta namespaceMeta = return namespaceFactory
namespaceFactory .fetchNamespace(namespace.getName())
.fetchNamespace(namespace.getName()) .orElseThrow(
.orElseThrow( () -> new InfrastructureException("Not able to find namespace " + namespace.getName()));
() ->
new InfrastructureException(
"Not able to find namespace " + namespace.getName()));
configureNamespace(namespaceResolutionContext);
return namespaceMeta;
}
private void configureNamespace(NamespaceResolutionContext namespaceResolutionContext)
throws InfrastructureException {
for (NamespaceConfigurator configurator : namespaceConfigurators) {
configurator.configure(namespaceResolutionContext);
}
} }
} }

View File

@ -20,6 +20,7 @@ import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.Kub
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.SECRETS_ROLE_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory.NAMESPACE_TEMPLATE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory.NAMESPACE_TEMPLATE_ATTRIBUTE;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyMap;
@ -51,6 +52,7 @@ import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.ServiceAccountList; import io.fabric8.kubernetes.api.model.ServiceAccountList;
import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder; import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder;
import io.fabric8.kubernetes.api.model.rbac.PolicyRule;
import io.fabric8.kubernetes.api.model.rbac.Role; import io.fabric8.kubernetes.api.model.rbac.Role;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingList; import io.fabric8.kubernetes.api.model.rbac.RoleBindingList;
import io.fabric8.kubernetes.api.model.rbac.RoleList; import io.fabric8.kubernetes.api.model.rbac.RoleList;
@ -88,6 +90,10 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesCl
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.WorkspaceServiceAccountConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.NamespaceProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@ -145,6 +151,7 @@ public class KubernetesNamespaceFactoryTest {
serverMock.before(); serverMock.before();
k8sClient = spy(serverMock.getClient()); k8sClient = spy(serverMock.getClient());
lenient().when(clientFactory.create()).thenReturn(k8sClient); lenient().when(clientFactory.create()).thenReturn(k8sClient);
lenient().when(cheClientFactory.create()).thenReturn(k8sClient);
lenient().when(k8sClient.namespaces()).thenReturn(namespaceOperation); lenient().when(k8sClient.namespaces()).thenReturn(namespaceOperation);
lenient().when(namespaceOperation.withName(any())).thenReturn(namespaceResource); lenient().when(namespaceOperation.withName(any())).thenReturn(namespaceResource);
@ -171,14 +178,13 @@ public class KubernetesNamespaceFactoryTest {
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -199,14 +205,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -220,14 +225,13 @@ public class KubernetesNamespaceFactoryTest {
public void shouldNormaliseNamespaceWhenUserNameStartsWithKube() { public void shouldNormaliseNamespaceWhenUserNameStartsWithKube() {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -246,14 +250,13 @@ public class KubernetesNamespaceFactoryTest {
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -269,14 +272,13 @@ public class KubernetesNamespaceFactoryTest {
public void shouldThrowExceptionIfNoDefaultNamespaceIsConfigured() { public void shouldThrowExceptionIfNoDefaultNamespaceIsConfigured() {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
null, null,
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -320,14 +322,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -364,14 +365,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -395,14 +395,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -429,14 +428,13 @@ public class KubernetesNamespaceFactoryTest {
.build()); .build());
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -458,14 +456,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -489,28 +486,27 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
Set.of(new CredentialsSecretConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
preferenceManager, preferenceManager,
pool)); pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
KubernetesSecrets secrets = mock(KubernetesSecrets.class); when(toReturnNamespace.getName()).thenReturn("namespaceName");
when(toReturnNamespace.secrets()).thenReturn(secrets);
when(toReturnNamespace.configMaps()).thenReturn(mock(KubernetesConfigsMaps.class));
when(secrets.get()).thenReturn(Collections.emptyList());
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(k8sClient.secrets()).thenReturn(mixedOperation); when(k8sClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
when(namespaceResource.get()).thenReturn(null);
when(cheClientFactory.create()).thenReturn(k8sClient);
when(clientFactory.create()).thenReturn(k8sClient);
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -531,28 +527,25 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
preferenceManager, preferenceManager,
pool)); pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class); when(toReturnNamespace.getName()).thenReturn("namespaceName");
when(toReturnNamespace.secrets()).thenReturn(mock(KubernetesSecrets.class));
when(toReturnNamespace.configMaps()).thenReturn(configsMaps);
when(configsMaps.get(eq(PREFERENCES_CONFIGMAP_NAME))).thenReturn(empty());
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(k8sClient.configMaps()).thenReturn(mixedOperation); when(k8sClient.configMaps()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
when(namespaceResource.get()).thenReturn(null);
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -567,19 +560,60 @@ public class KubernetesNamespaceFactoryTest {
} }
@Test @Test
public void shouldNotCreateCredentialsSecretIfExists() throws Exception { public void testAllConfiguratorsAreCalledWhenCreatingNamespace() throws InfrastructureException {
// given // given
String namespaceName = "testNamespaceName";
NamespaceConfigurator configurator1 = Mockito.mock(NamespaceConfigurator.class);
NamespaceConfigurator configurator2 = Mockito.mock(NamespaceConfigurator.class);
Set<NamespaceConfigurator> namespaceConfigurators = Set.of(configurator1, configurator2);
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
namespaceConfigurators,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
when(toReturnNamespace.getName()).thenReturn(namespaceName);
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che");
doReturn(toReturnNamespace).when(namespaceFactory).get(identity);
// when
KubernetesNamespace namespace = namespaceFactory.getOrCreate(identity);
// then
NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext("workspace123", "123", "jondoe");
verify(configurator1).configure(resolutionCtx, namespaceName);
verify(configurator2).configure(resolutionCtx, namespaceName);
assertEquals(namespace, toReturnNamespace);
}
@Test
public void shouldNotCreateCredentialsSecretIfExists() throws Exception {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -607,14 +641,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -643,14 +676,13 @@ public class KubernetesNamespaceFactoryTest {
public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throws Exception { public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -669,14 +701,13 @@ public class KubernetesNamespaceFactoryTest {
public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception { public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -700,14 +731,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -723,7 +753,6 @@ public class KubernetesNamespaceFactoryTest {
// then // then
assertEquals(toReturnNamespace, namespace); assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false), any(), any()); verify(toReturnNamespace).prepare(eq(false), any(), any());
} }
@ -733,14 +762,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
false, false,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -757,7 +785,6 @@ public class KubernetesNamespaceFactoryTest {
// then // then
assertEquals(toReturnNamespace, namespace); assertEquals(toReturnNamespace, namespace);
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnNamespace).prepare(eq(false), any(), any()); verify(toReturnNamespace).prepare(eq(false), any(), any());
} }
@ -765,17 +792,18 @@ public class KubernetesNamespaceFactoryTest {
public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined() public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined()
throws Exception { throws Exception {
// given // given
var serviceAccountCfg =
spy(new WorkspaceServiceAccountConfigurator("serviceAccount", "", clientFactory));
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"serviceAccount",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
Set.of(serviceAccountCfg),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -783,13 +811,12 @@ public class KubernetesNamespaceFactoryTest {
pool)); pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace); prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123"); when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
KubernetesWorkspaceServiceAccount serviceAccount = KubernetesWorkspaceServiceAccount serviceAccount =
mock(KubernetesWorkspaceServiceAccount.class); mock(KubernetesWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any()); doReturn(serviceAccount).when(serviceAccountCfg).doCreateServiceAccount(any(), any());
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -797,24 +824,25 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory.getOrCreate(identity); namespaceFactory.getOrCreate(identity);
// then // then
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123"); verify(serviceAccountCfg).doCreateServiceAccount("workspace123", "workspace123");
verify(serviceAccount).prepare(); verify(serviceAccount).prepare();
} }
@Test @Test
public void shouldBindToAllConfiguredClusterRoles() throws Exception { public void shouldBindToAllConfiguredClusterRoles() throws Exception {
// given // given
var serviceAccountConfigurator =
new WorkspaceServiceAccountConfigurator("serviceAccount", "cr2, cr3", clientFactory);
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"serviceAccount",
"cr2, cr3",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
Set.of(serviceAccountConfigurator),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -822,10 +850,10 @@ public class KubernetesNamespaceFactoryTest {
pool)); pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace); prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123"); when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true); when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true);
when(cheClientFactory.create()).thenReturn(k8sClient);
when(clientFactory.create(any())).thenReturn(k8sClient); when(clientFactory.create(any())).thenReturn(k8sClient);
// pre-create the cluster roles // pre-create the cluster roles
@ -848,7 +876,6 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory.getOrCreate(identity); namespaceFactory.getOrCreate(identity);
// then // then
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list(); ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list();
assertEquals(sas.getItems().size(), 1); assertEquals(sas.getItems().size(), 1);
@ -881,19 +908,20 @@ public class KubernetesNamespaceFactoryTest {
} }
@Test @Test
public void shouldCreateExecAndViewRolesAndBindings() throws Exception { public void shouldCreateAndBindCredentialsSecretRole() throws Exception {
// given // given
var serviceAccountConfigurator =
new WorkspaceServiceAccountConfigurator("serviceAccount", "cr2, cr3", clientFactory);
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"serviceAccount",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
Set.of(serviceAccountConfigurator),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -901,11 +929,70 @@ public class KubernetesNamespaceFactoryTest {
pool)); pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace); prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123"); when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any()); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true);
when(clientFactory.create(any())).thenReturn(k8sClient); when(clientFactory.create(any())).thenReturn(k8sClient);
when(cheClientFactory.create()).thenReturn(k8sClient);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
namespaceFactory.getOrCreate(identity);
// then
Optional<Role> roleOptional =
k8sClient
.rbac()
.roles()
.inNamespace("workspace123")
.list()
.getItems()
.stream()
.filter(r -> r.getMetadata().getName().equals(SECRETS_ROLE_NAME))
.findAny();
assertTrue(roleOptional.isPresent());
PolicyRule rule = roleOptional.get().getRules().get(0);
assertEquals(rule.getResources(), singletonList("secrets"));
assertEquals(rule.getResourceNames(), singletonList(CREDENTIALS_SECRET_NAME));
assertEquals(rule.getApiGroups(), singletonList(""));
assertEquals(rule.getVerbs(), Arrays.asList("get", "patch"));
assertTrue(
k8sClient
.rbac()
.roleBindings()
.inNamespace("workspace123")
.list()
.getItems()
.stream()
.anyMatch(rb -> rb.getMetadata().getName().equals("serviceAccount-secrets")));
}
@Test
public void shouldCreateExecAndViewRolesAndBindings() throws Exception {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
Set.of(
new WorkspaceServiceAccountConfigurator("serviceAccount", "", clientFactory)),
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true);
when(clientFactory.create(any())).thenReturn(k8sClient);
when(cheClientFactory.create()).thenReturn(k8sClient);
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -913,7 +1000,6 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory.getOrCreate(identity); namespaceFactory.getOrCreate(identity);
// then // then
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list(); ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list();
assertEquals(sas.getItems().size(), 1); assertEquals(sas.getItems().size(), 1);
@ -951,62 +1037,19 @@ public class KubernetesNamespaceFactoryTest {
"serviceAccount-secrets")); "serviceAccount-secrets"));
} }
@Test
public void testNullClusterRolesResultsInEmptySet() {
namespaceFactory =
new KubernetesNamespaceFactory(
"",
null,
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
assertTrue(namespaceFactory.getClusterRoleNames().isEmpty());
}
@Test
public void testClusterRolesProperlyParsed() {
namespaceFactory =
new KubernetesNamespaceFactory(
"",
" one,two, three ,,five ",
"che-<userid>",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool);
Set<String> expected = Sets.newHashSet("one", "two", "three", "five");
assertTrue(namespaceFactory.getClusterRoleNames().containsAll(expected));
assertTrue(expected.containsAll(namespaceFactory.getClusterRoleNames()));
}
@Test @Test
public void public void
testEvalNamespaceUsesNamespaceDefaultIfWorkspaceDoesntRecordNamespaceAndLegacyNamespaceDoesntExist() testEvalNamespaceUsesNamespaceDefaultIfWorkspaceDoesntRecordNamespaceAndLegacyNamespaceDoesntExist()
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1025,14 +1068,13 @@ public class KubernetesNamespaceFactoryTest {
public void testEvalNamespaceUsesNamespaceFromUserPreferencesIfExist() throws Exception { public void testEvalNamespaceUsesNamespaceFromUserPreferencesIfExist() throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1056,14 +1098,13 @@ public class KubernetesNamespaceFactoryTest {
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>-<username>", "che-<userid>-<username>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1088,14 +1129,13 @@ public class KubernetesNamespaceFactoryTest {
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>-<username>", "che-<userid>-<username>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1120,14 +1160,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<username>", "che-<username>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1148,14 +1187,13 @@ public class KubernetesNamespaceFactoryTest {
throws Exception { throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1178,14 +1216,13 @@ public class KubernetesNamespaceFactoryTest {
public void testEvalNamespaceTreatsWorkspaceRecordedNamespaceLiterally() throws Exception { public void testEvalNamespaceTreatsWorkspaceRecordedNamespaceLiterally() throws Exception {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1229,14 +1266,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1256,14 +1292,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
false, false,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1296,14 +1331,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-cha-cha-cha", "<username>-cha-cha-cha",
false, false,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1335,14 +1369,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-cha-cha-cha", "<username>-cha-cha-cha",
false, false,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1386,14 +1419,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
"try_placeholder_here=<username>", "try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1412,14 +1444,13 @@ public class KubernetesNamespaceFactoryTest {
namespaceFactory = namespaceFactory =
spy( spy(
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
"try_placeholder_here=<username>", "try_placeholder_here=<username>",
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1444,14 +1475,13 @@ public class KubernetesNamespaceFactoryTest {
public void normalizeTest(String raw, String expected) { public void normalizeTest(String raw, String expected) {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"<username>-che", "<username>-che",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1464,14 +1494,13 @@ public class KubernetesNamespaceFactoryTest {
public void normalizeLengthTest() { public void normalizeLengthTest() {
namespaceFactory = namespaceFactory =
new KubernetesNamespaceFactory( new KubernetesNamespaceFactory(
"",
"",
"che-<userid>", "che-<userid>",
true, true,
true, true,
true, true,
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -1551,14 +1580,15 @@ public class KubernetesNamespaceFactoryTest {
private void prepareNamespace(KubernetesNamespace namespace) throws InfrastructureException { private void prepareNamespace(KubernetesNamespace namespace) throws InfrastructureException {
KubernetesSecrets secrets = mock(KubernetesSecrets.class); KubernetesSecrets secrets = mock(KubernetesSecrets.class);
lenient().when(namespace.secrets()).thenReturn(secrets);
KubernetesConfigsMaps configmaps = mock(KubernetesConfigsMaps.class); KubernetesConfigsMaps configmaps = mock(KubernetesConfigsMaps.class);
when(namespace.secrets()).thenReturn(secrets); lenient().when(namespace.secrets()).thenReturn(secrets);
when(namespace.configMaps()).thenReturn(configmaps); lenient().when(namespace.configMaps()).thenReturn(configmaps);
Secret secretMock = mock(Secret.class); Secret secretMock = mock(Secret.class);
ObjectMeta objectMeta = mock(ObjectMeta.class); ObjectMeta objectMeta = mock(ObjectMeta.class);
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME); lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
when(secretMock.getMetadata()).thenReturn(objectMeta); lenient().when(secretMock.getMetadata()).thenReturn(objectMeta);
when(secrets.get()).thenReturn(Collections.singletonList(secretMock)); lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
} }
private Namespace createNamespace(String name, String phase) { private Namespace createNamespace(String name, String phase) {
@ -1574,6 +1604,6 @@ public class KubernetesNamespaceFactoryTest {
private KubernetesNamespaceMeta testProvisioning(NamespaceResolutionContext context) private KubernetesNamespaceMeta testProvisioning(NamespaceResolutionContext context)
throws InfrastructureException { throws InfrastructureException {
return new NamespaceProvisioner(namespaceFactory, emptySet()).provision(context); return new NamespaceProvisioner(namespaceFactory).provision(context);
} }
} }

View File

@ -84,6 +84,7 @@ public class KubernetesNamespaceTest {
@BeforeMethod @BeforeMethod
public void setUp() throws Exception { public void setUp() throws Exception {
lenient().when(clientFactory.create(anyString())).thenReturn(kubernetesClient); lenient().when(clientFactory.create(anyString())).thenReturn(kubernetesClient);
lenient().when(cheClientFactory.create(anyString())).thenReturn(kubernetesClient);
lenient().doReturn(namespaceOperation).when(kubernetesClient).namespaces(); lenient().doReturn(namespaceOperation).when(kubernetesClient).namespaces();

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import java.util.Map;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class CredentialsSecretConfiguratorTest {
private NamespaceConfigurator configurator;
@Mock private KubernetesClientFactory clientFactory;
private KubernetesServer serverMock;
private NamespaceResolutionContext namespaceResolutionContext;
private final String TEST_NAMESPACE_NAME = "namespace123";
private final String TEST_WORKSPACE_ID = "workspace123";
private final String TEST_USER_ID = "user123";
private final String TEST_USERNAME = "jondoe";
@BeforeMethod
public void setUp() throws InfrastructureException {
configurator = new CredentialsSecretConfigurator(clientFactory);
serverMock = new KubernetesServer(true, true);
serverMock.before();
KubernetesClient client = spy(serverMock.getClient());
when(clientFactory.create()).thenReturn(client);
namespaceResolutionContext =
new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME);
}
@Test
public void createCredentialsSecretWhenDoesNotExist()
throws InfrastructureException, InterruptedException {
// given - clean env
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then create a secret
Assert.assertEquals(serverMock.getLastRequest().getMethod(), "POST");
Assert.assertNotNull(
serverMock
.getClient()
.secrets()
.inNamespace(TEST_NAMESPACE_NAME)
.withName(CREDENTIALS_SECRET_NAME)
.get());
}
@Test
public void doNothingWhenSecretAlreadyExists()
throws InfrastructureException, InterruptedException {
// given - secret already exists
serverMock
.getClient()
.secrets()
.inNamespace(TEST_NAMESPACE_NAME)
.create(
new SecretBuilder()
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.withAnnotations(Map.of("already", "created"))
.endMetadata()
.build());
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - don't create the secret
Assert.assertEquals(serverMock.getLastRequest().getMethod(), "GET");
var secrets =
serverMock.getClient().secrets().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
Assert.assertEquals(secrets.size(), 1);
Assert.assertEquals(secrets.get(0).getMetadata().getAnnotations().get("already"), "created");
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import java.util.Map;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class PreferencesConfigMapConfiguratorTest {
private NamespaceConfigurator configurator;
@Mock private KubernetesClientFactory clientFactory;
private KubernetesServer serverMock;
private NamespaceResolutionContext namespaceResolutionContext;
private final String TEST_NAMESPACE_NAME = "namespace123";
private final String TEST_WORKSPACE_ID = "workspace123";
private final String TEST_USER_ID = "user123";
private final String TEST_USERNAME = "jondoe";
@BeforeMethod
public void setUp() throws InfrastructureException {
configurator = new PreferencesConfigMapConfigurator(clientFactory);
serverMock = new KubernetesServer(true, true);
serverMock.before();
KubernetesClient client = spy(serverMock.getClient());
when(clientFactory.create()).thenReturn(client);
namespaceResolutionContext =
new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME);
}
@Test
public void createConfigmapWhenDoesntExist()
throws InfrastructureException, InterruptedException {
// given - clean env
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then configmap created
Assert.assertEquals(serverMock.getLastRequest().getMethod(), "POST");
Assert.assertNotNull(
serverMock
.getClient()
.configMaps()
.inNamespace(TEST_NAMESPACE_NAME)
.withName(PREFERENCES_CONFIGMAP_NAME)
.get());
verify(clientFactory, times(1)).create();
}
@Test
public void doNothingWhenConfigmapExists() throws InfrastructureException, InterruptedException {
// given - configmap already exists
serverMock
.getClient()
.configMaps()
.inNamespace(TEST_NAMESPACE_NAME)
.create(
new ConfigMapBuilder()
.withNewMetadata()
.withName(PREFERENCES_CONFIGMAP_NAME)
.withAnnotations(Map.of("already", "created"))
.endMetadata()
.build());
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - don't create the configmap
Assert.assertEquals(serverMock.getLastRequest().getMethod(), "GET");
var configmaps =
serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
Assert.assertEquals(configmaps.size(), 1);
Assert.assertEquals(configmaps.get(0).getMetadata().getAnnotations().get("already"), "created");
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder;
import io.fabric8.kubernetes.api.model.rbac.Subject;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class UserPermissionConfiguratorTest {
private NamespaceConfigurator configurator;
@Mock private CheServerKubernetesClientFactory clientFactory;
private KubernetesClient client;
private KubernetesServer serverMock;
private NamespaceResolutionContext namespaceResolutionContext;
private final String TEST_NAMESPACE_NAME = "namespace123";
private final String TEST_WORKSPACE_ID = "workspace123";
private final String TEST_USER_ID = "user123";
private final String TEST_USERNAME = "jondoe";
private final String TEST_CLUSTER_ROLES = "cr1,cr2";
@BeforeMethod
public void setUp() throws InfrastructureException {
configurator = new UserPermissionConfigurator(TEST_CLUSTER_ROLES, clientFactory);
serverMock = new KubernetesServer(true, true);
serverMock.before();
client = spy(serverMock.getClient());
lenient().when(clientFactory.create()).thenReturn(client);
namespaceResolutionContext =
new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME);
}
@Test
public void doNothingWhenNoClusterRolesSet()
throws InfrastructureException, InterruptedException {
// given - no cluster roles set
configurator = new UserPermissionConfigurator("", clientFactory);
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - do nothing
Assert.assertNull(serverMock.getLastRequest());
verify(clientFactory, never()).create();
}
@Test
public void bindAllClusterRolesWhenEmptyEnv()
throws InfrastructureException, InterruptedException {
// given - clean env
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - create all role bindings
var roleBindings =
serverMock.getClient().rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME);
Assert.assertEquals(roleBindings.list().getItems().size(), 2);
var cr1 = roleBindings.withName("cr1").get();
Assert.assertNotNull(cr1);
Assert.assertEquals(cr1.getSubjects().get(0).getName(), TEST_USERNAME);
Assert.assertEquals(cr1.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME);
Assert.assertEquals(cr1.getRoleRef().getName(), "cr1");
var cr2 = roleBindings.withName("cr2").get();
Assert.assertNotNull(cr2);
Assert.assertEquals(cr2.getSubjects().get(0).getName(), TEST_USERNAME);
Assert.assertEquals(cr2.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME);
Assert.assertEquals(cr2.getRoleRef().getName(), "cr2");
verify(client, times(2)).rbac();
}
@Test
public void replaceExistingBindingsWithSameName() throws InfrastructureException {
// given - cr1 binding already exists
client
.rbac()
.roleBindings()
.inNamespace(TEST_NAMESPACE_NAME)
.create(
new RoleBindingBuilder()
.withNewMetadata()
.withName("cr1")
.endMetadata()
.withSubjects(new Subject("blabol", "blabol", "blabol", "blabol"))
.withNewRoleRef()
.withName("blabol")
.endRoleRef()
.build());
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then
var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME);
Assert.assertEquals(roleBindings.list().getItems().size(), 2);
var cr1 = roleBindings.withName("cr1").get();
Assert.assertEquals(cr1.getRoleRef().getName(), "cr1");
Assert.assertEquals(cr1.getSubjects().size(), 1);
Assert.assertEquals(cr1.getSubjects().get(0).getName(), TEST_USERNAME);
Assert.assertEquals(cr1.getSubjects().get(0).getNamespace(), TEST_NAMESPACE_NAME);
}
@Test
public void keepOtherClusterRoles() throws InfrastructureException {
// given - some other binding in place
client
.rbac()
.roleBindings()
.inNamespace(TEST_NAMESPACE_NAME)
.create(
new RoleBindingBuilder()
.withNewMetadata()
.withName("othercr")
.endMetadata()
.withSubjects(new Subject("blabol", "blabol", "blabol", "blabol"))
.withNewRoleRef()
.withName("blabol")
.endRoleRef()
.build());
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then
var roleBindings = client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME);
Assert.assertEquals(roleBindings.list().getItems().size(), 3);
}
}

View File

@ -86,7 +86,7 @@ public class UserPreferencesConfiguratorTest {
@Test @Test
public void shouldCreatePreferencesSecret() throws InfrastructureException { public void shouldCreatePreferencesSecret() throws InfrastructureException {
userPreferencesConfigurator.configure(context); userPreferencesConfigurator.configure(context, USER_NAMESPACE);
List<Secret> secrets = List<Secret> secrets =
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems(); kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
assertEquals(secrets.size(), 1); assertEquals(secrets.size(), 1);
@ -99,7 +99,7 @@ public class UserPreferencesConfiguratorTest {
"Preferences of user with id:" + USER_ID + " cannot be retrieved.") "Preferences of user with id:" + USER_ID + " cannot be retrieved.")
public void shouldNotCreateSecretOnException() throws ServerException, InfrastructureException { public void shouldNotCreateSecretOnException() throws ServerException, InfrastructureException {
when(preferenceManager.find(USER_ID)).thenThrow(new ServerException("test exception")); when(preferenceManager.find(USER_ID)).thenThrow(new ServerException("test exception"));
userPreferencesConfigurator.configure(context); userPreferencesConfigurator.configure(context, USER_NAMESPACE);
fail("InfrastructureException should have been thrown."); fail("InfrastructureException should have been thrown.");
} }

View File

@ -11,7 +11,6 @@
*/ */
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail; import static org.testng.Assert.fail;
@ -26,7 +25,6 @@ import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener; import org.mockito.testng.MockitoTestNGListener;
@ -47,7 +45,6 @@ public class UserProfileConfiguratorTest {
private static final String USER_EMAIL = "user-email"; private static final String USER_EMAIL = "user-email";
private static final String USER_NAMESPACE = "user-namespace"; private static final String USER_NAMESPACE = "user-namespace";
@Mock private KubernetesNamespaceFactory namespaceFactory;
@Mock private KubernetesClientFactory clientFactory; @Mock private KubernetesClientFactory clientFactory;
@Mock private UserManager userManager; @Mock private UserManager userManager;
@ -63,7 +60,6 @@ public class UserProfileConfiguratorTest {
kubernetesServer.before(); kubernetesServer.before();
when(userManager.getById(USER_ID)).thenReturn(new UserImpl(USER_ID, USER_EMAIL, USER_NAME)); when(userManager.getById(USER_ID)).thenReturn(new UserImpl(USER_ID, USER_EMAIL, USER_NAME));
when(namespaceFactory.evaluateNamespaceName(any())).thenReturn(USER_NAMESPACE);
when(clientFactory.create()).thenReturn(kubernetesServer.getClient()); when(clientFactory.create()).thenReturn(kubernetesServer.getClient());
} }
@ -74,7 +70,7 @@ public class UserProfileConfiguratorTest {
@Test @Test
public void shouldCreateProfileSecret() throws InfrastructureException { public void shouldCreateProfileSecret() throws InfrastructureException {
userProfileConfigurator.configure(context); userProfileConfigurator.configure(context, USER_NAMESPACE);
List<Secret> secrets = List<Secret> secrets =
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems(); kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
assertEquals(secrets.size(), 1); assertEquals(secrets.size(), 1);
@ -87,7 +83,7 @@ public class UserProfileConfiguratorTest {
public void shouldNotCreateSecretOnException() public void shouldNotCreateSecretOnException()
throws NotFoundException, ServerException, InfrastructureException { throws NotFoundException, ServerException, InfrastructureException {
when(userManager.getById(USER_ID)).thenThrow(new ServerException("test exception")); when(userManager.getById(USER_ID)).thenThrow(new ServerException("test exception"));
userProfileConfigurator.configure(context); userProfileConfigurator.configure(context, USER_NAMESPACE);
fail("InfrastructureException should have been thrown."); fail("InfrastructureException should have been thrown.");
} }
} }

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
import static java.util.stream.Collectors.joining;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class WorkspaceServiceAccountConfiguratorTest {
private WorkspaceServiceAccountConfigurator configurator;
@Mock private CheServerKubernetesClientFactory clientFactory;
private KubernetesClient client;
private KubernetesServer serverMock;
private NamespaceResolutionContext namespaceResolutionContext;
@Mock private KubernetesWorkspaceServiceAccount kubeWSA;
private final String TEST_NAMESPACE_NAME = "namespace123";
private final String TEST_WORKSPACE_ID = "workspace123";
private final String TEST_USER_ID = "user123";
private final String TEST_USERNAME = "jondoe";
private final String TEST_SERVICE_ACCOUNT = "serviceAccount123";
private final String TEST_CLUSTER_ROLES = "cr1, cr2";
@BeforeMethod
public void setUp() throws InfrastructureException {
configurator =
spy(
new WorkspaceServiceAccountConfigurator(
TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory));
// when(configurator.doCreateServiceAccount(TEST_WORKSPACE_ID,
// TEST_NAMESPACE_NAME)).thenReturn(kubeWSA);
serverMock = new KubernetesServer(true, true);
serverMock.before();
client = spy(serverMock.getClient());
lenient().when(clientFactory.create(TEST_WORKSPACE_ID)).thenReturn(client);
namespaceResolutionContext =
new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME);
}
@Test
public void createWorkspaceServiceAccountWithBindings()
throws InfrastructureException, InterruptedException {
// given - cluster roles exists in cluster
configurator =
new WorkspaceServiceAccountConfigurator(
TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory);
client
.rbac()
.clusterRoles()
.create(new ClusterRoleBuilder().withNewMetadata().withName("cr1").endMetadata().build());
client
.rbac()
.clusterRoles()
.create(new ClusterRoleBuilder().withNewMetadata().withName("cr2").endMetadata().build());
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - create service account with all the bindings
var serviceAccount =
client
.serviceAccounts()
.inNamespace(TEST_NAMESPACE_NAME)
.withName(TEST_SERVICE_ACCOUNT)
.get();
assertNotNull(serviceAccount);
var roleBindings =
client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
assertEquals(
roleBindings.size(),
6,
roleBindings
.stream()
.map(r -> r.getMetadata().getName())
.collect(joining(", "))); // exec, secrets, configmaps, view bindings + cr1, cr2
}
@Test
public void dontCreateBindingsWhenClusterRolesDontExists() throws InfrastructureException {
// given - cluster roles exists in cluster
configurator =
new WorkspaceServiceAccountConfigurator(
TEST_SERVICE_ACCOUNT, TEST_CLUSTER_ROLES, clientFactory);
// when
configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
// then - create service account with default bindings
var serviceAccount =
client
.serviceAccounts()
.inNamespace(TEST_NAMESPACE_NAME)
.withName(TEST_SERVICE_ACCOUNT)
.get();
assertNotNull(serviceAccount);
var roleBindings =
client.rbac().roleBindings().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
assertEquals(
roleBindings.size(),
4,
roleBindings
.stream()
.map(r -> r.getMetadata().getName())
.collect(joining(", "))); // exec, secrets, configmaps, view bindings
}
}

View File

@ -134,6 +134,10 @@
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-shared</artifactId> <artifactId>che-multiuser-keycloak-shared</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-oidc</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
@ -148,6 +152,22 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-server-mock</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit-jupiter-api</artifactId>
<groupId>org.junit.jupiter</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.che.core</groupId> <groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-dto</artifactId> <artifactId>che-core-api-dto</artifactId>

View File

@ -57,8 +57,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
private static final String BEFORE_TOKEN = "access_token="; private static final String BEFORE_TOKEN = "access_token=";
private static final String AFTER_TOKEN = "&expires"; private static final String AFTER_TOKEN = "&expires";
private final KubernetesClientConfigFactory configBuilder;
@Inject @Inject
public OpenShiftClientFactory( public OpenShiftClientFactory(
KubernetesClientConfigFactory configBuilder, KubernetesClientConfigFactory configBuilder,
@ -72,6 +70,7 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
int connectionPoolKeepAlive, int connectionPoolKeepAlive,
EventListener eventListener) { EventListener eventListener) {
super( super(
configBuilder,
masterUrl, masterUrl,
doTrustCerts, doTrustCerts,
maxConcurrentRequests, maxConcurrentRequests,
@ -79,7 +78,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
maxIdleConnections, maxIdleConnections,
connectionPoolKeepAlive, connectionPoolKeepAlive,
eventListener); eventListener);
this.configBuilder = configBuilder;
} }
/** /**
@ -96,7 +94,7 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
*/ */
public OpenShiftClient createOC(String workspaceId) throws InfrastructureException { public OpenShiftClient createOC(String workspaceId) throws InfrastructureException {
Config configForWorkspace = buildConfig(getDefaultConfig(), workspaceId); Config configForWorkspace = buildConfig(getDefaultConfig(), workspaceId);
return createOC(configForWorkspace); return create(configForWorkspace);
} }
/** /**
@ -114,23 +112,13 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
* @throws InfrastructureException if any error occurs on client instance creation. * @throws InfrastructureException if any error occurs on client instance creation.
*/ */
public OpenShiftClient createOC() throws InfrastructureException { public OpenShiftClient createOC() throws InfrastructureException {
return createOC(buildConfig(getDefaultConfig(), null)); return create(buildConfig(getDefaultConfig(), null));
} }
public OpenShiftClient createAuthenticatedClient(String token) { public OpenShiftClient createAuthenticatedClient(String token) {
Config config = getDefaultConfig(); Config config = getDefaultConfig();
config.setOauthToken(token); config.setOauthToken(token);
return createOC(config); return create(config);
}
@Override
public OkHttpClient getAuthenticatedHttpClient() throws InfrastructureException {
if (!configBuilder.isPersonalized()) {
throw new InfrastructureException(
"Not able to construct impersonating openshift API client.");
}
// Ensure to get OkHttpClient with all necessary interceptors.
return createOC(buildConfig(getDefaultConfig(), null)).getHttpClient();
} }
@Override @Override
@ -147,19 +135,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
return configBuilder.build(); return configBuilder.build();
} }
/**
* Builds the Openshift {@link Config} object based on a provided {@link Config} object and an
* optional workspace ID.
*
* <p>This method overrides the one in the Kubernetes infrastructure to introduce an additional
* extension level by delegating to an {@link KubernetesClientConfigFactory}
*/
@Override
protected Config buildConfig(Config config, @Nullable String workspaceId)
throws InfrastructureException {
return configBuilder.buildConfig(config, workspaceId);
}
@Override @Override
protected Interceptor buildKubernetesInterceptor(Config config) { protected Interceptor buildKubernetesInterceptor(Config config) {
final String oauthToken; final String oauthToken;
@ -223,7 +198,7 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
}; };
} }
private DefaultOpenShiftClient createOC(Config config) { protected DefaultOpenShiftClient create(Config config) {
return new UnclosableOpenShiftClient( return new UnclosableOpenShiftClient(
clientForConfig(config), config, this::initializeRequestTracing); clientForConfig(config), config, this::initializeRequestTracing);
} }

View File

@ -53,7 +53,9 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesDev
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserPreferencesConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.UserProfileConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy;
@ -94,6 +96,8 @@ import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftE
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProjectFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProjectFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.RemoveProjectOnWorkspaceRemove; import org.eclipse.che.workspace.infrastructure.openshift.project.RemoveProjectOnWorkspaceRemove;
import org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftStopWorkspaceRoleConfigurator;
import org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftWorkspaceServiceAccountConfigurator;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftPreviewUrlCommandProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftPreviewUrlCommandProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenshiftTrustedCAProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenshiftTrustedCAProvisioner;
import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner;
@ -117,6 +121,10 @@ public class OpenShiftInfraModule extends AbstractModule {
Multibinder.newSetBinder(binder(), NamespaceConfigurator.class); Multibinder.newSetBinder(binder(), NamespaceConfigurator.class);
namespaceConfigurators.addBinding().to(UserProfileConfigurator.class); namespaceConfigurators.addBinding().to(UserProfileConfigurator.class);
namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class); namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class);
namespaceConfigurators.addBinding().to(CredentialsSecretConfigurator.class);
namespaceConfigurators.addBinding().to(PreferencesConfigMapConfigurator.class);
namespaceConfigurators.addBinding().to(OpenShiftWorkspaceServiceAccountConfigurator.class);
namespaceConfigurators.addBinding().to(OpenShiftStopWorkspaceRoleConfigurator.class);
bind(KubernetesNamespaceService.class); bind(KubernetesNamespaceService.class);

View File

@ -11,9 +11,9 @@
*/ */
package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth; package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
import com.google.inject.Provider; import com.google.inject.Provider;
import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.Config;

View File

@ -16,15 +16,6 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.OpenShiftClient;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -46,9 +37,6 @@ import org.slf4j.LoggerFactory;
/** /**
* This filter uses given token directly. It's used for native OpenShift user authentication. * This filter uses given token directly. It's used for native OpenShift user authentication.
* Requests without token or with invalid token are rejected. * Requests without token or with invalid token are rejected.
*
* <p>{@link OpenshiftTokenInitializationFilter#UNAUTHORIZED_ENDPOINT_PATHS} is list of
* unauthenticated paths, that are allowed without token.
*/ */
@Singleton @Singleton
public class OpenshiftTokenInitializationFilter public class OpenshiftTokenInitializationFilter
@ -57,9 +45,6 @@ public class OpenshiftTokenInitializationFilter
private static final Logger LOG = private static final Logger LOG =
LoggerFactory.getLogger(OpenshiftTokenInitializationFilter.class); LoggerFactory.getLogger(OpenshiftTokenInitializationFilter.class);
private static final List<String> UNAUTHORIZED_ENDPOINT_PATHS =
Collections.singletonList("/system/state");
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final OpenShiftClientFactory clientFactory; private final OpenShiftClientFactory clientFactory;
@ -121,38 +106,4 @@ public class OpenshiftTokenInitializationFilter
// we can use fake email, but probably we will need to find better solution. // we can use fake email, but probably we will need to find better solution.
return userMeta.getName() + "@che"; return userMeta.getName() + "@che";
} }
/**
* If request path is in {@link OpenshiftTokenInitializationFilter#UNAUTHORIZED_ENDPOINT_PATHS},
* the request is allowed. All other requests are rejected with error code 401.
*/
@Override
protected void handleMissingToken(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// if request path is in unauthorized endpoints, continue
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getServletPath();
if (UNAUTHORIZED_ENDPOINT_PATHS.contains(path)) {
LOG.debug("Allowing request to '{}' without authorization header.", path);
chain.doFilter(request, response);
return;
}
}
LOG.error("Rejecting the request due to missing/expired token in Authorization header.");
sendError(response, 401, "Authorization token is missing or expired");
}
@Override
public void init(FilterConfig filterConfig) {
LOG.trace("OpenshiftTokenInitializationFilter#init({})", filterConfig);
}
@Override
public void destroy() {
LOG.trace("OpenshiftTokenInitializationFilter#destroy()");
}
} }

View File

@ -50,7 +50,6 @@ public class OpenShiftProject extends KubernetesNamespace {
private final OpenShiftRoutes routes; private final OpenShiftRoutes routes;
private final OpenShiftClientFactory clientFactory; private final OpenShiftClientFactory clientFactory;
private final KubernetesClientFactory cheClientFactory;
private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory;
@VisibleForTesting @VisibleForTesting
@ -78,7 +77,6 @@ public class OpenShiftProject extends KubernetesNamespace {
ingresses, ingresses,
secrets, secrets,
configMaps); configMaps);
this.cheClientFactory = cheClientFactory;
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.routes = routes; this.routes = routes;
this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory;
@ -93,7 +91,6 @@ public class OpenShiftProject extends KubernetesNamespace {
String workspaceId) { String workspaceId) {
super(clientFactory, cheClientFactory, executor, name, workspaceId); super(clientFactory, cheClientFactory, executor, name, workspaceId);
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.cheClientFactory = cheClientFactory;
this.routes = new OpenShiftRoutes(name, workspaceId, clientFactory); this.routes = new OpenShiftRoutes(name, workspaceId, clientFactory);
this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory; this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory;
} }

View File

@ -16,23 +16,18 @@ import static java.lang.String.format;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.PREFERENCES_CONFIGMAP_NAME;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.Project;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Named; import javax.inject.Named;
import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.Workspace;
@ -47,11 +42,11 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesCl
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.impls.KubernetesNamespaceMetaImpl;
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.Constants; import org.eclipse.che.workspace.infrastructure.openshift.Constants;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftStopWorkspaceRoleProvisioner;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -67,14 +62,11 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
private final boolean initWithCheServerSa; private final boolean initWithCheServerSa;
private final OpenShiftClientFactory clientFactory; private final OpenShiftClientFactory clientFactory;
private final CheServerOpenshiftClientFactory cheOpenShiftClientFactory; private final CheServerOpenshiftClientFactory cheOpenShiftClientFactory;
private final OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
private final String oAuthIdentityProvider; private final String oAuthIdentityProvider;
@Inject @Inject
public OpenShiftProjectFactory( public OpenShiftProjectFactory(
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
@Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames,
@Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName, @Nullable @Named("che.infra.kubernetes.namespace.default") String defaultNamespaceName,
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed, @Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
@Named("che.infra.kubernetes.namespace.label") boolean labelProjects, @Named("che.infra.kubernetes.namespace.label") boolean labelProjects,
@ -82,24 +74,23 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
@Named("che.infra.kubernetes.namespace.labels") String projectLabels, @Named("che.infra.kubernetes.namespace.labels") String projectLabels,
@Named("che.infra.kubernetes.namespace.annotations") String projectAnnotations, @Named("che.infra.kubernetes.namespace.annotations") String projectAnnotations,
@Named("che.infra.openshift.project.init_with_server_sa") boolean initWithCheServerSa, @Named("che.infra.openshift.project.init_with_server_sa") boolean initWithCheServerSa,
Set<NamespaceConfigurator> namespaceConfigurators,
OpenShiftClientFactory clientFactory, OpenShiftClientFactory clientFactory,
CheServerKubernetesClientFactory cheClientFactory, CheServerKubernetesClientFactory cheClientFactory,
CheServerOpenshiftClientFactory cheOpenShiftClientFactory, CheServerOpenshiftClientFactory cheOpenShiftClientFactory,
OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner,
UserManager userManager, UserManager userManager,
PreferenceManager preferenceManager, PreferenceManager preferenceManager,
KubernetesSharedPool sharedPool, KubernetesSharedPool sharedPool,
@Nullable @Named("che.infra.openshift.oauth_identity_provider") @Nullable @Named("che.infra.openshift.oauth_identity_provider")
String oAuthIdentityProvider) { String oAuthIdentityProvider) {
super( super(
serviceAccountName,
clusterRoleNames,
defaultNamespaceName, defaultNamespaceName,
namespaceCreationAllowed, namespaceCreationAllowed,
labelProjects, labelProjects,
annotateProjects, annotateProjects,
projectLabels, projectLabels,
projectAnnotations, projectAnnotations,
namespaceConfigurators,
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
userManager, userManager,
@ -108,15 +99,16 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
this.initWithCheServerSa = initWithCheServerSa; this.initWithCheServerSa = initWithCheServerSa;
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.cheOpenShiftClientFactory = cheOpenShiftClientFactory; this.cheOpenShiftClientFactory = cheOpenShiftClientFactory;
this.stopWorkspaceRoleProvisioner = stopWorkspaceRoleProvisioner;
this.oAuthIdentityProvider = oAuthIdentityProvider; this.oAuthIdentityProvider = oAuthIdentityProvider;
} }
public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException { public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
OpenShiftProject osProject = get(identity); OpenShiftProject osProject = get(identity);
var subject = EnvironmentContext.getCurrent().getSubject();
NamespaceResolutionContext resolutionCtx = NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject()); new NamespaceResolutionContext(
identity.getWorkspaceId(), subject.getUserId(), subject.getUserName());
Map<String, String> namespaceAnnotationsEvaluated = Map<String, String> namespaceAnnotationsEvaluated =
evaluateAnnotationPlaceholders(resolutionCtx); evaluateAnnotationPlaceholders(resolutionCtx);
@ -126,50 +118,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
labelNamespaces ? namespaceLabels : emptyMap(), labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap()); annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
// create credentials secret configureNamespace(resolutionCtx, osProject.getName());
if (osProject
.secrets()
.get()
.stream()
.noneMatch(s -> s.getMetadata().getName().equals(CREDENTIALS_SECRET_NAME))) {
Secret secret =
new SecretBuilder()
.withType("opaque")
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.endMetadata()
.build();
clientFactory
.createOC()
.secrets()
.inNamespace(identity.getInfrastructureNamespace())
.create(secret);
}
// create preferences configmap
if (osProject.configMaps().get(PREFERENCES_CONFIGMAP_NAME).isEmpty()) {
ConfigMap configMap =
new ConfigMapBuilder()
.withNewMetadata()
.withName(PREFERENCES_CONFIGMAP_NAME)
.endMetadata()
.build();
clientFactory
.createOC()
.configMaps()
.inNamespace(identity.getInfrastructureNamespace())
.create(configMap);
}
if (!isNullOrEmpty(getServiceAccountName())) {
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =
doCreateServiceAccount(osProject.getWorkspaceId(), osProject.getName());
osWorkspaceServiceAccount.prepare();
}
if (!isNullOrEmpty(oAuthIdentityProvider)) {
stopWorkspaceRoleProvisioner.provision(osProject.getName());
}
return osProject; return osProject;
} }
@ -190,11 +140,6 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
} }
} }
@Override
protected boolean checkNamespaceExists(String namespaceName) throws InfrastructureException {
return fetchNamespaceObject(namespaceName).isPresent();
}
/** /**
* Creates a kubernetes namespace for the specified workspace. * Creates a kubernetes namespace for the specified workspace.
* *
@ -218,12 +163,6 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
workspaceId); workspaceId);
} }
@VisibleForTesting
OpenShiftWorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) {
return new OpenShiftWorkspaceServiceAccount(
workspaceId, projectName, getServiceAccountName(), getClusterRoleNames(), clientFactory);
}
@Override @Override
public Optional<KubernetesNamespaceMeta> fetchNamespace(String name) public Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
throws InfrastructureException { throws InfrastructureException {

View File

@ -33,10 +33,10 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory
* @see * @see
* org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount * org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount
*/ */
class OpenShiftWorkspaceServiceAccount public class OpenShiftWorkspaceServiceAccount
extends AbstractWorkspaceServiceAccount<OpenShiftClient, Role, RoleBinding> { extends AbstractWorkspaceServiceAccount<OpenShiftClient, Role, RoleBinding> {
OpenShiftWorkspaceServiceAccount( public OpenShiftWorkspaceServiceAccount(
String workspaceId, String workspaceId,
String projectName, String projectName,
String serviceAccountName, String serviceAccountName,

View File

@ -9,7 +9,9 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.workspace.infrastructure.openshift.provision; package org.eclipse.che.workspace.infrastructure.openshift.project.configurator;
import static com.google.common.base.Strings.isNullOrEmpty;
import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;
import io.fabric8.openshift.api.model.PolicyRuleBuilder; import io.fabric8.openshift.api.model.PolicyRuleBuilder;
@ -20,8 +22,12 @@ import io.fabric8.openshift.api.model.RoleBuilder;
import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.OpenShiftClient;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.CheInstallationLocation; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.CheInstallationLocation;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,27 +38,37 @@ import org.slf4j.LoggerFactory;
* *
* @author Tom George * @author Tom George
*/ */
public class OpenShiftStopWorkspaceRoleProvisioner { @Singleton
public class OpenShiftStopWorkspaceRoleConfigurator implements NamespaceConfigurator {
private final OpenShiftClientFactory clientFactory; private final OpenShiftClientFactory clientFactory;
private final String installationLocation; private final String installationLocation;
private final boolean stopWorkspaceRoleEnabled; private final boolean stopWorkspaceRoleEnabled;
private final String oAuthIdentityProvider;
private static final Logger LOG = private static final Logger LOG =
LoggerFactory.getLogger(OpenShiftStopWorkspaceRoleProvisioner.class); LoggerFactory.getLogger(OpenShiftStopWorkspaceRoleConfigurator.class);
@Inject @Inject
public OpenShiftStopWorkspaceRoleProvisioner( public OpenShiftStopWorkspaceRoleConfigurator(
OpenShiftClientFactory clientFactory, OpenShiftClientFactory clientFactory,
CheInstallationLocation installationLocation, CheInstallationLocation installationLocation,
@Named("che.workspace.stop.role.enabled") boolean stopWorkspaceRoleEnabled) @Named("che.workspace.stop.role.enabled") boolean stopWorkspaceRoleEnabled,
@Nullable @Named("che.infra.openshift.oauth_identity_provider") String oAuthIdentityProvider)
throws InfrastructureException { throws InfrastructureException {
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.installationLocation = installationLocation.getInstallationLocationNamespace(); this.installationLocation = installationLocation.getInstallationLocationNamespace();
this.stopWorkspaceRoleEnabled = stopWorkspaceRoleEnabled; this.stopWorkspaceRoleEnabled = stopWorkspaceRoleEnabled;
this.oAuthIdentityProvider = oAuthIdentityProvider;
} }
public void provision(String projectName) throws InfrastructureException { @Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String projectName)
throws InfrastructureException {
if (isNullOrEmpty(oAuthIdentityProvider)) {
return;
}
if (stopWorkspaceRoleEnabled && installationLocation != null) { if (stopWorkspaceRoleEnabled && installationLocation != null) {
OpenShiftClient osClient = clientFactory.createOC(); OpenShiftClient osClient = clientFactory.createOC();
String stopWorkspacesRoleName = "workspace-stop"; String stopWorkspacesRoleName = "workspace-stop";

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.openshift.project.configurator;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftWorkspaceServiceAccount;
/**
* This {@link NamespaceConfigurator} ensures that workspace ServiceAccount with proper ClusterRole
* is set in Workspace project.
*/
@Singleton
public class OpenShiftWorkspaceServiceAccountConfigurator implements NamespaceConfigurator {
private final OpenShiftClientFactory clientFactory;
private final String serviceAccountName;
private final Set<String> clusterRoleNames;
@Inject
public OpenShiftWorkspaceServiceAccountConfigurator(
@Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName,
@Nullable @Named("che.infra.kubernetes.workspace_sa_cluster_roles") String clusterRoleNames,
OpenShiftClientFactory clientFactory) {
this.clientFactory = clientFactory;
this.serviceAccountName = serviceAccountName;
if (!isNullOrEmpty(clusterRoleNames)) {
this.clusterRoleNames =
Sets.newHashSet(
Splitter.on(",").trimResults().omitEmptyStrings().split(clusterRoleNames));
} else {
this.clusterRoleNames = Collections.emptySet();
}
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
if (!isNullOrEmpty(serviceAccountName)) {
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =
createServiceAccount(namespaceResolutionContext.getWorkspaceId(), namespaceName);
osWorkspaceServiceAccount.prepare();
}
}
@VisibleForTesting
public OpenShiftWorkspaceServiceAccount createServiceAccount(String wsId, String namespaceName) {
return new OpenShiftWorkspaceServiceAccount(
wsId, namespaceName, serviceAccountName, clusterRoleNames, clientFactory);
}
}

View File

@ -11,9 +11,9 @@
*/ */
package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth; package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;

View File

@ -11,8 +11,6 @@
*/ */
package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth; package org.eclipse.che.workspace.infrastructure.openshift.multiuser.oauth;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.testng.Assert.*; import static org.testng.Assert.*;
@ -21,11 +19,6 @@ import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.User; import io.fabric8.openshift.api.model.User;
import io.fabric8.openshift.client.OpenShiftClient; import io.fabric8.openshift.client.OpenShiftClient;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ServerException;
@ -55,10 +48,6 @@ public class OpenshiftTokenInitializationFilterTest {
@Mock private User openshiftUser; @Mock private User openshiftUser;
@Mock private ObjectMeta openshiftUserMeta; @Mock private ObjectMeta openshiftUserMeta;
@Mock private HttpServletRequest servletRequest;
@Mock private HttpServletResponse servletResponse;
@Mock private FilterChain filterChain;
private static final String TOKEN = "touken"; private static final String TOKEN = "touken";
private static final String USER_UID = "almost-certainly-unique-id"; private static final String USER_UID = "almost-certainly-unique-id";
private static final String USERNAME = "test_username"; private static final String USERNAME = "test_username";
@ -111,7 +100,7 @@ public class OpenshiftTokenInitializationFilterTest {
@Test @Test
public void extractSubjectCreatesSubjectWithCurrentlyAuthenticatedUser() public void extractSubjectCreatesSubjectWithCurrentlyAuthenticatedUser()
throws InfrastructureException, ServerException, ConflictException { throws ServerException, ConflictException {
when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient);
when(openShiftClient.currentUser()).thenReturn(openshiftUser); when(openShiftClient.currentUser()).thenReturn(openshiftUser);
when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta); when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta);
@ -128,27 +117,6 @@ public class OpenshiftTokenInitializationFilterTest {
assertEquals(subject.getUserName(), USERNAME); assertEquals(subject.getUserName(), USERNAME);
} }
@Test
public void handleMissingTokenShouldAllowUnauthorizedEndpoint()
throws ServletException, IOException {
when(servletRequest.getServletPath()).thenReturn("/system/state");
openshiftTokenInitializationFilter.handleMissingToken(
servletRequest, servletResponse, filterChain);
verify(filterChain).doFilter(servletRequest, servletResponse);
}
@Test
public void handleMissingTokenShouldRejectRequest() throws ServletException, IOException {
when(servletRequest.getServletPath()).thenReturn("blabol");
openshiftTokenInitializationFilter.handleMissingToken(
servletRequest, servletResponse, filterChain);
verify(servletResponse).sendError(eq(401), anyString());
}
@Test @Test
public void invalidTokenShouldBeHandledAsMissing() throws Exception { public void invalidTokenShouldBeHandledAsMissing() throws Exception {
when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient); when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient);

View File

@ -13,8 +13,8 @@ package org.eclipse.che.workspace.infrastructure.openshift.project;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Optional.empty;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE; import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
@ -32,7 +32,6 @@ import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
@ -58,7 +57,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Set;
import org.eclipse.che.api.core.ValidationException; import org.eclipse.che.api.core.ValidationException;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.PreferenceManager;
@ -76,12 +75,16 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesCl
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesConfigsMaps;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesSecrets;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftStopWorkspaceRoleProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftWorkspaceServiceAccountConfigurator;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.testng.MockitoTestNGListener; import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.AfterMethod; import org.testng.annotations.AfterMethod;
@ -110,7 +113,6 @@ public class OpenShiftProjectFactoryTest {
@Mock private OpenShiftClientFactory clientFactory; @Mock private OpenShiftClientFactory clientFactory;
@Mock private CheServerKubernetesClientFactory cheClientFactory; @Mock private CheServerKubernetesClientFactory cheClientFactory;
@Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory; @Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory;
@Mock private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
@Mock private WorkspaceManager workspaceManager; @Mock private WorkspaceManager workspaceManager;
@Mock private UserManager userManager; @Mock private UserManager userManager;
@Mock private PreferenceManager preferenceManager; @Mock private PreferenceManager preferenceManager;
@ -131,6 +133,7 @@ public class OpenShiftProjectFactoryTest {
@BeforeMethod @BeforeMethod
public void setUp() throws Exception { public void setUp() throws Exception {
lenient().when(clientFactory.createOC()).thenReturn(osClient); lenient().when(clientFactory.createOC()).thenReturn(osClient);
lenient().when(clientFactory.create()).thenReturn(osClient);
lenient().when(osClient.projects()).thenReturn(projectOperation); lenient().when(osClient.projects()).thenReturn(projectOperation);
lenient() lenient()
@ -162,8 +165,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -171,10 +172,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -195,8 +196,6 @@ public class OpenShiftProjectFactoryTest {
System.out.println("2--------"); System.out.println("2--------");
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -204,10 +203,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -228,8 +227,6 @@ public class OpenShiftProjectFactoryTest {
throws Exception { throws Exception {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
null, null,
true, true,
true, true,
@ -237,10 +234,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -266,8 +263,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
"",
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -275,10 +270,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -305,8 +300,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
"",
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -314,10 +307,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -340,8 +333,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
"",
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -349,10 +340,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -385,8 +376,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -394,10 +383,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -424,8 +413,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -433,10 +420,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -463,8 +450,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -472,10 +457,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -492,8 +477,6 @@ public class OpenShiftProjectFactoryTest {
throwOnTryToGetProjectsList(new KubernetesClientException("connection refused")); throwOnTryToGetProjectsList(new KubernetesClientException("connection refused"));
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<username>-che", "<username>-che",
true, true,
true, true,
@ -501,10 +484,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -526,8 +509,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -535,10 +516,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -554,7 +535,6 @@ public class OpenShiftProjectFactoryTest {
// then // then
assertEquals(toReturnProject, project); assertEquals(toReturnProject, project);
verify(projectFactory, never()).doCreateServiceAccount(any(), any());
verify(toReturnProject).prepare(eq(false), eq(false), any(), any()); verify(toReturnProject).prepare(eq(false), eq(false), any(), any());
} }
@ -564,8 +544,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -573,30 +551,28 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
Set.of(new CredentialsSecretConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
NO_OAUTH_IDENTITY_PROVIDER)); NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class); OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
when(toReturnProject.getName()).thenReturn("namespace123");
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
KubernetesSecrets secrets = mock(KubernetesSecrets.class); when(osClient.secrets()).thenReturn(mixedOperation);
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
when(toReturnProject.secrets()).thenReturn(secrets); Resource<Secret> nullSecret = mock(Resource.class);
when(toReturnProject.configMaps()).thenReturn(configsMaps); when(namespaceOperation.withName(CREDENTIALS_SECRET_NAME)).thenReturn(nullSecret);
when(secrets.get()).thenReturn(Collections.emptyList()); when(nullSecret.get()).thenReturn(null);
when(configsMaps.get(anyString())).thenReturn(Optional.of(mock(ConfigMap.class)));
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123"); new RuntimeIdentityImpl("workspace123", null, USER_ID, "namespace123");
projectFactory.getOrCreate(identity); projectFactory.getOrCreate(identity);
// then // then
@ -613,8 +589,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -622,31 +596,23 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
NO_OAUTH_IDENTITY_PROVIDER)); NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class); OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
when(toReturnProject.getName()).thenReturn("namespace123");
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
KubernetesSecrets secrets = mock(KubernetesSecrets.class); when(osClient.configMaps()).thenReturn(mixedOperation);
Secret secret = mock(Secret.class); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
ObjectMeta objectMeta = mock(ObjectMeta.class); Resource<ConfigMap> nullCm = mock(Resource.class);
when(secret.getMetadata()).thenReturn(objectMeta); when(namespaceOperation.withName(PREFERENCES_CONFIGMAP_NAME)).thenReturn(nullCm);
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
when(toReturnProject.secrets()).thenReturn(secrets);
when(secrets.get()).thenReturn(singletonList(secret));
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class);
when(toReturnProject.configMaps()).thenReturn(configsMaps);
when(configsMaps.get(eq(PREFERENCES_CONFIGMAP_NAME))).thenReturn(empty());
lenient().when(osClient.configMaps()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -666,8 +632,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -675,10 +639,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
Set.of(new CredentialsSecretConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -686,10 +650,14 @@ public class OpenShiftProjectFactoryTest {
OpenShiftProject toReturnProject = mock(OpenShiftProject.class); OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject); prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
when(toReturnProject.getName()).thenReturn("namespace123");
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(osClient.secrets()).thenReturn(mixedOperation); when(osClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
Resource<Secret> secretResource = mock(Resource.class);
when(namespaceOperation.withName(CREDENTIALS_SECRET_NAME)).thenReturn(secretResource);
when(secretResource.get()).thenReturn(mock(Secret.class));
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -706,8 +674,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -715,10 +681,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -726,10 +692,14 @@ public class OpenShiftProjectFactoryTest {
OpenShiftProject toReturnProject = mock(OpenShiftProject.class); OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject); prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
when(toReturnProject.getName()).thenReturn("namespace123");
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class); NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class); MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(osClient.configMaps()).thenReturn(mixedOperation); when(osClient.configMaps()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation); when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
Resource<ConfigMap> cmResource = mock(Resource.class);
when(namespaceOperation.withName(PREFERENCES_CONFIGMAP_NAME)).thenReturn(cmResource);
when(cmResource.get()).thenReturn(mock(ConfigMap.class));
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -740,56 +710,13 @@ public class OpenShiftProjectFactoryTest {
verify(namespaceOperation, never()).create(any()); verify(namespaceOperation, never()).create(any());
} }
@Test
public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndProjectIsNotPredefined()
throws Exception {
// given
projectFactory =
spy(
new OpenShiftProjectFactory(
"serviceAccount",
null,
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123");
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any());
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
projectFactory.getOrCreate(identity);
// then
verify(projectFactory).doCreateServiceAccount("workspace123", "workspace123");
verify(serviceAccount).prepare();
}
@Test @Test
public void shouldCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined() throws Exception { public void shouldCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined() throws Exception {
var saConf =
spy(new OpenShiftWorkspaceServiceAccountConfigurator("serviceAccount", "", clientFactory));
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"serviceAccount",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -797,22 +724,21 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
Set.of(saConf),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
OAUTH_IDENTITY_PROVIDER)); OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class); OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123"); when(toReturnProject.getName()).thenReturn("workspace123");
prepareProject(toReturnProject); prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any()); doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class); OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any()); doReturn(serviceAccount).when(saConf).createServiceAccount("workspace123", "workspace123");
// when // when
RuntimeIdentity identity = RuntimeIdentity identity =
@ -820,52 +746,7 @@ public class OpenShiftProjectFactoryTest {
projectFactory.getOrCreate(identity); projectFactory.getOrCreate(identity);
// then // then
verify(projectFactory).doCreateServiceAccount("workspace123", "workspace123");
verify(serviceAccount).prepare(); verify(serviceAccount).prepare();
verify(stopWorkspaceRoleProvisioner, times(1)).provision("workspace123");
}
@Test
public void shouldNotCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined()
throws Exception {
projectFactory =
spy(
new OpenShiftProjectFactory(
"serviceAccount",
null,
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123");
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any());
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
projectFactory.getOrCreate(identity);
// then
verify(projectFactory).doCreateServiceAccount("workspace123", "workspace123");
verify(serviceAccount).prepare();
verify(stopWorkspaceRoleProvisioner, times(0)).provision("workspace123");
} }
@Test @Test
@ -886,8 +767,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
"",
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -895,10 +774,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -921,8 +800,6 @@ public class OpenShiftProjectFactoryTest {
projectFactory = projectFactory =
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -930,10 +807,10 @@ public class OpenShiftProjectFactoryTest {
"try_placeholder_here=<username>", "try_placeholder_here=<username>",
NAMESPACE_ANNOTATIONS, NAMESPACE_ANNOTATIONS,
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -946,13 +823,10 @@ public class OpenShiftProjectFactoryTest {
@Test @Test
public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException { public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException {
// given // given
projectFactory = projectFactory =
spy( spy(
new OpenShiftProjectFactory( new OpenShiftProjectFactory(
"",
null,
"<userid>-che", "<userid>-che",
true, true,
true, true,
@ -960,10 +834,10 @@ public class OpenShiftProjectFactoryTest {
NAMESPACE_LABELS, NAMESPACE_LABELS,
"try_placeholder_here=<username>", "try_placeholder_here=<username>",
true, true,
emptySet(),
clientFactory, clientFactory,
cheClientFactory, cheClientFactory,
cheServerOpenshiftClientFactory, cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager, userManager,
preferenceManager, preferenceManager,
pool, pool,
@ -983,6 +857,51 @@ public class OpenShiftProjectFactoryTest {
.prepare(eq(false), eq(false), any(), eq(Map.of("try_placeholder_here", "jondoe"))); .prepare(eq(false), eq(false), any(), eq(Map.of("try_placeholder_here", "jondoe")));
} }
@Test
public void testAllConfiguratorsAreCalledWhenCreatingProject() throws InfrastructureException {
// given
String projectName = "testprojectname";
NamespaceConfigurator configurator1 = Mockito.mock(NamespaceConfigurator.class);
NamespaceConfigurator configurator2 = Mockito.mock(NamespaceConfigurator.class);
Set<NamespaceConfigurator> namespaceConfigurators = Set.of(configurator1, configurator2);
projectFactory =
spy(
new OpenShiftProjectFactory(
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
"try_placeholder_here=<username>",
true,
namespaceConfigurators,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
when(toReturnProject.getName()).thenReturn(projectName);
RuntimeIdentity identity = new RuntimeIdentityImpl("workspace123", null, USER_ID, "old-che");
doReturn(toReturnProject).when(projectFactory).get(identity);
// when
OpenShiftProject project = projectFactory.getOrCreate(identity);
// then
NamespaceResolutionContext resolutionCtx =
new NamespaceResolutionContext("workspace123", "123", "jondoe");
verify(configurator1).configure(resolutionCtx, projectName);
verify(configurator2).configure(resolutionCtx, projectName);
assertEquals(project, toReturnProject);
}
private void prepareNamespaceToBeFoundByName(String name, Project project) throws Exception { private void prepareNamespaceToBeFoundByName(String name, Project project) throws Exception {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Resource<Project> getProjectByNameOperation = mock(Resource.class); Resource<Project> getProjectByNameOperation = mock(Resource.class);
@ -1010,15 +929,13 @@ public class OpenShiftProjectFactoryTest {
private void prepareProject(OpenShiftProject project) throws InfrastructureException { private void prepareProject(OpenShiftProject project) throws InfrastructureException {
KubernetesSecrets secrets = mock(KubernetesSecrets.class); KubernetesSecrets secrets = mock(KubernetesSecrets.class);
lenient().when(project.secrets()).thenReturn(secrets);
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class); KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class);
when(project.secrets()).thenReturn(secrets);
when(project.configMaps()).thenReturn(configsMaps);
when(configsMaps.get(anyString())).thenReturn(Optional.of(mock(ConfigMap.class)));
Secret secretMock = mock(Secret.class); Secret secretMock = mock(Secret.class);
ObjectMeta objectMeta = mock(ObjectMeta.class); ObjectMeta objectMeta = mock(ObjectMeta.class);
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME); lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
when(secretMock.getMetadata()).thenReturn(objectMeta); lenient().when(secretMock.getMetadata()).thenReturn(objectMeta);
when(secrets.get()).thenReturn(singletonList(secretMock)); lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
} }
private void throwOnTryToGetProjectsList(Throwable e) throws Exception { private void throwOnTryToGetProjectsList(Throwable e) throws Exception {

View File

@ -9,7 +9,7 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.workspace.infrastructure.openshift.provision; package org.eclipse.che.workspace.infrastructure.openshift.project.configurator;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.anyString;
@ -17,6 +17,7 @@ import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;
@ -42,15 +43,16 @@ import org.testng.annotations.Listeners;
import org.testng.annotations.Test; import org.testng.annotations.Test;
/** /**
* Test for {@link OpenShiftStopWorkspaceRoleProvisioner} * Test for {@link
* org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftStopWorkspaceRoleConfigurator}
* *
* <p>#author Tom George * <p>#author Tom George
*/ */
@Listeners(MockitoTestNGListener.class) @Listeners(MockitoTestNGListener.class)
public class OpenShiftStopWorkspaceRoleProvisionerTest { public class OpenShiftStopWorkspaceRoleConfiguratorTest {
@Mock private CheInstallationLocation cheInstallationLocation; @Mock private CheInstallationLocation cheInstallationLocation;
private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner; private OpenShiftStopWorkspaceRoleConfigurator stopWorkspaceRoleProvisioner;
@Mock private OpenShiftClientFactory clientFactory; @Mock private OpenShiftClientFactory clientFactory;
@Mock private OpenShiftClient osClient; @Mock private OpenShiftClient osClient;
@ -123,7 +125,8 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
public void setUp() throws Exception { public void setUp() throws Exception {
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che"); lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che");
stopWorkspaceRoleProvisioner = stopWorkspaceRoleProvisioner =
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true); new OpenShiftStopWorkspaceRoleConfigurator(
clientFactory, cheInstallationLocation, true, "yes");
lenient().when(clientFactory.createOC()).thenReturn(osClient); lenient().when(clientFactory.createOC()).thenReturn(osClient);
lenient().when(osClient.roles()).thenReturn(mixedRoleOperation); lenient().when(osClient.roles()).thenReturn(mixedRoleOperation);
lenient().when(osClient.roleBindings()).thenReturn(mixedRoleBindingOperation); lenient().when(osClient.roleBindings()).thenReturn(mixedRoleBindingOperation);
@ -160,7 +163,7 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
@Test @Test
public void shouldCreateRoleAndRoleBindingWhenRoleDoesNotYetExist() public void shouldCreateRoleAndRoleBindingWhenRoleDoesNotYetExist()
throws InfrastructureException { throws InfrastructureException {
stopWorkspaceRoleProvisioner.provision("developer-che"); stopWorkspaceRoleProvisioner.configure(null, "developer-che");
verify(osClient, times(2)).roles(); verify(osClient, times(2)).roles();
verify(osClient.roles(), times(2)).inNamespace("developer-che"); verify(osClient.roles(), times(2)).inNamespace("developer-che");
verify(osClient.roles().inNamespace("developer-che")).withName("workspace-stop"); verify(osClient.roles().inNamespace("developer-che")).withName("workspace-stop");
@ -174,7 +177,7 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
@Test @Test
public void shouldCreateRoleBindingWhenRoleAlreadyExists() throws InfrastructureException { public void shouldCreateRoleBindingWhenRoleAlreadyExists() throws InfrastructureException {
lenient().when(roleResource.get()).thenReturn(expectedRole); lenient().when(roleResource.get()).thenReturn(expectedRole);
stopWorkspaceRoleProvisioner.provision("developer-che"); stopWorkspaceRoleProvisioner.configure(null, "developer-che");
verify(osClient, times(1)).roles(); verify(osClient, times(1)).roles();
verify(osClient).roleBindings(); verify(osClient).roleBindings();
verify(osClient.roleBindings()).inNamespace("developer-che"); verify(osClient.roleBindings()).inNamespace("developer-che");
@ -185,9 +188,10 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
@Test @Test
public void shouldNotCreateRoleBindingWhenStopWorkspaceRolePropertyIsDisabled() public void shouldNotCreateRoleBindingWhenStopWorkspaceRolePropertyIsDisabled()
throws InfrastructureException { throws InfrastructureException {
OpenShiftStopWorkspaceRoleProvisioner disabledStopWorkspaceRoleProvisioner = OpenShiftStopWorkspaceRoleConfigurator disabledStopWorkspaceRoleProvisioner =
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, false); new OpenShiftStopWorkspaceRoleConfigurator(
disabledStopWorkspaceRoleProvisioner.provision("developer-che"); clientFactory, cheInstallationLocation, false, "yes");
disabledStopWorkspaceRoleProvisioner.configure(null, "developer-che");
verify(osClient, never()).roles(); verify(osClient, never()).roles();
verify(osClient, never()).roleBindings(); verify(osClient, never()).roleBindings();
verify(osClient.roleBindings(), never()).inNamespace("developer-che"); verify(osClient.roleBindings(), never()).inNamespace("developer-che");
@ -197,12 +201,26 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
public void shouldNotCreateRoleBindingWhenInstallationLocationIsNull() public void shouldNotCreateRoleBindingWhenInstallationLocationIsNull()
throws InfrastructureException { throws InfrastructureException {
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn(null); lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn(null);
OpenShiftStopWorkspaceRoleProvisioner OpenShiftStopWorkspaceRoleConfigurator
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation = stopWorkspaceRoleProvisionerWithoutValidInstallationLocation =
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true); new OpenShiftStopWorkspaceRoleConfigurator(
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.provision("developer-che"); clientFactory, cheInstallationLocation, true, "yes");
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.configure(null, "developer-che");
verify(osClient, never()).roles(); verify(osClient, never()).roles();
verify(osClient, never()).roleBindings(); verify(osClient, never()).roleBindings();
verify(osClient.roleBindings(), never()).inNamespace("developer-che"); verify(osClient.roleBindings(), never()).inNamespace("developer-che");
} }
@Test
public void shouldNotCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined()
throws Exception {
when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("something");
OpenShiftStopWorkspaceRoleConfigurator configurator =
new OpenShiftStopWorkspaceRoleConfigurator(
clientFactory, cheInstallationLocation, true, null);
configurator.configure(null, "something");
verify(clientFactory, times(0)).createOC();
}
} }

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.workspace.infrastructure.openshift.project.configurator;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.*;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftWorkspaceServiceAccount;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class OpenShiftWorkspaceServiceAccountConfiguratorTest {
private final String SA_NAME = "test-serviceaccout";
private final String CLUSTER_ROLES = "role1, role2";
private final String WS_ID = "ws123";
private final String USER_ID = "user123";
private final String USERNAME = "user-che";
private final String NS_NAME = "namespace-che";
private NamespaceResolutionContext nsContext;
@Mock private OpenShiftClientFactory clientFactory;
private OpenShiftWorkspaceServiceAccountConfigurator saConfigurator;
@BeforeMethod
public void setUp() {
nsContext = new NamespaceResolutionContext(WS_ID, USER_ID, USERNAME);
}
@Test
public void testPreparesServiceAccount() throws InfrastructureException {
saConfigurator =
spy(
new OpenShiftWorkspaceServiceAccountConfigurator(
SA_NAME, CLUSTER_ROLES, clientFactory));
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
doReturn(serviceAccount).when(saConfigurator).createServiceAccount(WS_ID, NS_NAME);
saConfigurator.configure(nsContext, NS_NAME);
verify(serviceAccount).prepare();
}
@Test
public void testDoNothingWhenServiceAccountNotSet() throws InfrastructureException {
saConfigurator =
spy(new OpenShiftWorkspaceServiceAccountConfigurator(null, CLUSTER_ROLES, clientFactory));
saConfigurator.configure(nsContext, NS_NAME);
verify(saConfigurator, times(0)).createServiceAccount(any(), any());
}
}

View File

@ -23,6 +23,8 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.Subject;
@ -43,6 +45,9 @@ import org.slf4j.LoggerFactory;
* <li>Set subject for current request into {@link EnvironmentContext} * <li>Set subject for current request into {@link EnvironmentContext}
* </ul> * </ul>
* *
* <p>{@link MultiUserEnvironmentInitializationFilter#UNAUTHORIZED_ENDPOINT_PATHS} is list of
* unauthenticated paths, that are allowed without token.
*
* @param <T> the type of intermediary type used for conversion from a string token to a Subject * @param <T> the type of intermediary type used for conversion from a string token to a Subject
* @author Max Shaposhnyk (mshaposh@redhat.com) * @author Max Shaposhnyk (mshaposh@redhat.com)
*/ */
@ -51,6 +56,9 @@ public abstract class MultiUserEnvironmentInitializationFilter<T> implements Fil
private static final Logger LOG = private static final Logger LOG =
LoggerFactory.getLogger(MultiUserEnvironmentInitializationFilter.class); LoggerFactory.getLogger(MultiUserEnvironmentInitializationFilter.class);
private static final List<String> UNAUTHORIZED_ENDPOINT_PATHS =
Collections.singletonList("/system/state");
private final SessionStore sessionStore; private final SessionStore sessionStore;
private final RequestTokenExtractor tokenExtractor; private final RequestTokenExtractor tokenExtractor;
@ -197,9 +205,23 @@ public abstract class MultiUserEnvironmentInitializationFilter<T> implements Fil
* @throws IOException inherited from {@link FilterChain#doFilter} * @throws IOException inherited from {@link FilterChain#doFilter}
* @throws ServletException inherited from {@link FilterChain#doFilter} * @throws ServletException inherited from {@link FilterChain#doFilter}
*/ */
protected abstract void handleMissingToken( protected void handleMissingToken(
ServletRequest request, ServletResponse response, FilterChain chain) ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException; throws IOException, ServletException {
// if request path is in unauthorized endpoints, continue
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getServletPath();
if (UNAUTHORIZED_ENDPOINT_PATHS.contains(path)) {
LOG.debug("Allowing request to '{}' without authorization header.", path);
chain.doFilter(request, response);
return;
}
}
LOG.error("Rejecting the request due to missing/expired token in Authorization header.");
sendError(response, 401, "Authorization token is missing or expired");
}
/** /**
* Sends appropriate error status code and message into response. * Sends appropriate error status code and message into response.

View File

@ -25,9 +25,11 @@ import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings; import static org.mockito.Mockito.withSettings;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.Subject;
@ -83,6 +85,7 @@ public class MultiUserEnvironmentInitializationFilterTest {
// then // then
verify(tokenExtractor).getToken(eq(request)); verify(tokenExtractor).getToken(eq(request));
verify(filter).handleMissingToken(eq(request), eq(response), eq(chain)); verify(filter).handleMissingToken(eq(request), eq(response), eq(chain));
verify(request).getServletPath();
verifyNoMoreInteractions(request); verifyNoMoreInteractions(request);
verify(filter, never()).getUserId(any()); verify(filter, never()).getUserId(any());
verify(filter, never()).extractSubject(anyString(), any()); verify(filter, never()).extractSubject(anyString(), any());
@ -100,6 +103,7 @@ public class MultiUserEnvironmentInitializationFilterTest {
// then // then
verify(tokenExtractor).getToken(eq(request)); verify(tokenExtractor).getToken(eq(request));
verify(filter).handleMissingToken(eq(request), eq(response), eq(chain)); verify(filter).handleMissingToken(eq(request), eq(response), eq(chain));
verify(request).getServletPath();
verifyNoMoreInteractions(request); verifyNoMoreInteractions(request);
verify(filter, never()).getUserId(any()); verify(filter, never()).getUserId(any());
verify(filter, never()).extractSubject(anyString(), any()); verify(filter, never()).extractSubject(anyString(), any());
@ -168,4 +172,23 @@ public class MultiUserEnvironmentInitializationFilterTest {
// then // then
verify(context).setSubject(eq(subject)); verify(context).setSubject(eq(subject));
} }
@Test
public void handleMissingTokenShouldAllowUnauthorizedEndpoint()
throws ServletException, IOException {
when(request.getServletPath()).thenReturn("/system/state");
filter.handleMissingToken(request, response, chain);
verify(chain).doFilter(request, response);
}
@Test
public void handleMissingTokenShouldRejectRequest() throws ServletException, IOException {
when(request.getServletPath()).thenReturn("blabol");
filter.handleMissingToken(request, response, chain);
verify(response).sendError(eq(401), anyString());
}
} }

View File

@ -30,14 +30,6 @@
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId> <artifactId>jwks-rsa</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
@ -102,10 +94,6 @@
<groupId>org.eclipse.che.core</groupId> <groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId> <artifactId>che-core-commons-annotations</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.che.core</groupId> <groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId> <artifactId>che-core-commons-lang</artifactId>
@ -130,6 +118,10 @@
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication-shared</artifactId> <artifactId>che-multiuser-machine-authentication-shared</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-oidc</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-personal-account</artifactId> <artifactId>che-multiuser-personal-account</artifactId>

View File

@ -13,6 +13,7 @@ package org.eclipse.che.multiuser.keycloak.server;
import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.isNullOrEmpty;
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
@ -43,7 +44,6 @@ import org.eclipse.che.multiuser.api.authentication.commons.filter.MultiUserEnvi
import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor;
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject; import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -123,8 +123,7 @@ public class KeycloakEnvironmentInitializationFilter
try { try {
String username = String username =
claims.get( claims.get(keycloakSettings.get().get(OIDC_USERNAME_CLAIM_SETTING), String.class);
keycloakSettings.get().get(KeycloakConstants.USERNAME_CLAIM_SETTING), String.class);
if (username == null) { // fallback to unique id promised by spec if (username == null) { // fallback to unique id promised by spec
// https://openid.net/specs/openid-connect-basic-1_0.html#ClaimStability // https://openid.net/specs/openid-connect-basic-1_0.html#ClaimStability
username = claims.getIssuer() + ":" + claims.getSubject(); username = claims.getIssuer() + ":" + claims.getSubject();

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.multiuser.oidc.OIDCInfoProvider;
/**
* KeycloakOIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint.
* These information is useful to provide access to the Keycloak api.
*/
public class KeycloakOIDCInfoProvider extends OIDCInfoProvider {
public final String realm;
@Inject
public KeycloakOIDCInfoProvider(
@Nullable @Named(AUTH_SERVER_URL_SETTING) String serverURL,
@Nullable @Named(AUTH_SERVER_URL_INTERNAL_SETTING) String serverInternalURL,
@Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl,
@Nullable @Named(REALM_SETTING) String realm) {
super(serverURL, serverInternalURL, oidcProviderUrl);
this.realm = realm;
}
@Override
protected String constructServerAuthUrl(String serverAuthUrl) {
return serverAuthUrl + "/realms/" + realm;
}
protected void validate() {
if (oidcProviderUrl == null && realm == null) {
throw new RuntimeException("The '" + REALM_SETTING + "' property must be set");
}
super.validate();
}
}

View File

@ -21,6 +21,7 @@ import javax.inject.Singleton;
import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -52,6 +52,7 @@ import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse; import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse; import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -75,7 +76,7 @@ public class KeycloakServiceClient {
Pattern.compile("<div id=\"kc-error-message\">(\\s*)<p class=\"instruction\">(.+?)</p>"); Pattern.compile("<div id=\"kc-error-message\">(\\s*)<p class=\"instruction\">(.+?)</p>");
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private JwtParser jwtParser; private final JwtParser jwtParser;
@Inject @Inject
public KeycloakServiceClient( public KeycloakServiceClient(

View File

@ -11,7 +11,6 @@
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_IDE; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_IDE;
@ -19,16 +18,17 @@ import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.GITHUB
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JS_ADAPTER_URL_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JS_ADAPTER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JWKS_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JWKS_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.LOGOUT_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.LOGOUT_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OIDC_PROVIDER_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OSO_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OSO_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PASSWORD_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PASSWORD_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PROFILE_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PROFILE_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.TOKEN_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.TOKEN_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERINFO_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERINFO_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERNAME_CLAIM_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_FIXED_REDIRECT_URLS_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_FIXED_REDIRECT_URLS_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_PROVIDER_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import java.util.Collections; import java.util.Collections;
@ -37,6 +37,7 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
/** @author Max Shaposhnik (mshaposh@redhat.com) */ /** @author Max Shaposhnik (mshaposh@redhat.com) */
@Singleton @Singleton
@ -54,7 +55,7 @@ public class KeycloakSettings {
@Nullable @Named(REALM_SETTING) String realm, @Nullable @Named(REALM_SETTING) String realm,
@Named(CLIENT_ID_SETTING) String clientId, @Named(CLIENT_ID_SETTING) String clientId,
@Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl, @Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl,
@Nullable @Named(USERNAME_CLAIM_SETTING) String usernameClaim, @Nullable @Named(OIDC_USERNAME_CLAIM_SETTING) String usernameClaim,
@Named(USE_NONCE_SETTING) boolean useNonce, @Named(USE_NONCE_SETTING) boolean useNonce,
@Nullable @Named(OSO_ENDPOINT_SETTING) String osoEndpoint, @Nullable @Named(OSO_ENDPOINT_SETTING) String osoEndpoint,
@Nullable @Named(GITHUB_ENDPOINT_SETTING) String gitHubEndpoint, @Nullable @Named(GITHUB_ENDPOINT_SETTING) String gitHubEndpoint,
@ -64,7 +65,8 @@ public class KeycloakSettings {
Map<String, String> settings = Maps.newHashMap(); Map<String, String> settings = Maps.newHashMap();
settings.put( settings.put(
USERNAME_CLAIM_SETTING, usernameClaim == null ? DEFAULT_USERNAME_CLAIM : usernameClaim); OIDC_USERNAME_CLAIM_SETTING,
usernameClaim == null ? DEFAULT_USERNAME_CLAIM : usernameClaim);
settings.put(CLIENT_ID_SETTING, clientId); settings.put(CLIENT_ID_SETTING, clientId);
settings.put(REALM_SETTING, realm); settings.put(REALM_SETTING, realm);
@ -80,9 +82,8 @@ public class KeycloakSettings {
serverURL + "/realms/" + realm + "/protocol/openid-connect/token"); serverURL + "/realms/" + realm + "/protocol/openid-connect/token");
} }
if (oidcInfo.getEndSessionPublicEndpoint() != null) { oidcInfo.getEndSessionPublicEndpoint().ifPresent(e -> settings.put(LOGOUT_ENDPOINT_SETTING, e));
settings.put(LOGOUT_ENDPOINT_SETTING, oidcInfo.getEndSessionPublicEndpoint());
}
if (oidcInfo.getTokenPublicEndpoint() != null) { if (oidcInfo.getTokenPublicEndpoint() != null) {
settings.put(TOKEN_ENDPOINT_SETTING, oidcInfo.getTokenPublicEndpoint()); settings.put(TOKEN_ENDPOINT_SETTING, oidcInfo.getTokenPublicEndpoint());
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012-2018 Red Hat, Inc. * Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made * This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0 * available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/ * which is available at https://www.eclipse.org/legal/epl-2.0/
@ -13,30 +13,20 @@ package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND; import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider; import com.auth0.jwk.JwkProvider;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import java.security.Key; import java.security.Key;
import java.security.PublicKey;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.slf4j.Logger; import org.eclipse.che.multiuser.oidc.OIDCSigningKeyResolver;
import org.slf4j.LoggerFactory;
/** Resolves signing key based on id from JWT header */ /** Resolves signing key based on id from JWT header */
@Singleton @Singleton
public class KeycloakSigningKeyResolver extends SigningKeyResolverAdapter { public class KeycloakSigningKeyResolver extends OIDCSigningKeyResolver {
private final JwkProvider jwkProvider;
private static final Logger LOG = LoggerFactory.getLogger(KeycloakSigningKeyResolver.class);
@Inject @Inject
KeycloakSigningKeyResolver(JwkProvider jwkProvider) { KeycloakSigningKeyResolver(JwkProvider jwkProvider) {
this.jwkProvider = jwkProvider; super(jwkProvider);
} }
@Override @Override
@ -54,19 +44,4 @@ public class KeycloakSigningKeyResolver extends SigningKeyResolverAdapter {
} }
return getJwtPublicKey(header); return getJwtPublicKey(header);
} }
private synchronized PublicKey getJwtPublicKey(JwsHeader<?> header) {
String kid = header.getKeyId();
if (header.getKeyId() == null) {
LOG.warn(
"'kid' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys");
throw new JwtException("'kid' is missing in the JWT token header.");
}
try {
return jwkProvider.get(kid).getPublicKey();
} catch (JwkException e) {
throw new JwtException(
"Error during the retrieval of the public key during JWT token validation", e);
}
}
} }

View File

@ -14,18 +14,20 @@ package org.eclipse.che.multiuser.keycloak.server.deploy;
import com.auth0.jwk.JwkProvider; import com.auth0.jwk.JwkProvider;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SigningKeyResolver;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.user.server.TokenValidator; import org.eclipse.che.api.user.server.TokenValidator;
import org.eclipse.che.api.user.server.spi.ProfileDao; import org.eclipse.che.api.user.server.spi.ProfileDao;
import org.eclipse.che.multiuser.api.account.personal.PersonalAccountUserManager; import org.eclipse.che.multiuser.api.account.personal.PersonalAccountUserManager;
import org.eclipse.che.multiuser.keycloak.server.KeycloakConfigurationService; import org.eclipse.che.multiuser.keycloak.server.KeycloakConfigurationService;
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwkProvider; import org.eclipse.che.multiuser.keycloak.server.KeycloakOIDCInfoProvider;
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwtParserProvider; import org.eclipse.che.multiuser.keycloak.server.KeycloakSigningKeyResolver;
import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator; import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator;
import org.eclipse.che.multiuser.keycloak.server.KeycloakUserManager; import org.eclipse.che.multiuser.keycloak.server.KeycloakUserManager;
import org.eclipse.che.multiuser.keycloak.server.OIDCInfo;
import org.eclipse.che.multiuser.keycloak.server.OIDCInfoProvider;
import org.eclipse.che.multiuser.keycloak.server.dao.KeycloakProfileDao; import org.eclipse.che.multiuser.keycloak.server.dao.KeycloakProfileDao;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.eclipse.che.multiuser.oidc.OIDCJwkProvider;
import org.eclipse.che.multiuser.oidc.OIDCJwtParserProvider;
import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth.OAuthAPI;
public class KeycloakModule extends AbstractModule { public class KeycloakModule extends AbstractModule {
@ -38,9 +40,10 @@ public class KeycloakModule extends AbstractModule {
bind(KeycloakConfigurationService.class); bind(KeycloakConfigurationService.class);
bind(ProfileDao.class).to(KeycloakProfileDao.class); bind(ProfileDao.class).to(KeycloakProfileDao.class);
bind(JwkProvider.class).toProvider(KeycloakJwkProvider.class); bind(JwkProvider.class).toProvider(OIDCJwkProvider.class);
bind(JwtParser.class).toProvider(KeycloakJwtParserProvider.class); bind(SigningKeyResolver.class).to(KeycloakSigningKeyResolver.class);
bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton(); bind(JwtParser.class).toProvider(OIDCJwtParserProvider.class);
bind(OIDCInfo.class).toProvider(KeycloakOIDCInfoProvider.class).asEagerSingleton();
bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class); bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class);
bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class); bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class);

View File

@ -12,7 +12,7 @@
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.api.authentication.commons.Constants.CHE_SUBJECT_ATTRIBUTE; import static org.eclipse.che.multiuser.api.authentication.commons.Constants.CHE_SUBJECT_ATTRIBUTE;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERNAME_CLAIM_SETTING; import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@ -50,7 +50,6 @@ import org.eclipse.che.multiuser.api.authentication.commons.SessionStore;
import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor; import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor;
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject; import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager; import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
@ -119,7 +118,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, ""); DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token"); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws); when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws);
keycloakSettingsMap.put(USERNAME_CLAIM_SETTING, "preferred_username"); keycloakSettingsMap.put(OIDC_USERNAME_CLAIM_SETTING, "preferred_username");
when(userManager.getOrCreateUser(anyString(), anyString(), anyString())) when(userManager.getOrCreateUser(anyString(), anyString(), anyString()))
.thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS)); .thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS));
filter = filter =
@ -149,7 +148,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, ""); DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token"); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws); when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws);
keycloakSettingsMap.put(USERNAME_CLAIM_SETTING, "preferred_username"); keycloakSettingsMap.put(OIDC_USERNAME_CLAIM_SETTING, "preferred_username");
when(userManager.getOrCreateUser(anyString(), anyString(), anyString())) when(userManager.getOrCreateUser(anyString(), anyString(), anyString()))
.thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS)); .thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS));
filter = filter =
@ -210,7 +209,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
Claims claims = new DefaultClaims(claimParams).setSubject("id"); Claims claims = new DefaultClaims(claimParams).setSubject("id");
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, ""); DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
UserImpl user = new UserImpl("id", "test@test.com", "username"); UserImpl user = new UserImpl("id", "test@test.com", "username");
keycloakSettingsMap.put(KeycloakConstants.USERNAME_CLAIM_SETTING, "preferred_username"); keycloakSettingsMap.put(OIDC_USERNAME_CLAIM_SETTING, "preferred_username");
// given // given
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token"); when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws); when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws);

View File

@ -49,6 +49,7 @@ import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse; import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse;
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse; import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.everrest.assured.EverrestJetty; import org.everrest.assured.EverrestJetty;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener; import org.mockito.testng.MockitoTestNGListener;

View File

@ -12,7 +12,6 @@
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.keycloak.server.KeycloakSettings.DEFAULT_USERNAME_CLAIM; import static org.eclipse.che.multiuser.keycloak.server.KeycloakSettings.DEFAULT_USERNAME_CLAIM;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_DASHBOARD;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_IDE; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.FIXED_REDIRECT_URL_FOR_IDE;
@ -20,20 +19,23 @@ import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.GITHUB
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JS_ADAPTER_URL_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JS_ADAPTER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JWKS_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.JWKS_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.LOGOUT_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.LOGOUT_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OIDC_PROVIDER_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OSO_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OSO_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PASSWORD_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PASSWORD_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PROFILE_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.PROFILE_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.TOKEN_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.TOKEN_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERINFO_ENDPOINT_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERINFO_ENDPOINT_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USERNAME_CLAIM_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.USE_NONCE_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_PROVIDER_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener; import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners; import org.testng.annotations.Listeners;
@ -185,7 +187,8 @@ public class KeycloakSettingsTest {
public void shouldBeUsedConfigurationFromExternalOIDCProviderWithoutFixedRedirectLinks() { public void shouldBeUsedConfigurationFromExternalOIDCProviderWithoutFixedRedirectLinks() {
final String SERVER_AUTH_URL = "https://external-keycloak-che.apps-crc.testing/auth"; final String SERVER_AUTH_URL = "https://external-keycloak-che.apps-crc.testing/auth";
when(oidcInfo.getEndSessionPublicEndpoint()).thenReturn(SERVER_AUTH_URL + LOGOUT_URL_PATH); when(oidcInfo.getEndSessionPublicEndpoint())
.thenReturn(Optional.of(SERVER_AUTH_URL + LOGOUT_URL_PATH));
when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH);
when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH); when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH);
when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH); when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH);
@ -206,7 +209,7 @@ public class KeycloakSettingsTest {
oidcInfo); oidcInfo);
Map<String, String> publicSettings = settings.get(); Map<String, String> publicSettings = settings.get();
assertEquals(publicSettings.get(USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM); assertEquals(publicSettings.get(OIDC_USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM);
assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID); assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID);
assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM); assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM);
assertNull(publicSettings.get(AUTH_SERVER_URL_SETTING)); assertNull(publicSettings.get(AUTH_SERVER_URL_SETTING));
@ -229,7 +232,8 @@ public class KeycloakSettingsTest {
public void shouldBeUsedConfigurationFromExternalAuthServer() { public void shouldBeUsedConfigurationFromExternalAuthServer() {
final String SERVER_AUTH_URL = "https://keycloak-che.apps-crc.testing/auth"; final String SERVER_AUTH_URL = "https://keycloak-che.apps-crc.testing/auth";
when(oidcInfo.getEndSessionPublicEndpoint()).thenReturn(SERVER_AUTH_URL + LOGOUT_URL_PATH); when(oidcInfo.getEndSessionPublicEndpoint())
.thenReturn(Optional.of(SERVER_AUTH_URL + LOGOUT_URL_PATH));
when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH); when(oidcInfo.getJwksPublicUri()).thenReturn(SERVER_AUTH_URL + JWKS_ENDPOINT_PATH);
when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH); when(oidcInfo.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH);
when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH); when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH);
@ -250,7 +254,7 @@ public class KeycloakSettingsTest {
oidcInfo); oidcInfo);
Map<String, String> publicSettings = settings.get(); Map<String, String> publicSettings = settings.get();
assertEquals(publicSettings.get(USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM); assertEquals(publicSettings.get(OIDC_USERNAME_CLAIM_SETTING), DEFAULT_USERNAME_CLAIM);
assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID); assertEquals(publicSettings.get(CLIENT_ID_SETTING), CLIENT_ID);
assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM); assertEquals(publicSettings.get(REALM_SETTING), CHE_REALM);
assertEquals(publicSettings.get(AUTH_SERVER_URL_SETTING), SERVER_AUTH_URL); assertEquals(publicSettings.get(AUTH_SERVER_URL_SETTING), SERVER_AUTH_URL);

View File

@ -21,6 +21,7 @@ import static org.testng.Assert.assertNull;
import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.client.WireMock;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.testng.annotations.AfterClass; import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -86,9 +87,8 @@ public class OIDCInfoProviderTest {
.willReturn( .willReturn(
aResponse().withHeader("Content-Type", "text/html").withBody("broken json"))); aResponse().withHeader("Content-Type", "text/html").withBody("broken json")));
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.oidcProviderUrl = serverUrl; new KeycloakOIDCInfoProvider(null, null, serverUrl, CHE_REALM);
oidcInfoProvider.realm = CHE_REALM;
oidcInfoProvider.get(); oidcInfoProvider.get();
} }
@ -100,9 +100,8 @@ public class OIDCInfoProviderTest {
.willReturn( .willReturn(
aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig))); aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig)));
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.serverURL = serverUrl; new KeycloakOIDCInfoProvider(serverUrl, null, null, CHE_REALM);
oidcInfoProvider.realm = CHE_REALM;
OIDCInfo oidcInfo = oidcInfoProvider.get(); OIDCInfo oidcInfo = oidcInfoProvider.get();
assertEquals( assertEquals(
@ -110,7 +109,7 @@ public class OIDCInfoProviderTest {
oidcInfo.getTokenPublicEndpoint()); oidcInfo.getTokenPublicEndpoint());
assertEquals( assertEquals(
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
oidcInfo.getEndSessionPublicEndpoint()); oidcInfo.getEndSessionPublicEndpoint().get());
assertNull(oidcInfo.getUserInfoInternalEndpoint()); assertNull(oidcInfo.getUserInfoInternalEndpoint());
assertNull(oidcInfo.getJwksInternalUri()); assertNull(oidcInfo.getJwksInternalUri());
} }
@ -150,10 +149,8 @@ public class OIDCInfoProviderTest {
.withHeader("Content-Type", "text/html") .withHeader("Content-Type", "text/html")
.withBody(OPEN_ID_CONF_TEMPLATE))); .withBody(OPEN_ID_CONF_TEMPLATE)));
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.serverURL = serverPublicUrl; new KeycloakOIDCInfoProvider(serverPublicUrl, serverUrl, null, CHE_REALM);
oidcInfoProvider.serverInternalURL = serverUrl;
oidcInfoProvider.realm = CHE_REALM;
OIDCInfo oidcInfo = oidcInfoProvider.get(); OIDCInfo oidcInfo = oidcInfoProvider.get();
assertEquals( assertEquals(
@ -161,7 +158,7 @@ public class OIDCInfoProviderTest {
oidcInfo.getTokenPublicEndpoint()); oidcInfo.getTokenPublicEndpoint());
assertEquals( assertEquals(
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
oidcInfo.getEndSessionPublicEndpoint()); oidcInfo.getEndSessionPublicEndpoint().get());
assertEquals( assertEquals(
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
oidcInfo.getUserInfoPublicEndpoint()); oidcInfo.getUserInfoPublicEndpoint());
@ -215,10 +212,8 @@ public class OIDCInfoProviderTest {
.withHeader("Content-Type", "text/html") .withHeader("Content-Type", "text/html")
.withBody(OPEN_ID_CONF_TEMPLATE))); .withBody(OPEN_ID_CONF_TEMPLATE)));
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.serverURL = serverPublicUrl; new KeycloakOIDCInfoProvider(serverPublicUrl, serverInternalUrl, null, CHE_REALM);
oidcInfoProvider.serverInternalURL = serverInternalUrl;
oidcInfoProvider.realm = CHE_REALM;
OIDCInfo oidcInfo = oidcInfoProvider.get(); OIDCInfo oidcInfo = oidcInfoProvider.get();
assertEquals( assertEquals(
@ -226,7 +221,7 @@ public class OIDCInfoProviderTest {
oidcInfo.getTokenPublicEndpoint()); oidcInfo.getTokenPublicEndpoint());
assertEquals( assertEquals(
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
oidcInfo.getEndSessionPublicEndpoint()); oidcInfo.getEndSessionPublicEndpoint().get());
assertEquals( assertEquals(
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
oidcInfo.getUserInfoPublicEndpoint()); oidcInfo.getUserInfoPublicEndpoint());
@ -253,11 +248,8 @@ public class OIDCInfoProviderTest {
.willReturn( .willReturn(
aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig))); aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig)));
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.serverURL = TEST_URL; new KeycloakOIDCInfoProvider(TEST_URL, TEST_URL, OIDCProviderUrl, CHE_REALM);
oidcInfoProvider.serverInternalURL = TEST_URL;
oidcInfoProvider.oidcProviderUrl = OIDCProviderUrl;
oidcInfoProvider.realm = CHE_REALM;
OIDCInfo oidcInfo = oidcInfoProvider.get(); OIDCInfo oidcInfo = oidcInfoProvider.get();
assertEquals( assertEquals(
@ -265,7 +257,7 @@ public class OIDCInfoProviderTest {
oidcInfo.getTokenPublicEndpoint()); oidcInfo.getTokenPublicEndpoint());
assertEquals( assertEquals(
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout", serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
oidcInfo.getEndSessionPublicEndpoint()); oidcInfo.getEndSessionPublicEndpoint().get());
assertEquals( assertEquals(
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo", serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
oidcInfo.getUserInfoInternalEndpoint()); oidcInfo.getUserInfoInternalEndpoint());
@ -278,17 +270,17 @@ public class OIDCInfoProviderTest {
expectedExceptions = RuntimeException.class, expectedExceptions = RuntimeException.class,
expectedExceptionsMessageRegExp = "Either the '.*' or '.*' or '.*' property should be set") expectedExceptionsMessageRegExp = "Either the '.*' or '.*' or '.*' property should be set")
public void shouldThrowErrorWhenAuthServerWasNotSet() { public void shouldThrowErrorWhenAuthServerWasNotSet() {
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.realm = CHE_REALM; new KeycloakOIDCInfoProvider(null, null, null, CHE_REALM);
oidcInfoProvider.get(); oidcInfoProvider.get();
} }
@Test( @Test(
expectedExceptions = RuntimeException.class, expectedExceptions = RuntimeException.class,
expectedExceptionsMessageRegExp = "The '.*' property should be set") expectedExceptionsMessageRegExp = "The '.*' property must be set")
public void shouldThrowErrorWhenRealmPropertyWasNotSet() { public void shouldThrowErrorWhenRealmPropertyWasNotSet() {
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider(); KeycloakOIDCInfoProvider oidcInfoProvider =
oidcInfoProvider.serverURL = TEST_URL; new KeycloakOIDCInfoProvider(null, null, null, null);
oidcInfoProvider.get(); oidcInfoProvider.get();
} }
} }

View File

@ -15,22 +15,13 @@ package org.eclipse.che.multiuser.keycloak.shared;
public class KeycloakConstants { public class KeycloakConstants {
private static final String KEYCLOAK_SETTING_PREFIX = "che.keycloak."; private static final String KEYCLOAK_SETTING_PREFIX = "che.keycloak.";
private static final String KEYCLOAK_SETTINGS_ENDPOINT_PATH = "/keycloak/settings";
public static final String AUTH_SERVER_URL_SETTING = KEYCLOAK_SETTING_PREFIX + "auth_server_url";
public static final String AUTH_SERVER_URL_INTERNAL_SETTING =
KEYCLOAK_SETTING_PREFIX + "auth_internal_server_url";
public static final String REALM_SETTING = KEYCLOAK_SETTING_PREFIX + "realm"; public static final String REALM_SETTING = KEYCLOAK_SETTING_PREFIX + "realm";
public static final String CLIENT_ID_SETTING = KEYCLOAK_SETTING_PREFIX + "client_id"; public static final String CLIENT_ID_SETTING = KEYCLOAK_SETTING_PREFIX + "client_id";
public static final String OIDC_PROVIDER_SETTING = KEYCLOAK_SETTING_PREFIX + "oidc_provider";
public static final String USERNAME_CLAIM_SETTING = KEYCLOAK_SETTING_PREFIX + "username_claim";
public static final String USE_NONCE_SETTING = KEYCLOAK_SETTING_PREFIX + "use_nonce"; public static final String USE_NONCE_SETTING = KEYCLOAK_SETTING_PREFIX + "use_nonce";
public static final String USE_FIXED_REDIRECT_URLS_SETTING = public static final String USE_FIXED_REDIRECT_URLS_SETTING =
KEYCLOAK_SETTING_PREFIX + "use_fixed_redirect_urls"; KEYCLOAK_SETTING_PREFIX + "use_fixed_redirect_urls";
public static final String JS_ADAPTER_URL_SETTING = KEYCLOAK_SETTING_PREFIX + "js_adapter_url"; public static final String JS_ADAPTER_URL_SETTING = KEYCLOAK_SETTING_PREFIX + "js_adapter_url";
public static final String ALLOWED_CLOCK_SKEW_SEC =
KEYCLOAK_SETTING_PREFIX + "allowed_clock_skew_sec";
public static final String OSO_ENDPOINT_SETTING = KEYCLOAK_SETTING_PREFIX + "oso.endpoint"; public static final String OSO_ENDPOINT_SETTING = KEYCLOAK_SETTING_PREFIX + "oso.endpoint";
public static final String PROFILE_ENDPOINT_SETTING = public static final String PROFILE_ENDPOINT_SETTING =
@ -48,8 +39,4 @@ public class KeycloakConstants {
KEYCLOAK_SETTING_PREFIX + "redirect_url.dashboard"; KEYCLOAK_SETTING_PREFIX + "redirect_url.dashboard";
public static final String FIXED_REDIRECT_URL_FOR_IDE = public static final String FIXED_REDIRECT_URL_FOR_IDE =
KEYCLOAK_SETTING_PREFIX + "redirect_url.ide"; KEYCLOAK_SETTING_PREFIX + "redirect_url.ide";
public static String getEndpoint(String apiEndpoint) {
return apiEndpoint + KEYCLOAK_SETTINGS_ENDPOINT_PATH;
}
} }

View File

@ -79,6 +79,10 @@
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-shared</artifactId> <artifactId>che-multiuser-keycloak-shared</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-oidc</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

View File

@ -11,7 +11,9 @@
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.keycloak.server;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.*; import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_INTERNAL_SETTING;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
@ -30,6 +32,7 @@ import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent;
import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; import org.eclipse.che.core.db.cascade.CascadeEventSubscriber;
import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -25,6 +25,7 @@ import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.DefaultHttpJsonRequest; import org.eclipse.che.api.core.rest.DefaultHttpJsonRequest;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.multiuser.oidc.OIDCInfo;
import org.mockito.Mock; import org.mockito.Mock;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;

104
multiuser/oidc/pom.xml Normal file
View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-2021 Red Hat, Inc.
This program and the accompanying materials are made
available under the terms of the Eclipse Public License 2.0
which is available at https://www.eclipse.org/legal/epl-2.0/
SPDX-License-Identifier: EPL-2.0
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-multiuser-parent</artifactId>
<groupId>org.eclipse.che.multiuser</groupId>
<version>7.40.0-SNAPSHOT</version>
</parent>
<artifactId>che-multiuser-oidc</artifactId>
<packaging>jar</packaging>
<name>Che Multiuser :: OIDC</name>
<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-model</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-user</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-api-authentication-commons</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-api-authorization</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -9,9 +9,12 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.oidc;
/** OIDCInfo - POJO object to store information about Keycloak api. */ import java.util.Optional;
import java.util.StringJoiner;
/** OIDCInfo - POJO object to store information about OIDC api. */
public class OIDCInfo { public class OIDCInfo {
private final String tokenPublicEndpoint; private final String tokenPublicEndpoint;
@ -38,7 +41,6 @@ public class OIDCInfo {
this.userInfoInternalEndpoint = userInfoInternalEndpoint; this.userInfoInternalEndpoint = userInfoInternalEndpoint;
this.jwksPublicUri = jwksPublicUri; this.jwksPublicUri = jwksPublicUri;
this.jwksInternalUri = jwksInternalUri; this.jwksInternalUri = jwksInternalUri;
this.authServerURL = authServerURL; this.authServerURL = authServerURL;
this.authServerPublicURL = authServerPublicURL; this.authServerPublicURL = authServerPublicURL;
} }
@ -48,11 +50,6 @@ public class OIDCInfo {
return tokenPublicEndpoint; return tokenPublicEndpoint;
} }
/** @return public log out url. */
public String getEndSessionPublicEndpoint() {
return endSessionPublicEndpoint;
}
/** @return public url to get user profile information. */ /** @return public url to get user profile information. */
public String getUserInfoPublicEndpoint() { public String getUserInfoPublicEndpoint() {
return userInfoPublicEndpoint; return userInfoPublicEndpoint;
@ -85,4 +82,21 @@ public class OIDCInfo {
public String getAuthServerPublicURL() { public String getAuthServerPublicURL() {
return authServerPublicURL; return authServerPublicURL;
} }
@Override
public String toString() {
return new StringJoiner(", ", OIDCInfo.class.getSimpleName() + "[", "]")
.add("tokenPublicEndpoint='" + tokenPublicEndpoint + "'")
.add("userInfoPublicEndpoint='" + userInfoPublicEndpoint + "'")
.add("userInfoInternalEndpoint='" + userInfoInternalEndpoint + "'")
.add("jwksPublicUri='" + jwksPublicUri + "'")
.add("jwksInternalUri='" + jwksInternalUri + "'")
.add("authServerURL='" + authServerURL + "'")
.add("authServerPublicURL='" + authServerPublicURL + "'")
.toString();
}
public Optional<String> getEndSessionPublicEndpoint() {
return Optional.ofNullable(endSessionPublicEndpoint);
}
} }

View File

@ -9,13 +9,9 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.oidc;
import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.MoreObjects.firstNonNull;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_INTERNAL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.OIDC_PROVIDER_SETTING;
import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
@ -34,32 +30,35 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. These * OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. This
* information is useful to provide access to the Keycloak api. * information is useful to provide access to the OIDC api.
*/ */
public class OIDCInfoProvider implements Provider<OIDCInfo> { public class OIDCInfoProvider implements Provider<OIDCInfo> {
private static final Logger LOG = LoggerFactory.getLogger(OIDCInfoProvider.class); private static final Logger LOG = LoggerFactory.getLogger(OIDCInfoProvider.class);
@Inject private static final String OIDC_SETTING_PREFIX = "che.oidc.";
@Nullable
@Named(AUTH_SERVER_URL_SETTING) public static final String AUTH_SERVER_URL_SETTING = OIDC_SETTING_PREFIX + "auth_server_url";
public static final String AUTH_SERVER_URL_INTERNAL_SETTING =
OIDC_SETTING_PREFIX + "auth_internal_server_url";
public static final String OIDC_PROVIDER_SETTING = OIDC_SETTING_PREFIX + "oidc_provider";
public static final String OIDC_USERNAME_CLAIM_SETTING = OIDC_SETTING_PREFIX + "username_claim";
public static final String OIDC_ALLOWED_CLOCK_SKEW_SEC =
OIDC_SETTING_PREFIX + "allowed_clock_skew_sec";
protected String serverURL; protected String serverURL;
@Inject
@Nullable
@Named(AUTH_SERVER_URL_INTERNAL_SETTING)
protected String serverInternalURL; protected String serverInternalURL;
@Inject
@Nullable
@Named(OIDC_PROVIDER_SETTING)
protected String oidcProviderUrl; protected String oidcProviderUrl;
@Inject @Inject
@Nullable public OIDCInfoProvider(
@Named(REALM_SETTING) @Nullable @Named(AUTH_SERVER_URL_SETTING) String serverURL,
protected String realm; @Nullable @Named(AUTH_SERVER_URL_INTERNAL_SETTING) String serverInternalURL,
@Nullable @Named(OIDC_PROVIDER_SETTING) String oidcProviderUrl) {
this.serverURL = serverURL;
this.serverInternalURL = serverInternalURL;
this.oidcProviderUrl = oidcProviderUrl;
}
/** @return OIDCInfo with OIDC settings information. */ /** @return OIDCInfo with OIDC settings information. */
@Override @Override
@ -84,7 +83,9 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
String userInfoPublicEndpoint = String userInfoPublicEndpoint =
setPublicUrl((String) openIdConfiguration.get("userinfo_endpoint")); setPublicUrl((String) openIdConfiguration.get("userinfo_endpoint"));
String endSessionPublicEndpoint = String endSessionPublicEndpoint =
setPublicUrl((String) openIdConfiguration.get("end_session_endpoint")); openIdConfiguration.containsKey("end_session_endpoint")
? setPublicUrl((String) openIdConfiguration.get("end_session_endpoint"))
: null;
String jwksPublicUri = setPublicUrl((String) openIdConfiguration.get("jwks_uri")); String jwksPublicUri = setPublicUrl((String) openIdConfiguration.get("jwks_uri"));
String jwksInternalUri = setInternalUrl(jwksPublicUri); String jwksInternalUri = setInternalUrl(jwksPublicUri);
String userInfoInternalEndpoint = setInternalUrl(userInfoPublicEndpoint); String userInfoInternalEndpoint = setInternalUrl(userInfoPublicEndpoint);
@ -107,7 +108,7 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
} }
private String getWellKnownEndpoint(String serverAuthUrl) { private String getWellKnownEndpoint(String serverAuthUrl) {
String wellKnownEndpoint = firstNonNull(oidcProviderUrl, serverAuthUrl + "/realms/" + realm); String wellKnownEndpoint = firstNonNull(oidcProviderUrl, constructServerAuthUrl(serverAuthUrl));
if (!wellKnownEndpoint.endsWith("/")) { if (!wellKnownEndpoint.endsWith("/")) {
wellKnownEndpoint = wellKnownEndpoint + "/"; wellKnownEndpoint = wellKnownEndpoint + "/";
} }
@ -115,7 +116,11 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
return wellKnownEndpoint; return wellKnownEndpoint;
} }
private void validate() { protected String constructServerAuthUrl(String serverAuthUrl) {
return serverAuthUrl;
}
protected void validate() {
if (serverURL == null && serverInternalURL == null && oidcProviderUrl == null) { if (serverURL == null && serverInternalURL == null && oidcProviderUrl == null) {
throw new RuntimeException( throw new RuntimeException(
"Either the '" "Either the '"
@ -126,10 +131,6 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
+ OIDC_PROVIDER_SETTING + OIDC_PROVIDER_SETTING
+ "' property should be set"); + "' property should be set");
} }
if (oidcProviderUrl == null && realm == null) {
throw new RuntimeException("The '" + REALM_SETTING + "' property should be set");
}
} }
private String setInternalUrl(String endpointUrl) { private String setInternalUrl(String endpointUrl) {

View File

@ -9,13 +9,12 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.oidc;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.auth0.jwk.GuavaCachedJwkProvider; import com.auth0.jwk.GuavaCachedJwkProvider;
import com.auth0.jwk.JwkProvider; import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider; import com.auth0.jwk.UrlJwkProvider;
import com.google.common.base.Strings;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import javax.inject.Inject; import javax.inject.Inject;
@ -23,14 +22,14 @@ import javax.inject.Provider;
import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.inject.ConfigurationException;
/** Constructs {@link UrlJwkProvider} based on Jwk endpoint from keycloak settings */ /** Constructs {@link UrlJwkProvider} based on Jwk endpoint from keycloak settings */
public class KeycloakJwkProvider implements Provider<JwkProvider> { public class OIDCJwkProvider implements Provider<JwkProvider> {
private final JwkProvider jwkProvider; private final JwkProvider jwkProvider;
@Inject @Inject
public KeycloakJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException { public OIDCJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException {
final String jwksUrl = final String jwksUrl =
isNullOrEmpty(oidcInfo.getJwksInternalUri()) Strings.isNullOrEmpty(oidcInfo.getJwksInternalUri())
? oidcInfo.getJwksPublicUri() ? oidcInfo.getJwksPublicUri()
: oidcInfo.getJwksInternalUri(); : oidcInfo.getJwksInternalUri();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012-2018 Red Hat, Inc. * Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made * This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0 * available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/ * which is available at https://www.eclipse.org/legal/epl-2.0/
@ -9,30 +9,32 @@
* Contributors: * Contributors:
* Red Hat, Inc. - initial API and implementation * Red Hat, Inc. - initial API and implementation
*/ */
package org.eclipse.che.multiuser.keycloak.server; package org.eclipse.che.multiuser.oidc;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_ALLOWED_CLOCK_SKEW_SEC;
import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolver;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
/** Provides instance of {@link JwtParser} */ /** Provides instance of {@link JwtParser} */
@Singleton @Singleton
public class KeycloakJwtParserProvider implements Provider<JwtParser> { public class OIDCJwtParserProvider implements Provider<JwtParser> {
private final JwtParser jwtParser; private final JwtParser jwtParser;
@Inject @Inject
public KeycloakJwtParserProvider( public OIDCJwtParserProvider(
@Named(KeycloakConstants.ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec, @Named(OIDC_ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
KeycloakSigningKeyResolver keycloakSigningKeyResolver) { SigningKeyResolver signingKeyResolver) {
this.jwtParser = this.jwtParser =
Jwts.parser() Jwts.parserBuilder()
.setSigningKeyResolver(signingKeyResolver)
.setAllowedClockSkewSeconds(allowedClockSkewSec) .setAllowedClockSkewSeconds(allowedClockSkewSec)
.setSigningKeyResolver(keycloakSigningKeyResolver); .build();
} }
@Override @Override

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.oidc;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import java.security.Key;
import java.security.PublicKey;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Resolves signing key based on id from JWT header */
@Singleton
public class OIDCSigningKeyResolver extends SigningKeyResolverAdapter {
private final JwkProvider jwkProvider;
private static final Logger LOG = LoggerFactory.getLogger(OIDCSigningKeyResolver.class);
@Inject
protected OIDCSigningKeyResolver(JwkProvider jwkProvider) {
this.jwkProvider = jwkProvider;
}
@Override
public Key resolveSigningKey(JwsHeader header, String plaintext) {
return getJwtPublicKey(header);
}
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
return getJwtPublicKey(header);
}
protected synchronized PublicKey getJwtPublicKey(JwsHeader<?> header) {
String kid = header.getKeyId();
if (kid == null) {
LOG.warn(
"'kid' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys");
throw new JwtException("'kid' is missing in the JWT token header.");
}
try {
return jwkProvider.get(kid).getPublicKey();
} catch (JwkException e) {
throw new JwtException(
"Error during the retrieval of the public key during JWT token validation", e);
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.oidc.filter;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.OIDC_USERNAME_CLAIM_SETTING;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.subject.SubjectImpl;
import org.eclipse.che.multiuser.api.authentication.commons.SessionStore;
import org.eclipse.che.multiuser.api.authentication.commons.filter.MultiUserEnvironmentInitializationFilter;
import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor;
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
/**
* This filter uses given token directly. It's used for native Kubernetes user authentication.
* Requests without token or with invalid token are rejected.
*
* <p>It also makes sure that User is present in Che database. If not, it will create the User from
* JWT token claims. The username claim is configured with {@link
* org.eclipse.che.multiuser.oidc.OIDCInfoProvider#OIDC_USERNAME_CLAIM_SETTING}.
*/
@Singleton
public class OidcTokenInitializationFilter
extends MultiUserEnvironmentInitializationFilter<Jws<Claims>> {
private static final String EMAIL_CLAIM = "email";
private static final String NAME_CLAIM = "name";
protected static final String DEFAULT_USERNAME_CLAIM = NAME_CLAIM;
private final JwtParser jwtParser;
private final PermissionChecker permissionChecker;
private final UserManager userManager;
private final String usernameClaim;
@Inject
public OidcTokenInitializationFilter(
PermissionChecker permissionChecker,
JwtParser jwtParser,
SessionStore sessionStore,
RequestTokenExtractor tokenExtractor,
UserManager userManager,
@Nullable @Named(OIDC_USERNAME_CLAIM_SETTING) String usernameClaim) {
super(sessionStore, tokenExtractor);
this.permissionChecker = permissionChecker;
this.jwtParser = jwtParser;
this.userManager = userManager;
this.usernameClaim = isNullOrEmpty(usernameClaim) ? DEFAULT_USERNAME_CLAIM : usernameClaim;
}
@Override
protected Optional<Jws<Claims>> processToken(String token) {
return Optional.ofNullable(jwtParser.parseClaimsJws(token));
}
@Override
protected String getUserId(Jws<Claims> processedToken) {
return processedToken.getBody().getSubject();
}
@Override
protected Subject extractSubject(String token, Jws<Claims> processedToken) {
try {
Claims claims = processedToken.getBody();
User user =
userManager.getOrCreateUser(
claims.getSubject(),
claims.get(EMAIL_CLAIM, String.class),
claims.get(usernameClaim, String.class));
return new AuthorizedSubject(
new SubjectImpl(user.getName(), user.getId(), token, false), permissionChecker);
} catch (ServerException | ConflictException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2012-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.oidc.filter;
import static org.eclipse.che.multiuser.oidc.filter.OidcTokenInitializationFilter.DEFAULT_USERNAME_CLAIM;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.model.user.User;
import org.eclipse.che.api.user.server.UserManager;
import org.eclipse.che.multiuser.api.authentication.commons.SessionStore;
import org.eclipse.che.multiuser.api.authentication.commons.token.RequestTokenExtractor;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class OidcTokenInitializationFilterTest {
@Mock private PermissionChecker permissionsChecker;
@Mock private JwtParser jwtParser;
@Mock private SessionStore sessionStore;
@Mock private RequestTokenExtractor tokenExtractor;
@Mock private UserManager userManager;
private final String usernameClaim = "blabolClaim";
private final String TEST_USERNAME = "jondoe";
private final String TEST_USER_EMAIL = "jon@doe";
private final String TEST_USERID = "jon1337";
private final String TEST_TOKEN = "abcToken";
@Mock private Jws<Claims> jwsClaims;
@Mock private Claims claims;
OidcTokenInitializationFilter tokenInitializationFilter;
@BeforeMethod
public void setUp() {
tokenInitializationFilter =
new OidcTokenInitializationFilter(
permissionsChecker,
jwtParser,
sessionStore,
tokenExtractor,
userManager,
usernameClaim);
lenient().when(jwsClaims.getBody()).thenReturn(claims);
lenient().when(claims.getSubject()).thenReturn(TEST_USERID);
lenient().when(claims.get("email", String.class)).thenReturn(TEST_USER_EMAIL);
lenient().when(claims.get(usernameClaim, String.class)).thenReturn(TEST_USERNAME);
}
@Test
public void testProcessToken() {
when(jwtParser.parseClaimsJws(TEST_TOKEN)).thenReturn(jwsClaims);
var returnedClaims = tokenInitializationFilter.processToken(TEST_TOKEN).get();
assertEquals(returnedClaims, jwsClaims);
verify(jwtParser).parseClaimsJws(TEST_TOKEN);
}
@Test
public void testProcessEmptyToken() {
var returnedClaims = tokenInitializationFilter.processToken("");
assertTrue(returnedClaims.isEmpty());
}
@Test
public void testGetUserId() {
var userId = tokenInitializationFilter.getUserId(jwsClaims);
assertEquals(userId, TEST_USERID);
}
@Test
public void testExtractSubject() throws ServerException, ConflictException {
User createdUser = mock(User.class);
when(createdUser.getId()).thenReturn(TEST_USERID);
when(createdUser.getName()).thenReturn(TEST_USERNAME);
when(userManager.getOrCreateUser(TEST_USERID, TEST_USER_EMAIL, TEST_USERNAME))
.thenReturn(createdUser);
var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims);
assertEquals(subject.getUserId(), TEST_USERID);
assertEquals(subject.getUserName(), TEST_USERNAME);
assertEquals(subject.getToken(), TEST_TOKEN);
verify(userManager).getOrCreateUser(TEST_USERID, TEST_USER_EMAIL, TEST_USERNAME);
}
@Test(dataProvider = "usernameClaims")
public void testDefaultUsernameClaimWhenEmpty(String customUsernameClaim)
throws ServerException, ConflictException {
tokenInitializationFilter =
new OidcTokenInitializationFilter(
permissionsChecker,
jwtParser,
sessionStore,
tokenExtractor,
userManager,
customUsernameClaim);
User createdUser = mock(User.class);
when(createdUser.getId()).thenReturn(TEST_USERID);
when(createdUser.getName()).thenReturn(TEST_USERNAME);
when(userManager.getOrCreateUser(TEST_USERID, TEST_USER_EMAIL, TEST_USERNAME))
.thenReturn(createdUser);
when(claims.get(DEFAULT_USERNAME_CLAIM, String.class)).thenReturn(TEST_USERNAME);
var subject = tokenInitializationFilter.extractSubject(TEST_TOKEN, jwsClaims);
assertEquals(subject.getUserId(), TEST_USERID);
assertEquals(subject.getUserName(), TEST_USERNAME);
assertEquals(subject.getToken(), TEST_TOKEN);
verify(userManager).getOrCreateUser(TEST_USERID, TEST_USER_EMAIL, TEST_USERNAME);
verify(claims).get(DEFAULT_USERNAME_CLAIM, String.class);
verify(claims, never()).get(usernameClaim, String.class);
}
@DataProvider
public static Object[][] usernameClaims() {
return new Object[][] {{""}, {null}};
}
}

View File

@ -32,5 +32,6 @@
<module>machine-auth</module> <module>machine-auth</module>
<module>personal-account</module> <module>personal-account</module>
<module>integration-tests</module> <module>integration-tests</module>
<module>oidc</module>
</modules> </modules>
</project> </project>

15
pom.xml
View File

@ -271,6 +271,11 @@
<artifactId>logging-interceptor</artifactId> <artifactId>logging-interceptor</artifactId>
<version>${com.squareup.okhttp3.version}</version> <version>${com.squareup.okhttp3.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>${com.squareup.okhttp3.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
@ -307,6 +312,11 @@
<artifactId>commons-lang</artifactId> <artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version> <version>${commons-lang.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-server-mock</artifactId>
<version>${io.fabric8.kubernetes-client}</version>
</dependency>
<dependency> <dependency>
<groupId>io.github.mweirauch</groupId> <groupId>io.github.mweirauch</groupId>
<artifactId>micrometer-jvm-extras</artifactId> <artifactId>micrometer-jvm-extras</artifactId>
@ -1115,6 +1125,11 @@
<artifactId>che-multiuser-machine-authentication-shared</artifactId> <artifactId>che-multiuser-machine-authentication-shared</artifactId>
<version>${che.version}</version> <version>${che.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-oidc</artifactId>
<version>${che.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.che.multiuser</groupId> <groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-devfile</artifactId> <artifactId>che-multiuser-permission-devfile</artifactId>

View File

@ -24,10 +24,6 @@
<name>Che Core :: API :: InfraProxy</name> <name>Che Core :: API :: InfraProxy</name>
<description>Provides direct HTTP access to the underlying infrastructure web API.</description> <description>Provides direct HTTP access to the underlying infrastructure web API.</description>
<dependencies> <dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>

View File

@ -11,9 +11,7 @@
*/ */
package org.eclipse.che.api.infraproxy.server; package org.eclipse.che.api.infraproxy.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.Beta; import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
@ -24,7 +22,6 @@ import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -52,39 +49,17 @@ public class InfrastructureApiService extends Service {
private final boolean allowed; private final boolean allowed;
private final RuntimeInfrastructure runtimeInfrastructure; private final RuntimeInfrastructure runtimeInfrastructure;
private final ObjectMapper mapper;
@Context private MediaType mediaType; private static boolean determineAllowed(String identityProvider) {
return identityProvider != null;
private static boolean determineAllowed(
String infra, String identityProvider, boolean allowedForKubernetes) {
if ("openshift".equals(infra)) {
return identityProvider != null && identityProvider.startsWith("openshift");
}
return allowedForKubernetes;
} }
@Inject @Inject
public InfrastructureApiService( public InfrastructureApiService(
@Nullable @Named("che.infra.openshift.oauth_identity_provider") String identityProvider, @Nullable @Named("che.infra.openshift.oauth_identity_provider") String identityProvider,
@Named("che.infra.kubernetes.enable_unsupported_k8s") boolean allowedForKubernetes,
RuntimeInfrastructure runtimeInfrastructure) { RuntimeInfrastructure runtimeInfrastructure) {
this( this.runtimeInfrastructure = runtimeInfrastructure;
System.getenv("CHE_INFRASTRUCTURE_ACTIVE"), this.allowed = determineAllowed(identityProvider);
allowedForKubernetes,
identityProvider,
runtimeInfrastructure);
}
@VisibleForTesting
InfrastructureApiService(
String infraName,
boolean allowedForKubernetes,
String identityProvider,
RuntimeInfrastructure infra) {
this.runtimeInfrastructure = infra;
this.mapper = new ObjectMapper();
this.allowed = determineAllowed(infraName, identityProvider, allowedForKubernetes);
} }
@GET @GET

View File

@ -37,49 +37,13 @@ public class InfrastructureApiServiceTest {
@BeforeMethod @BeforeMethod
public void setup() throws Exception { public void setup() throws Exception {
apiService = apiService = new InfrastructureApiService("openshift-identityProvider", infra);
new InfrastructureApiService("openshift", false, "openshift-identityProvider", infra);
}
@Test
public void testFailsAuthWhenNotAllowedForKubernetesAndNotOnOpenShift() throws Exception {
// given
apiService =
new InfrastructureApiService("not-openshift", false, "openshift-identityProvider", infra);
// when
Response response =
given()
.contentType("application/json; charset=utf-8")
.when()
.get("/unsupported/k8s/nazdar/");
// then
assertEquals(response.getStatusCode(), 403);
}
@Test
public void testFailsAuthWhenNotUsingOpenShiftIdentityProvider() throws Exception {
// given
apiService =
new InfrastructureApiService("openshift", false, "not-openshift-identityProvider", infra);
// when
Response response =
given()
.contentType("application/json; charset=utf-8")
.when()
.get("/unsupported/k8s/nazdar/");
// then
assertEquals(response.getStatusCode(), 403);
} }
@Test @Test
public void testResolvesCallWhenAllowedForKubernetesOnKubernetes() throws Exception { public void testResolvesCallWhenAllowedForKubernetesOnKubernetes() throws Exception {
// given // given
apiService = apiService = new InfrastructureApiService("not-openshift-identityProvider", infra);
new InfrastructureApiService("kubernetes", true, "not-openshift-identityProvider", infra);
when(infra.sendDirectInfrastructureRequest(any(), any(), any(), any())) when(infra.sendDirectInfrastructureRequest(any(), any(), any(), any()))
.thenReturn( .thenReturn(
jakarta.ws.rs.core.Response.ok() jakarta.ws.rs.core.Response.ok()
@ -98,24 +62,6 @@ public class InfrastructureApiServiceTest {
assertEquals(response.getContentType(), "application/json;charset=utf-8"); assertEquals(response.getContentType(), "application/json;charset=utf-8");
} }
@Test
public void testFailsAuthWhenAllowedForKubernetesOnOpenshiftWithNonOpenshiftIdentityProvider()
throws Exception {
// given
apiService =
new InfrastructureApiService("openshift", true, "not-openshift-identityProvider", infra);
// when
Response response =
given()
.contentType("application/json; charset=utf-8")
.when()
.get("/unsupported/k8s/nazdar/");
// then
assertEquals(response.getStatusCode(), 403);
}
@Test @Test
public void testGet() throws Exception { public void testGet() throws Exception {
// given // given