feat: native auth on Kubernetes (#171)
Signed-off-by: Michal Vala <mvala@redhat.com>pull/186/head^2
parent
f289ec619f
commit
388a5183be
|
|
@ -35,6 +35,10 @@
|
|||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>jwks-rsa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
@ -275,6 +279,10 @@
|
|||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-machine-authentication</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-devfile</artifactId>
|
||||
|
|
@ -454,6 +462,7 @@
|
|||
<dep>com.google.guava:guava</dep>
|
||||
<dep>org.everrest:everrest-core</dep>
|
||||
<dep>io.jsonwebtoken:jjwt-jackson</dep>
|
||||
<dep>io.jsonwebtoken:jjwt-impl</dep>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
|||
|
|
@ -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.multiuser.api.permission.server.SystemDomain.SYSTEM_DOMAIN_ACTIONS;
|
||||
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.TypeLiteral;
|
||||
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.name.Names;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.impl.DefaultJwtParser;
|
||||
import io.jsonwebtoken.SigningKeyResolver;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
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.schema.SchemaInitializer;
|
||||
import org.eclipse.che.core.tracing.metrics.TracingMetricsModule;
|
||||
import org.eclipse.che.inject.ConfigurationException;
|
||||
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.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.KeycloakUserRemoverModule;
|
||||
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.OrganizationJpaModule;
|
||||
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);
|
||||
}
|
||||
|
||||
if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
|
||||
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
|
||||
bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class);
|
||||
} else {
|
||||
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.");
|
||||
if (Boolean.parseBoolean(System.getenv("CHE_AUTH_NATIVEUSER"))) {
|
||||
bind(KubernetesClientConfigFactory.class).to(KubernetesOidcProviderConfigFactory.class);
|
||||
} else if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
|
||||
bind(KubernetesClientConfigFactory.class).to(KeycloakProviderConfigFactory.class);
|
||||
}
|
||||
|
||||
persistenceProperties.put(
|
||||
|
|
@ -395,11 +392,16 @@ public class WsMasterModule extends AbstractModule {
|
|||
install(new OrganizationJpaModule());
|
||||
|
||||
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(JwtParser.class).to(DefaultJwtParser.class);
|
||||
bind(ProfileDao.class).to(JpaProfileDao.class);
|
||||
bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class);
|
||||
bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class);
|
||||
} else {
|
||||
install(new KeycloakModule());
|
||||
install(new KeycloakUserRemoverModule());
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import org.eclipse.che.inject.ConfigurationException;
|
|||
import org.eclipse.che.inject.DynaModule;
|
||||
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule;
|
||||
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.multiuser.oauth.OpenshiftTokenInitializationFilter;
|
||||
import org.everrest.guice.servlet.GuiceEverrestServlet;
|
||||
|
|
@ -78,6 +80,8 @@ public class WsMasterServletModule extends ServletModule {
|
|||
final String infrastructure = System.getenv("CHE_INFRASTRUCTURE_ACTIVE");
|
||||
if (OpenShiftInfrastructure.NAME.equals(infrastructure)) {
|
||||
filter("/*").through(OpenshiftTokenInitializationFilter.class);
|
||||
} else if (KubernetesInfrastructure.NAME.equals(infrastructure)) {
|
||||
filter("/*").through(OidcTokenInitializationFilter.class);
|
||||
} else {
|
||||
throw new ConfigurationException("Native user mode is currently supported on on OpenShift.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -342,6 +342,9 @@ che.infra.kubernetes.service_account_name=NULL
|
|||
# This property deprecates `che.infra.kubernetes.cluster_role_name`.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
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
|
||||
|
||||
# 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
|
||||
# Can be set to NULL only if `che.keycloak.oidcProvider`
|
||||
# is used
|
||||
|
|
@ -117,9 +132,6 @@ che.keycloak.oso.endpoint=NULL
|
|||
# URL to access Github OAuth tokens
|
||||
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.
|
||||
che.keycloak.use_nonce=true
|
||||
|
||||
|
|
@ -130,21 +142,11 @@ che.keycloak.use_nonce=true
|
|||
# if an alternate `oidc_provider` is used
|
||||
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
|
||||
# only supports fixed redirect Urls
|
||||
# This property is ignored when `che.keycloak.oidc_provider` is NULL
|
||||
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.
|
||||
# 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.
|
||||
|
|
|
|||
|
|
@ -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.openshift.trusted_ca.dest_configmap_labels=che.infra.openshift.trusted_ca_bundles_config_map_labels
|
||||
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
|
||||
|
|
|
|||
|
|
@ -220,6 +220,11 @@
|
|||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-server-mock</artifactId>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public class CheServerKubernetesClientFactory extends KubernetesClientFactory {
|
|||
|
||||
@Inject
|
||||
public CheServerKubernetesClientFactory(
|
||||
KubernetesClientConfigFactory configBuilder,
|
||||
@Nullable @Named("che.infra.kubernetes.master_url") String masterUrl,
|
||||
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts,
|
||||
@Named("che.infra.kubernetes.client.http.async_requests.max") int maxConcurrentRequests,
|
||||
|
|
@ -41,6 +42,7 @@ public class CheServerKubernetesClientFactory extends KubernetesClientFactory {
|
|||
int connectionPoolKeepAlive,
|
||||
EventListener eventListener) {
|
||||
super(
|
||||
configBuilder,
|
||||
masterUrl,
|
||||
doTrustCerts,
|
||||
maxConcurrentRequests,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ package org.eclipse.che.workspace.infrastructure.kubernetes;
|
|||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
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.ConfigBuilder;
|
||||
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
|
||||
* configurations.
|
||||
*/
|
||||
private Config defaultConfig;
|
||||
private final Config defaultConfig;
|
||||
|
||||
protected final KubernetesClientConfigFactory configBuilder;
|
||||
|
||||
@Inject
|
||||
public KubernetesClientFactory(
|
||||
KubernetesClientConfigFactory configBuilder,
|
||||
@Nullable @Named("che.infra.kubernetes.master_url") String masterUrl,
|
||||
@Nullable @Named("che.infra.kubernetes.trust_certs") Boolean doTrustCerts,
|
||||
@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")
|
||||
int connectionPoolKeepAlive,
|
||||
EventListener eventListener) {
|
||||
this.configBuilder = configBuilder;
|
||||
this.defaultConfig = buildDefaultConfig(masterUrl, doTrustCerts);
|
||||
OkHttpClient temporary = HttpClientUtils.createHttpClient(defaultConfig);
|
||||
OkHttpClient.Builder builder = temporary.newBuilder();
|
||||
|
|
@ -166,7 +171,12 @@ public class KubernetesClientFactory {
|
|||
* infromation
|
||||
*/
|
||||
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)
|
||||
throws InfrastructureException {
|
||||
return config;
|
||||
return configBuilder.buildConfig(config, workspaceId);
|
||||
}
|
||||
|
||||
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
|
||||
* config} parameter.
|
||||
*/
|
||||
private DefaultKubernetesClient create(Config config) {
|
||||
protected BaseKubernetesClient<?> create(Config config) {
|
||||
OkHttpClient clientHttpClient =
|
||||
httpClient.newBuilder().authenticator(Authenticator.NONE).build();
|
||||
OkHttpClient.Builder builder = clientHttpClient.newBuilder();
|
||||
|
|
|
|||
|
|
@ -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.KubernetesEnvironmentFactory;
|
||||
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.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.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.PerWorkspacePVCStrategy;
|
||||
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(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.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(UserPreferencesConfigurator.class);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import java.net.URI;
|
|||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
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.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.InternalInfrastructureException;
|
||||
|
|
@ -47,8 +46,7 @@ public class KubernetesRuntimeContext<T extends KubernetesEnvironment> extends R
|
|||
KubernetesRuntimeStateCache runtimeStatuses,
|
||||
@Assisted T kubernetesEnvironment,
|
||||
@Assisted RuntimeIdentity identity,
|
||||
@Assisted RuntimeInfrastructure infrastructure)
|
||||
throws ValidationException, InfrastructureException {
|
||||
@Assisted RuntimeInfrastructure infrastructure) {
|
||||
super(kubernetesEnvironment, identity, infrastructure);
|
||||
this.namespaceFactory = namespaceFactory;
|
||||
this.runtimeFactory = runtimeFactory;
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public class KubernetesNamespace {
|
|||
*/
|
||||
void prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations)
|
||||
throws InfrastructureException {
|
||||
KubernetesClient client = clientFactory.create(workspaceId);
|
||||
KubernetesClient client = cheSAClientFactory.create(workspaceId);
|
||||
Namespace namespace = get(name, client);
|
||||
|
||||
if (namespace == null) {
|
||||
|
|
|
|||
|
|
@ -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.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.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 com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Joiner;
|
||||
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.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.Namespace;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.api.model.SecretBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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.api.server.impls.KubernetesNamespaceMetaImpl;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -103,25 +97,23 @@ public class KubernetesNamespaceFactory {
|
|||
protected final Map<String, String> namespaceLabels;
|
||||
protected final Map<String, String> namespaceAnnotations;
|
||||
|
||||
private final String serviceAccountName;
|
||||
private final Set<String> clusterRoleNames;
|
||||
private final KubernetesClientFactory clientFactory;
|
||||
private final KubernetesClientFactory cheClientFactory;
|
||||
private final boolean namespaceCreationAllowed;
|
||||
private final UserManager userManager;
|
||||
private final PreferenceManager preferenceManager;
|
||||
protected final Set<NamespaceConfigurator> namespaceConfigurators;
|
||||
protected final KubernetesSharedPool sharedPool;
|
||||
|
||||
@Inject
|
||||
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,
|
||||
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
|
||||
@Named("che.infra.kubernetes.namespace.label") boolean labelNamespaces,
|
||||
@Named("che.infra.kubernetes.namespace.annotate") boolean annotateNamespaces,
|
||||
@Named("che.infra.kubernetes.namespace.labels") String namespaceLabels,
|
||||
@Named("che.infra.kubernetes.namespace.annotations") String namespaceAnnotations,
|
||||
Set<NamespaceConfigurator> namespaceConfigurators,
|
||||
KubernetesClientFactory clientFactory,
|
||||
CheServerKubernetesClientFactory cheClientFactory,
|
||||
UserManager userManager,
|
||||
|
|
@ -130,7 +122,6 @@ public class KubernetesNamespaceFactory {
|
|||
throws ConfigurationException {
|
||||
this.namespaceCreationAllowed = namespaceCreationAllowed;
|
||||
this.userManager = userManager;
|
||||
this.serviceAccountName = serviceAccountName;
|
||||
this.clientFactory = clientFactory;
|
||||
this.cheClientFactory = cheClientFactory;
|
||||
this.defaultNamespaceName = defaultNamespaceName;
|
||||
|
|
@ -138,6 +129,7 @@ public class KubernetesNamespaceFactory {
|
|||
this.sharedPool = sharedPool;
|
||||
this.labelNamespaces = labelNamespaces;
|
||||
this.annotateNamespaces = annotateNamespaces;
|
||||
this.namespaceConfigurators = ImmutableSet.copyOf(namespaceConfigurators);
|
||||
|
||||
//noinspection UnstableApiUsage
|
||||
Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("=");
|
||||
|
|
@ -162,14 +154,6 @@ public class KubernetesNamespaceFactory {
|
|||
+ " The current value is: `%s`.",
|
||||
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)
|
||||
throws InfrastructureException {
|
||||
try {
|
||||
Namespace namespace = clientFactory.create().namespaces().withName(name).get();
|
||||
Namespace namespace = cheClientFactory.create().namespaces().withName(name).get();
|
||||
if (namespace == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
|
|
@ -336,8 +320,10 @@ public class KubernetesNamespaceFactory {
|
|||
public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
|
||||
KubernetesNamespace namespace = get(identity);
|
||||
|
||||
var subject = EnvironmentContext.getCurrent().getSubject();
|
||||
NamespaceResolutionContext resolutionCtx =
|
||||
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject());
|
||||
new NamespaceResolutionContext(
|
||||
identity.getWorkspaceId(), subject.getUserId(), subject.getUserName());
|
||||
Map<String, String> namespaceAnnotationsEvaluated =
|
||||
evaluateAnnotationPlaceholders(resolutionCtx);
|
||||
|
||||
|
|
@ -346,44 +332,7 @@ public class KubernetesNamespaceFactory {
|
|||
labelNamespaces ? namespaceLabels : emptyMap(),
|
||||
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
|
||||
|
||||
if (namespace
|
||||
.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();
|
||||
}
|
||||
configureNamespace(resolutionCtx, namespace.getName());
|
||||
|
||||
return namespace;
|
||||
}
|
||||
|
|
@ -590,7 +539,7 @@ public class KubernetesNamespaceFactory {
|
|||
NamespaceResolutionContext namespaceCtx) throws InfrastructureException {
|
||||
try {
|
||||
List<Namespace> workspaceNamespaces =
|
||||
clientFactory.create().namespaces().withLabels(namespaceLabels).list().getItems();
|
||||
cheClientFactory.create().namespaces().withLabels(namespaceLabels).list().getItems();
|
||||
if (!workspaceNamespaces.isEmpty()) {
|
||||
Map<String, String> evaluatedAnnotations = evaluateAnnotationPlaceholders(namespaceCtx);
|
||||
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
|
||||
* 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) {
|
||||
checkArgument(!isNullOrEmpty(namespace));
|
||||
String evaluated = namespace;
|
||||
|
|
@ -710,7 +642,7 @@ public class KubernetesNamespaceFactory {
|
|||
preferences.put(NAMESPACE_TEMPLATE_ATTRIBUTE, defaultNamespaceName);
|
||||
preferenceManager.update(owner, preferences);
|
||||
} 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) {
|
||||
namespaceName =
|
||||
namespaceName
|
||||
.toLowerCase()
|
||||
.replaceAll("[^-a-zA-Z0-9]", "-") // replace invalid chars with '-'
|
||||
.replaceAll("-+", "-") // replace multiple '-' with single ones
|
||||
.replaceAll("^-|-$", ""); // trim dashes at beginning/end of the string
|
||||
|
|
@ -755,19 +688,4 @@ public class KubernetesNamespaceFactory {
|
|||
namespaceName.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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,6 @@ public interface NamespaceConfigurator {
|
|||
* @param namespaceResolutionContext users namespace context
|
||||
* @throws InfrastructureException when any error occurs
|
||||
*/
|
||||
public void configure(NamespaceResolutionContext namespaceResolutionContext)
|
||||
void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
|
||||
throws InfrastructureException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import java.util.Base64;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
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.NamespaceResolutionContext;
|
||||
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
|
||||
|
|
@ -39,39 +39,36 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesN
|
|||
*
|
||||
* @author Pavol Baran
|
||||
*/
|
||||
@Singleton
|
||||
public class UserPreferencesConfigurator implements NamespaceConfigurator {
|
||||
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 int PREFERENCE_NAME_MAX_LENGTH = 253;
|
||||
|
||||
private final KubernetesNamespaceFactory namespaceFactory;
|
||||
private final KubernetesClientFactory clientFactory;
|
||||
private final UserManager userManager;
|
||||
private final PreferenceManager preferenceManager;
|
||||
|
||||
@Inject
|
||||
public UserPreferencesConfigurator(
|
||||
KubernetesNamespaceFactory namespaceFactory,
|
||||
KubernetesClientFactory clientFactory,
|
||||
UserManager userManager,
|
||||
PreferenceManager preferenceManager) {
|
||||
this.namespaceFactory = namespaceFactory;
|
||||
this.clientFactory = clientFactory;
|
||||
this.userManager = userManager;
|
||||
this.preferenceManager = preferenceManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(NamespaceResolutionContext namespaceResolutionContext)
|
||||
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
|
||||
throws InfrastructureException {
|
||||
Secret userPreferencesSecret = preparePreferencesSecret(namespaceResolutionContext);
|
||||
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
|
||||
|
||||
try {
|
||||
clientFactory
|
||||
.create()
|
||||
.secrets()
|
||||
.inNamespace(namespace)
|
||||
.inNamespace(namespaceName)
|
||||
.createOrReplace(userPreferencesSecret);
|
||||
} catch (KubernetesClientException e) {
|
||||
throw new InfrastructureException(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.Base64;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
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.NamespaceResolutionContext;
|
||||
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
|
||||
|
|
@ -37,31 +37,30 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesN
|
|||
*
|
||||
* @author Pavol Baran
|
||||
*/
|
||||
@Singleton
|
||||
public class UserProfileConfigurator implements NamespaceConfigurator {
|
||||
private static final String USER_PROFILE_SECRET_NAME = "user-profile";
|
||||
private static final String USER_PROFILE_SECRET_MOUNT_PATH = "/config/user/profile";
|
||||
|
||||
private final KubernetesNamespaceFactory namespaceFactory;
|
||||
private final KubernetesClientFactory clientFactory;
|
||||
private final UserManager userManager;
|
||||
|
||||
@Inject
|
||||
public UserProfileConfigurator(
|
||||
KubernetesNamespaceFactory namespaceFactory,
|
||||
KubernetesClientFactory clientFactory,
|
||||
UserManager userManager) {
|
||||
this.namespaceFactory = namespaceFactory;
|
||||
public UserProfileConfigurator(KubernetesClientFactory clientFactory, UserManager userManager) {
|
||||
this.clientFactory = clientFactory;
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(NamespaceResolutionContext namespaceResolutionContext)
|
||||
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
|
||||
throws InfrastructureException {
|
||||
Secret userProfileSecret = prepareProfileSecret(namespaceResolutionContext);
|
||||
String namespace = namespaceFactory.evaluateNamespaceName(namespaceResolutionContext);
|
||||
try {
|
||||
clientFactory.create().secrets().inNamespace(namespace).createOrReplace(userProfileSecret);
|
||||
clientFactory
|
||||
.create()
|
||||
.secrets()
|
||||
.inNamespace(namespaceName)
|
||||
.createOrReplace(userProfileSecret);
|
||||
} catch (KubernetesClientException e) {
|
||||
throw new InfrastructureException(
|
||||
"Error occurred while trying to create user profile secret.", e);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
package org.eclipse.che.workspace.infrastructure.kubernetes.provision;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Namespace;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.RuntimeIdentityImpl;
|
||||
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 {
|
||||
private final KubernetesNamespaceFactory namespaceFactory;
|
||||
private final Set<NamespaceConfigurator> namespaceConfigurators;
|
||||
|
||||
@Inject
|
||||
public NamespaceProvisioner(
|
||||
KubernetesNamespaceFactory namespaceFactory,
|
||||
Set<NamespaceConfigurator> namespaceConfigurators) {
|
||||
public NamespaceProvisioner(KubernetesNamespaceFactory 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)
|
||||
throws InfrastructureException {
|
||||
KubernetesNamespace namespace =
|
||||
|
|
@ -51,21 +46,9 @@ public class NamespaceProvisioner {
|
|||
namespaceResolutionContext.getUserId(),
|
||||
namespaceFactory.evaluateNamespaceName(namespaceResolutionContext)));
|
||||
|
||||
KubernetesNamespaceMeta namespaceMeta =
|
||||
namespaceFactory
|
||||
.fetchNamespace(namespace.getName())
|
||||
.orElseThrow(
|
||||
() ->
|
||||
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);
|
||||
}
|
||||
return namespaceFactory
|
||||
.fetchNamespace(namespace.getName())
|
||||
.orElseThrow(
|
||||
() -> new InfrastructureException("Not able to find namespace " + namespace.getName()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.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.SECRETS_ROLE_NAME;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory.NAMESPACE_TEMPLATE_ATTRIBUTE;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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.Status;
|
||||
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.RoleBindingList;
|
||||
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.api.server.impls.KubernetesNamespaceMetaImpl;
|
||||
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.util.KubernetesSharedPool;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
|
@ -145,6 +151,7 @@ public class KubernetesNamespaceFactoryTest {
|
|||
serverMock.before();
|
||||
k8sClient = spy(serverMock.getClient());
|
||||
lenient().when(clientFactory.create()).thenReturn(k8sClient);
|
||||
lenient().when(cheClientFactory.create()).thenReturn(k8sClient);
|
||||
lenient().when(k8sClient.namespaces()).thenReturn(namespaceOperation);
|
||||
|
||||
lenient().when(namespaceOperation.withName(any())).thenReturn(namespaceResource);
|
||||
|
|
@ -171,14 +178,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -199,14 +205,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -220,14 +225,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void shouldNormaliseNamespaceWhenUserNameStartsWithKube() {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -246,14 +250,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -269,14 +272,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void shouldThrowExceptionIfNoDefaultNamespaceIsConfigured() {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -320,14 +322,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -364,14 +365,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -395,14 +395,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -429,14 +428,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
.build());
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -458,14 +456,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -489,28 +486,27 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
Set.of(new CredentialsSecretConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
|
||||
when(toReturnNamespace.secrets()).thenReturn(secrets);
|
||||
when(toReturnNamespace.configMaps()).thenReturn(mock(KubernetesConfigsMaps.class));
|
||||
when(secrets.get()).thenReturn(Collections.emptyList());
|
||||
when(toReturnNamespace.getName()).thenReturn("namespaceName");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
lenient().when(k8sClient.secrets()).thenReturn(mixedOperation);
|
||||
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(k8sClient.secrets()).thenReturn(mixedOperation);
|
||||
when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(namespaceResource.get()).thenReturn(null);
|
||||
when(cheClientFactory.create()).thenReturn(k8sClient);
|
||||
when(clientFactory.create()).thenReturn(k8sClient);
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -531,28 +527,25 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class);
|
||||
when(toReturnNamespace.secrets()).thenReturn(mock(KubernetesSecrets.class));
|
||||
when(toReturnNamespace.configMaps()).thenReturn(configsMaps);
|
||||
when(configsMaps.get(eq(PREFERENCES_CONFIGMAP_NAME))).thenReturn(empty());
|
||||
when(toReturnNamespace.getName()).thenReturn("namespaceName");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
lenient().when(k8sClient.configMaps()).thenReturn(mixedOperation);
|
||||
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(k8sClient.configMaps()).thenReturn(mixedOperation);
|
||||
when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(namespaceResource.get()).thenReturn(null);
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -567,19 +560,60 @@ public class KubernetesNamespaceFactoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotCreateCredentialsSecretIfExists() throws Exception {
|
||||
public void testAllConfiguratorsAreCalledWhenCreatingNamespace() throws InfrastructureException {
|
||||
// given
|
||||
String namespaceName = "testNamespaceName";
|
||||
NamespaceConfigurator configurator1 = Mockito.mock(NamespaceConfigurator.class);
|
||||
NamespaceConfigurator configurator2 = Mockito.mock(NamespaceConfigurator.class);
|
||||
Set<NamespaceConfigurator> namespaceConfigurators = Set.of(configurator1, configurator2);
|
||||
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
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,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -607,14 +641,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -643,14 +676,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -669,14 +701,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -700,14 +731,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -723,7 +753,6 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
// then
|
||||
assertEquals(toReturnNamespace, namespace);
|
||||
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
|
||||
verify(toReturnNamespace).prepare(eq(false), any(), any());
|
||||
}
|
||||
|
||||
|
|
@ -733,14 +762,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -757,7 +785,6 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
// then
|
||||
assertEquals(toReturnNamespace, namespace);
|
||||
verify(namespaceFactory, never()).doCreateServiceAccount(any(), any());
|
||||
verify(toReturnNamespace).prepare(eq(false), any(), any());
|
||||
}
|
||||
|
||||
|
|
@ -765,17 +792,18 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined()
|
||||
throws Exception {
|
||||
// given
|
||||
var serviceAccountCfg =
|
||||
spy(new WorkspaceServiceAccountConfigurator("serviceAccount", "", clientFactory));
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"serviceAccount",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
Set.of(serviceAccountCfg),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -783,13 +811,12 @@ public class KubernetesNamespaceFactoryTest {
|
|||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
||||
KubernetesWorkspaceServiceAccount serviceAccount =
|
||||
mock(KubernetesWorkspaceServiceAccount.class);
|
||||
doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any());
|
||||
doReturn(serviceAccount).when(serviceAccountCfg).doCreateServiceAccount(any(), any());
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -797,24 +824,25 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory.getOrCreate(identity);
|
||||
|
||||
// then
|
||||
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
|
||||
verify(serviceAccountCfg).doCreateServiceAccount("workspace123", "workspace123");
|
||||
verify(serviceAccount).prepare();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBindToAllConfiguredClusterRoles() throws Exception {
|
||||
// given
|
||||
var serviceAccountConfigurator =
|
||||
new WorkspaceServiceAccountConfigurator("serviceAccount", "cr2, cr3", clientFactory);
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"serviceAccount",
|
||||
"cr2, cr3",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
Set.of(serviceAccountConfigurator),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -822,10 +850,10 @@ public class KubernetesNamespaceFactoryTest {
|
|||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
when(k8sClient.supportsApiPath(eq("/apis/metrics.k8s.io"))).thenReturn(true);
|
||||
when(cheClientFactory.create()).thenReturn(k8sClient);
|
||||
when(clientFactory.create(any())).thenReturn(k8sClient);
|
||||
|
||||
// pre-create the cluster roles
|
||||
|
|
@ -848,7 +876,6 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory.getOrCreate(identity);
|
||||
|
||||
// then
|
||||
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
|
||||
|
||||
ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list();
|
||||
assertEquals(sas.getItems().size(), 1);
|
||||
|
|
@ -881,19 +908,20 @@ public class KubernetesNamespaceFactoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateExecAndViewRolesAndBindings() throws Exception {
|
||||
public void shouldCreateAndBindCredentialsSecretRole() throws Exception {
|
||||
// given
|
||||
var serviceAccountConfigurator =
|
||||
new WorkspaceServiceAccountConfigurator("serviceAccount", "cr2, cr3", clientFactory);
|
||||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"serviceAccount",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
Set.of(serviceAccountConfigurator),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -901,11 +929,70 @@ public class KubernetesNamespaceFactoryTest {
|
|||
pool));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
|
||||
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
|
||||
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
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -913,7 +1000,6 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory.getOrCreate(identity);
|
||||
|
||||
// then
|
||||
verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123");
|
||||
|
||||
ServiceAccountList sas = k8sClient.serviceAccounts().inNamespace("workspace123").list();
|
||||
assertEquals(sas.getItems().size(), 1);
|
||||
|
|
@ -951,62 +1037,19 @@ public class KubernetesNamespaceFactoryTest {
|
|||
"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
|
||||
public void
|
||||
testEvalNamespaceUsesNamespaceDefaultIfWorkspaceDoesntRecordNamespaceAndLegacyNamespaceDoesntExist()
|
||||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1025,14 +1068,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void testEvalNamespaceUsesNamespaceFromUserPreferencesIfExist() throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1056,14 +1098,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>-<username>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1088,14 +1129,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>-<username>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1120,14 +1160,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<username>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1148,14 +1187,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1178,14 +1216,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void testEvalNamespaceTreatsWorkspaceRecordedNamespaceLiterally() throws Exception {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1229,14 +1266,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1256,14 +1292,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1296,14 +1331,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-cha-cha-cha",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1335,14 +1369,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-cha-cha-cha",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1386,14 +1419,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"try_placeholder_here=<username>",
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1412,14 +1444,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceFactory =
|
||||
spy(
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
"try_placeholder_here=<username>",
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1444,14 +1475,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void normalizeTest(String raw, String expected) {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1464,14 +1494,13 @@ public class KubernetesNamespaceFactoryTest {
|
|||
public void normalizeLengthTest() {
|
||||
namespaceFactory =
|
||||
new KubernetesNamespaceFactory(
|
||||
"",
|
||||
"",
|
||||
"che-<userid>",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -1551,14 +1580,15 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
private void prepareNamespace(KubernetesNamespace namespace) throws InfrastructureException {
|
||||
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
|
||||
lenient().when(namespace.secrets()).thenReturn(secrets);
|
||||
KubernetesConfigsMaps configmaps = mock(KubernetesConfigsMaps.class);
|
||||
when(namespace.secrets()).thenReturn(secrets);
|
||||
when(namespace.configMaps()).thenReturn(configmaps);
|
||||
lenient().when(namespace.secrets()).thenReturn(secrets);
|
||||
lenient().when(namespace.configMaps()).thenReturn(configmaps);
|
||||
Secret secretMock = mock(Secret.class);
|
||||
ObjectMeta objectMeta = mock(ObjectMeta.class);
|
||||
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
|
||||
when(secretMock.getMetadata()).thenReturn(objectMeta);
|
||||
when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
|
||||
lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
|
||||
lenient().when(secretMock.getMetadata()).thenReturn(objectMeta);
|
||||
lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
|
||||
}
|
||||
|
||||
private Namespace createNamespace(String name, String phase) {
|
||||
|
|
@ -1574,6 +1604,6 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
private KubernetesNamespaceMeta testProvisioning(NamespaceResolutionContext context)
|
||||
throws InfrastructureException {
|
||||
return new NamespaceProvisioner(namespaceFactory, emptySet()).provision(context);
|
||||
return new NamespaceProvisioner(namespaceFactory).provision(context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ public class KubernetesNamespaceTest {
|
|||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
lenient().when(clientFactory.create(anyString())).thenReturn(kubernetesClient);
|
||||
lenient().when(cheClientFactory.create(anyString())).thenReturn(kubernetesClient);
|
||||
|
||||
lenient().doReturn(namespaceOperation).when(kubernetesClient).namespaces();
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ public class UserPreferencesConfiguratorTest {
|
|||
|
||||
@Test
|
||||
public void shouldCreatePreferencesSecret() throws InfrastructureException {
|
||||
userPreferencesConfigurator.configure(context);
|
||||
userPreferencesConfigurator.configure(context, USER_NAMESPACE);
|
||||
List<Secret> secrets =
|
||||
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
|
||||
assertEquals(secrets.size(), 1);
|
||||
|
|
@ -99,7 +99,7 @@ public class UserPreferencesConfiguratorTest {
|
|||
"Preferences of user with id:" + USER_ID + " cannot be retrieved.")
|
||||
public void shouldNotCreateSecretOnException() throws ServerException, InfrastructureException {
|
||||
when(preferenceManager.find(USER_ID)).thenThrow(new ServerException("test exception"));
|
||||
userPreferencesConfigurator.configure(context);
|
||||
userPreferencesConfigurator.configure(context, USER_NAMESPACE);
|
||||
fail("InfrastructureException should have been thrown.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
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.NamespaceResolutionContext;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
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_NAMESPACE = "user-namespace";
|
||||
|
||||
@Mock private KubernetesNamespaceFactory namespaceFactory;
|
||||
@Mock private KubernetesClientFactory clientFactory;
|
||||
@Mock private UserManager userManager;
|
||||
|
||||
|
|
@ -63,7 +60,6 @@ public class UserProfileConfiguratorTest {
|
|||
kubernetesServer.before();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +70,7 @@ public class UserProfileConfiguratorTest {
|
|||
|
||||
@Test
|
||||
public void shouldCreateProfileSecret() throws InfrastructureException {
|
||||
userProfileConfigurator.configure(context);
|
||||
userProfileConfigurator.configure(context, USER_NAMESPACE);
|
||||
List<Secret> secrets =
|
||||
kubernetesServer.getClient().secrets().inNamespace(USER_NAMESPACE).list().getItems();
|
||||
assertEquals(secrets.size(), 1);
|
||||
|
|
@ -87,7 +83,7 @@ public class UserProfileConfiguratorTest {
|
|||
public void shouldNotCreateSecretOnException()
|
||||
throws NotFoundException, ServerException, InfrastructureException {
|
||||
when(userManager.getById(USER_ID)).thenThrow(new ServerException("test exception"));
|
||||
userProfileConfigurator.configure(context);
|
||||
userProfileConfigurator.configure(context, USER_NAMESPACE);
|
||||
fail("InfrastructureException should have been thrown.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +134,10 @@
|
|||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-keycloak-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
|
@ -148,6 +152,22 @@
|
|||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-dto</artifactId>
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
|
|||
private static final String BEFORE_TOKEN = "access_token=";
|
||||
private static final String AFTER_TOKEN = "&expires";
|
||||
|
||||
private final KubernetesClientConfigFactory configBuilder;
|
||||
|
||||
@Inject
|
||||
public OpenShiftClientFactory(
|
||||
KubernetesClientConfigFactory configBuilder,
|
||||
|
|
@ -72,6 +70,7 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
|
|||
int connectionPoolKeepAlive,
|
||||
EventListener eventListener) {
|
||||
super(
|
||||
configBuilder,
|
||||
masterUrl,
|
||||
doTrustCerts,
|
||||
maxConcurrentRequests,
|
||||
|
|
@ -79,7 +78,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
|
|||
maxIdleConnections,
|
||||
connectionPoolKeepAlive,
|
||||
eventListener);
|
||||
this.configBuilder = configBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,7 +94,7 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
|
|||
*/
|
||||
public OpenShiftClient createOC(String workspaceId) throws InfrastructureException {
|
||||
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.
|
||||
*/
|
||||
public OpenShiftClient createOC() throws InfrastructureException {
|
||||
return createOC(buildConfig(getDefaultConfig(), null));
|
||||
return create(buildConfig(getDefaultConfig(), null));
|
||||
}
|
||||
|
||||
public OpenShiftClient createAuthenticatedClient(String token) {
|
||||
Config config = getDefaultConfig();
|
||||
config.setOauthToken(token);
|
||||
return createOC(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();
|
||||
return create(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -147,19 +135,6 @@ public class OpenShiftClientFactory extends KubernetesClientFactory {
|
|||
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
|
||||
protected Interceptor buildKubernetesInterceptor(Config config) {
|
||||
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(
|
||||
clientForConfig(config), config, this::initializeRequestTracing);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.KubernetesEnvironmentFactory;
|
||||
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.PreferencesConfigMapConfigurator;
|
||||
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.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.project.OpenShiftProjectFactory;
|
||||
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.OpenshiftTrustedCAProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner;
|
||||
|
|
@ -117,6 +121,10 @@ public class OpenShiftInfraModule extends AbstractModule {
|
|||
Multibinder.newSetBinder(binder(), NamespaceConfigurator.class);
|
||||
namespaceConfigurators.addBinding().to(UserProfileConfigurator.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);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
*/
|
||||
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.REALM_SETTING;
|
||||
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
|
|
|
|||
|
|
@ -16,15 +16,6 @@ import static com.google.common.base.MoreObjects.firstNonNull;
|
|||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
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 javax.inject.Inject;
|
||||
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.
|
||||
* 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
|
||||
public class OpenshiftTokenInitializationFilter
|
||||
|
|
@ -57,9 +45,6 @@ public class OpenshiftTokenInitializationFilter
|
|||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(OpenshiftTokenInitializationFilter.class);
|
||||
|
||||
private static final List<String> UNAUTHORIZED_ENDPOINT_PATHS =
|
||||
Collections.singletonList("/system/state");
|
||||
|
||||
private final PermissionChecker permissionChecker;
|
||||
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.
|
||||
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()");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ public class OpenShiftProject extends KubernetesNamespace {
|
|||
|
||||
private final OpenShiftRoutes routes;
|
||||
private final OpenShiftClientFactory clientFactory;
|
||||
private final KubernetesClientFactory cheClientFactory;
|
||||
private final CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory;
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -78,7 +77,6 @@ public class OpenShiftProject extends KubernetesNamespace {
|
|||
ingresses,
|
||||
secrets,
|
||||
configMaps);
|
||||
this.cheClientFactory = cheClientFactory;
|
||||
this.clientFactory = clientFactory;
|
||||
this.routes = routes;
|
||||
this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory;
|
||||
|
|
@ -93,7 +91,6 @@ public class OpenShiftProject extends KubernetesNamespace {
|
|||
String workspaceId) {
|
||||
super(clientFactory, cheClientFactory, executor, name, workspaceId);
|
||||
this.clientFactory = clientFactory;
|
||||
this.cheClientFactory = cheClientFactory;
|
||||
this.routes = new OpenShiftRoutes(name, workspaceId, clientFactory);
|
||||
this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,23 +16,18 @@ import static java.lang.String.format;
|
|||
import static java.util.Collections.emptyList;
|
||||
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.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.inject.Inject;
|
||||
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.Secret;
|
||||
import io.fabric8.kubernetes.api.model.SecretBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.openshift.api.model.Project;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Named;
|
||||
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.shared.KubernetesNamespaceMeta;
|
||||
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.openshift.CheServerOpenshiftClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.Constants;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftStopWorkspaceRoleProvisioner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -67,14 +62,11 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
private final boolean initWithCheServerSa;
|
||||
private final OpenShiftClientFactory clientFactory;
|
||||
private final CheServerOpenshiftClientFactory cheOpenShiftClientFactory;
|
||||
private final OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
|
||||
|
||||
private final String oAuthIdentityProvider;
|
||||
|
||||
@Inject
|
||||
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,
|
||||
@Named("che.infra.kubernetes.namespace.creation_allowed") boolean namespaceCreationAllowed,
|
||||
@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.annotations") String projectAnnotations,
|
||||
@Named("che.infra.openshift.project.init_with_server_sa") boolean initWithCheServerSa,
|
||||
Set<NamespaceConfigurator> namespaceConfigurators,
|
||||
OpenShiftClientFactory clientFactory,
|
||||
CheServerKubernetesClientFactory cheClientFactory,
|
||||
CheServerOpenshiftClientFactory cheOpenShiftClientFactory,
|
||||
OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner,
|
||||
UserManager userManager,
|
||||
PreferenceManager preferenceManager,
|
||||
KubernetesSharedPool sharedPool,
|
||||
@Nullable @Named("che.infra.openshift.oauth_identity_provider")
|
||||
String oAuthIdentityProvider) {
|
||||
super(
|
||||
serviceAccountName,
|
||||
clusterRoleNames,
|
||||
defaultNamespaceName,
|
||||
namespaceCreationAllowed,
|
||||
labelProjects,
|
||||
annotateProjects,
|
||||
projectLabels,
|
||||
projectAnnotations,
|
||||
namespaceConfigurators,
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
userManager,
|
||||
|
|
@ -108,15 +99,16 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
this.initWithCheServerSa = initWithCheServerSa;
|
||||
this.clientFactory = clientFactory;
|
||||
this.cheOpenShiftClientFactory = cheOpenShiftClientFactory;
|
||||
this.stopWorkspaceRoleProvisioner = stopWorkspaceRoleProvisioner;
|
||||
this.oAuthIdentityProvider = oAuthIdentityProvider;
|
||||
}
|
||||
|
||||
public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException {
|
||||
OpenShiftProject osProject = get(identity);
|
||||
|
||||
var subject = EnvironmentContext.getCurrent().getSubject();
|
||||
NamespaceResolutionContext resolutionCtx =
|
||||
new NamespaceResolutionContext(EnvironmentContext.getCurrent().getSubject());
|
||||
new NamespaceResolutionContext(
|
||||
identity.getWorkspaceId(), subject.getUserId(), subject.getUserName());
|
||||
Map<String, String> namespaceAnnotationsEvaluated =
|
||||
evaluateAnnotationPlaceholders(resolutionCtx);
|
||||
|
||||
|
|
@ -126,50 +118,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
labelNamespaces ? namespaceLabels : emptyMap(),
|
||||
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
|
||||
|
||||
// create credentials secret
|
||||
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);
|
||||
}
|
||||
configureNamespace(resolutionCtx, osProject.getName());
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
@ -218,12 +163,6 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
workspaceId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
OpenShiftWorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) {
|
||||
return new OpenShiftWorkspaceServiceAccount(
|
||||
workspaceId, projectName, getServiceAccountName(), getClusterRoleNames(), clientFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<KubernetesNamespaceMeta> fetchNamespace(String name)
|
||||
throws InfrastructureException {
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory
|
|||
* @see
|
||||
* org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount
|
||||
*/
|
||||
class OpenShiftWorkspaceServiceAccount
|
||||
public class OpenShiftWorkspaceServiceAccount
|
||||
extends AbstractWorkspaceServiceAccount<OpenShiftClient, Role, RoleBinding> {
|
||||
|
||||
OpenShiftWorkspaceServiceAccount(
|
||||
public OpenShiftWorkspaceServiceAccount(
|
||||
String workspaceId,
|
||||
String projectName,
|
||||
String serviceAccountName,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
* Contributors:
|
||||
* 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.openshift.api.model.PolicyRuleBuilder;
|
||||
|
|
@ -20,8 +22,12 @@ import io.fabric8.openshift.api.model.RoleBuilder;
|
|||
import io.fabric8.openshift.client.OpenShiftClient;
|
||||
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.environment.CheInstallationLocation;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -32,27 +38,37 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
* @author Tom George
|
||||
*/
|
||||
public class OpenShiftStopWorkspaceRoleProvisioner {
|
||||
@Singleton
|
||||
public class OpenShiftStopWorkspaceRoleConfigurator implements NamespaceConfigurator {
|
||||
|
||||
private final OpenShiftClientFactory clientFactory;
|
||||
private final String installationLocation;
|
||||
private final boolean stopWorkspaceRoleEnabled;
|
||||
private final String oAuthIdentityProvider;
|
||||
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(OpenShiftStopWorkspaceRoleProvisioner.class);
|
||||
LoggerFactory.getLogger(OpenShiftStopWorkspaceRoleConfigurator.class);
|
||||
|
||||
@Inject
|
||||
public OpenShiftStopWorkspaceRoleProvisioner(
|
||||
public OpenShiftStopWorkspaceRoleConfigurator(
|
||||
OpenShiftClientFactory clientFactory,
|
||||
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 {
|
||||
this.clientFactory = clientFactory;
|
||||
this.installationLocation = installationLocation.getInstallationLocationNamespace();
|
||||
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) {
|
||||
OpenShiftClient osClient = clientFactory.createOC();
|
||||
String stopWorkspacesRoleName = "workspace-stop";
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
*/
|
||||
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.REALM_SETTING;
|
||||
import static org.eclipse.che.multiuser.oidc.OIDCInfoProvider.AUTH_SERVER_URL_SETTING;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
*/
|
||||
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.when;
|
||||
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.openshift.api.model.User;
|
||||
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 org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
|
|
@ -55,10 +48,6 @@ public class OpenshiftTokenInitializationFilterTest {
|
|||
@Mock private User openshiftUser;
|
||||
@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 USER_UID = "almost-certainly-unique-id";
|
||||
private static final String USERNAME = "test_username";
|
||||
|
|
@ -111,7 +100,7 @@ public class OpenshiftTokenInitializationFilterTest {
|
|||
|
||||
@Test
|
||||
public void extractSubjectCreatesSubjectWithCurrentlyAuthenticatedUser()
|
||||
throws InfrastructureException, ServerException, ConflictException {
|
||||
throws ServerException, ConflictException {
|
||||
when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient);
|
||||
when(openShiftClient.currentUser()).thenReturn(openshiftUser);
|
||||
when(openshiftUser.getMetadata()).thenReturn(openshiftUserMeta);
|
||||
|
|
@ -128,27 +117,6 @@ public class OpenshiftTokenInitializationFilterTest {
|
|||
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
|
||||
public void invalidTokenShouldBeHandledAsMissing() throws Exception {
|
||||
when(openShiftClientFactory.createAuthenticatedClient(TOKEN)).thenReturn(openShiftClient);
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ package org.eclipse.che.workspace.infrastructure.openshift.project;
|
|||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.emptySet;
|
||||
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.PHASE_ATTRIBUTE;
|
||||
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.never;
|
||||
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 static org.testng.Assert.assertEquals;
|
||||
|
|
@ -58,7 +57,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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.model.workspace.runtime.RuntimeIdentity;
|
||||
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.namespace.KubernetesConfigsMaps;
|
||||
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.openshift.CheServerOpenshiftClientFactory;
|
||||
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.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
|
|
@ -110,7 +113,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
@Mock private OpenShiftClientFactory clientFactory;
|
||||
@Mock private CheServerKubernetesClientFactory cheClientFactory;
|
||||
@Mock private CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory;
|
||||
@Mock private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
|
||||
@Mock private WorkspaceManager workspaceManager;
|
||||
@Mock private UserManager userManager;
|
||||
@Mock private PreferenceManager preferenceManager;
|
||||
|
|
@ -131,6 +133,7 @@ public class OpenShiftProjectFactoryTest {
|
|||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
lenient().when(clientFactory.createOC()).thenReturn(osClient);
|
||||
lenient().when(clientFactory.create()).thenReturn(osClient);
|
||||
lenient().when(osClient.projects()).thenReturn(projectOperation);
|
||||
|
||||
lenient()
|
||||
|
|
@ -162,8 +165,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -171,10 +172,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -195,8 +196,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
System.out.println("2--------");
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -204,10 +203,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -228,8 +227,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
throws Exception {
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
|
|
@ -237,10 +234,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -266,8 +263,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
"",
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -275,10 +270,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -305,8 +300,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
"",
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -314,10 +307,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -340,8 +333,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
"",
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -349,10 +340,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -385,8 +376,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -394,10 +383,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -424,8 +413,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -433,10 +420,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -463,8 +450,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -472,10 +457,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -492,8 +477,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
throwOnTryToGetProjectsList(new KubernetesClientException("connection refused"));
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<username>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -501,10 +484,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -526,8 +509,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -535,10 +516,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -554,7 +535,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
// then
|
||||
assertEquals(toReturnProject, project);
|
||||
verify(projectFactory, never()).doCreateServiceAccount(any(), any());
|
||||
verify(toReturnProject).prepare(eq(false), eq(false), any(), any());
|
||||
}
|
||||
|
||||
|
|
@ -564,8 +544,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -573,30 +551,28 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
Set.of(new CredentialsSecretConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
when(toReturnProject.getName()).thenReturn("namespace123");
|
||||
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
|
||||
KubernetesConfigsMaps configsMaps = mock(KubernetesConfigsMaps.class);
|
||||
when(toReturnProject.secrets()).thenReturn(secrets);
|
||||
when(toReturnProject.configMaps()).thenReturn(configsMaps);
|
||||
when(secrets.get()).thenReturn(Collections.emptyList());
|
||||
when(configsMaps.get(anyString())).thenReturn(Optional.of(mock(ConfigMap.class)));
|
||||
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
|
||||
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(osClient.secrets()).thenReturn(mixedOperation);
|
||||
when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
Resource<Secret> nullSecret = mock(Resource.class);
|
||||
when(namespaceOperation.withName(CREDENTIALS_SECRET_NAME)).thenReturn(nullSecret);
|
||||
when(nullSecret.get()).thenReturn(null);
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
|
||||
new RuntimeIdentityImpl("workspace123", null, USER_ID, "namespace123");
|
||||
projectFactory.getOrCreate(identity);
|
||||
|
||||
// then
|
||||
|
|
@ -613,8 +589,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -622,31 +596,23 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
when(toReturnProject.getName()).thenReturn("namespace123");
|
||||
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
|
||||
Secret secret = mock(Secret.class);
|
||||
ObjectMeta objectMeta = mock(ObjectMeta.class);
|
||||
when(secret.getMetadata()).thenReturn(objectMeta);
|
||||
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(osClient.configMaps()).thenReturn(mixedOperation);
|
||||
when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
Resource<ConfigMap> nullCm = mock(Resource.class);
|
||||
when(namespaceOperation.withName(PREFERENCES_CONFIGMAP_NAME)).thenReturn(nullCm);
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -666,8 +632,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -675,10 +639,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
Set.of(new CredentialsSecretConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -686,10 +650,14 @@ public class OpenShiftProjectFactoryTest {
|
|||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
prepareProject(toReturnProject);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
when(toReturnProject.getName()).thenReturn("namespace123");
|
||||
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
|
||||
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(osClient.secrets()).thenReturn(mixedOperation);
|
||||
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
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -706,8 +674,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -715,10 +681,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
Set.of(new PreferencesConfigMapConfigurator(clientFactory)),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -726,10 +692,14 @@ public class OpenShiftProjectFactoryTest {
|
|||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
prepareProject(toReturnProject);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
when(toReturnProject.getName()).thenReturn("namespace123");
|
||||
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
|
||||
MixedOperation mixedOperation = mock(MixedOperation.class);
|
||||
lenient().when(osClient.configMaps()).thenReturn(mixedOperation);
|
||||
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
|
||||
when(osClient.configMaps()).thenReturn(mixedOperation);
|
||||
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
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -740,56 +710,13 @@ public class OpenShiftProjectFactoryTest {
|
|||
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
|
||||
public void shouldCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined() throws Exception {
|
||||
var saConf =
|
||||
spy(new OpenShiftWorkspaceServiceAccountConfigurator("serviceAccount", "", clientFactory));
|
||||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"serviceAccount",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -797,22 +724,21 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
Set.of(saConf),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
|
||||
when(toReturnProject.getName()).thenReturn("workspace123");
|
||||
prepareProject(toReturnProject);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
|
||||
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
|
||||
doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any());
|
||||
doReturn(serviceAccount).when(saConf).createServiceAccount("workspace123", "workspace123");
|
||||
|
||||
// when
|
||||
RuntimeIdentity identity =
|
||||
|
|
@ -820,52 +746,7 @@ public class OpenShiftProjectFactoryTest {
|
|||
projectFactory.getOrCreate(identity);
|
||||
|
||||
// then
|
||||
verify(projectFactory).doCreateServiceAccount("workspace123", "workspace123");
|
||||
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
|
||||
|
|
@ -886,8 +767,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
"",
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -895,10 +774,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -921,8 +800,6 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
projectFactory =
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -930,10 +807,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
"try_placeholder_here=<username>",
|
||||
NAMESPACE_ANNOTATIONS,
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -946,13 +823,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
@Test
|
||||
public void testUsernamePlaceholderInAnnotationsIsEvaluated() throws InfrastructureException {
|
||||
|
||||
// given
|
||||
projectFactory =
|
||||
spy(
|
||||
new OpenShiftProjectFactory(
|
||||
"",
|
||||
null,
|
||||
"<userid>-che",
|
||||
true,
|
||||
true,
|
||||
|
|
@ -960,10 +834,10 @@ public class OpenShiftProjectFactoryTest {
|
|||
NAMESPACE_LABELS,
|
||||
"try_placeholder_here=<username>",
|
||||
true,
|
||||
emptySet(),
|
||||
clientFactory,
|
||||
cheClientFactory,
|
||||
cheServerOpenshiftClientFactory,
|
||||
stopWorkspaceRoleProvisioner,
|
||||
userManager,
|
||||
preferenceManager,
|
||||
pool,
|
||||
|
|
@ -983,6 +857,51 @@ public class OpenShiftProjectFactoryTest {
|
|||
.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 {
|
||||
@SuppressWarnings("unchecked")
|
||||
Resource<Project> getProjectByNameOperation = mock(Resource.class);
|
||||
|
|
@ -1010,15 +929,13 @@ public class OpenShiftProjectFactoryTest {
|
|||
|
||||
private void prepareProject(OpenShiftProject project) throws InfrastructureException {
|
||||
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
|
||||
lenient().when(project.secrets()).thenReturn(secrets);
|
||||
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);
|
||||
ObjectMeta objectMeta = mock(ObjectMeta.class);
|
||||
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
|
||||
when(secretMock.getMetadata()).thenReturn(objectMeta);
|
||||
when(secrets.get()).thenReturn(singletonList(secretMock));
|
||||
lenient().when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
|
||||
lenient().when(secretMock.getMetadata()).thenReturn(objectMeta);
|
||||
lenient().when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
|
||||
}
|
||||
|
||||
private void throwOnTryToGetProjectsList(Throwable e) throws Exception {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
* Contributors:
|
||||
* 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.anyString;
|
||||
|
|
@ -17,6 +17,7 @@ import static org.mockito.Mockito.lenient;
|
|||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;
|
||||
|
|
@ -42,15 +43,16 @@ import org.testng.annotations.Listeners;
|
|||
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
|
||||
*/
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
||||
public class OpenShiftStopWorkspaceRoleConfiguratorTest {
|
||||
|
||||
@Mock private CheInstallationLocation cheInstallationLocation;
|
||||
private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner;
|
||||
private OpenShiftStopWorkspaceRoleConfigurator stopWorkspaceRoleProvisioner;
|
||||
|
||||
@Mock private OpenShiftClientFactory clientFactory;
|
||||
@Mock private OpenShiftClient osClient;
|
||||
|
|
@ -123,7 +125,8 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
|||
public void setUp() throws Exception {
|
||||
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che");
|
||||
stopWorkspaceRoleProvisioner =
|
||||
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true);
|
||||
new OpenShiftStopWorkspaceRoleConfigurator(
|
||||
clientFactory, cheInstallationLocation, true, "yes");
|
||||
lenient().when(clientFactory.createOC()).thenReturn(osClient);
|
||||
lenient().when(osClient.roles()).thenReturn(mixedRoleOperation);
|
||||
lenient().when(osClient.roleBindings()).thenReturn(mixedRoleBindingOperation);
|
||||
|
|
@ -160,7 +163,7 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
|||
@Test
|
||||
public void shouldCreateRoleAndRoleBindingWhenRoleDoesNotYetExist()
|
||||
throws InfrastructureException {
|
||||
stopWorkspaceRoleProvisioner.provision("developer-che");
|
||||
stopWorkspaceRoleProvisioner.configure(null, "developer-che");
|
||||
verify(osClient, times(2)).roles();
|
||||
verify(osClient.roles(), times(2)).inNamespace("developer-che");
|
||||
verify(osClient.roles().inNamespace("developer-che")).withName("workspace-stop");
|
||||
|
|
@ -174,7 +177,7 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
|||
@Test
|
||||
public void shouldCreateRoleBindingWhenRoleAlreadyExists() throws InfrastructureException {
|
||||
lenient().when(roleResource.get()).thenReturn(expectedRole);
|
||||
stopWorkspaceRoleProvisioner.provision("developer-che");
|
||||
stopWorkspaceRoleProvisioner.configure(null, "developer-che");
|
||||
verify(osClient, times(1)).roles();
|
||||
verify(osClient).roleBindings();
|
||||
verify(osClient.roleBindings()).inNamespace("developer-che");
|
||||
|
|
@ -185,9 +188,10 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
|||
@Test
|
||||
public void shouldNotCreateRoleBindingWhenStopWorkspaceRolePropertyIsDisabled()
|
||||
throws InfrastructureException {
|
||||
OpenShiftStopWorkspaceRoleProvisioner disabledStopWorkspaceRoleProvisioner =
|
||||
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, false);
|
||||
disabledStopWorkspaceRoleProvisioner.provision("developer-che");
|
||||
OpenShiftStopWorkspaceRoleConfigurator disabledStopWorkspaceRoleProvisioner =
|
||||
new OpenShiftStopWorkspaceRoleConfigurator(
|
||||
clientFactory, cheInstallationLocation, false, "yes");
|
||||
disabledStopWorkspaceRoleProvisioner.configure(null, "developer-che");
|
||||
verify(osClient, never()).roles();
|
||||
verify(osClient, never()).roleBindings();
|
||||
verify(osClient.roleBindings(), never()).inNamespace("developer-che");
|
||||
|
|
@ -197,12 +201,26 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest {
|
|||
public void shouldNotCreateRoleBindingWhenInstallationLocationIsNull()
|
||||
throws InfrastructureException {
|
||||
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn(null);
|
||||
OpenShiftStopWorkspaceRoleProvisioner
|
||||
OpenShiftStopWorkspaceRoleConfigurator
|
||||
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation =
|
||||
new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true);
|
||||
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.provision("developer-che");
|
||||
new OpenShiftStopWorkspaceRoleConfigurator(
|
||||
clientFactory, cheInstallationLocation, true, "yes");
|
||||
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.configure(null, "developer-che");
|
||||
verify(osClient, never()).roles();
|
||||
verify(osClient, never()).roleBindings();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,8 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
|
|||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
|
|
@ -43,6 +45,9 @@ import org.slf4j.LoggerFactory;
|
|||
* <li>Set subject for current request into {@link EnvironmentContext}
|
||||
* </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
|
||||
* @author Max Shaposhnyk (mshaposh@redhat.com)
|
||||
*/
|
||||
|
|
@ -51,6 +56,9 @@ public abstract class MultiUserEnvironmentInitializationFilter<T> implements Fil
|
|||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(MultiUserEnvironmentInitializationFilter.class);
|
||||
|
||||
private static final List<String> UNAUTHORIZED_ENDPOINT_PATHS =
|
||||
Collections.singletonList("/system/state");
|
||||
|
||||
private final SessionStore sessionStore;
|
||||
private final RequestTokenExtractor tokenExtractor;
|
||||
|
||||
|
|
@ -197,9 +205,23 @@ public abstract class MultiUserEnvironmentInitializationFilter<T> implements Fil
|
|||
* @throws IOException 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)
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import static org.mockito.Mockito.when;
|
|||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
|
|
@ -83,6 +85,7 @@ public class MultiUserEnvironmentInitializationFilterTest {
|
|||
// then
|
||||
verify(tokenExtractor).getToken(eq(request));
|
||||
verify(filter).handleMissingToken(eq(request), eq(response), eq(chain));
|
||||
verify(request).getServletPath();
|
||||
verifyNoMoreInteractions(request);
|
||||
verify(filter, never()).getUserId(any());
|
||||
verify(filter, never()).extractSubject(anyString(), any());
|
||||
|
|
@ -100,6 +103,7 @@ public class MultiUserEnvironmentInitializationFilterTest {
|
|||
// then
|
||||
verify(tokenExtractor).getToken(eq(request));
|
||||
verify(filter).handleMissingToken(eq(request), eq(response), eq(chain));
|
||||
verify(request).getServletPath();
|
||||
verifyNoMoreInteractions(request);
|
||||
verify(filter, never()).getUserId(any());
|
||||
verify(filter, never()).extractSubject(anyString(), any());
|
||||
|
|
@ -168,4 +172,23 @@ public class MultiUserEnvironmentInitializationFilterTest {
|
|||
// then
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,14 +30,6 @@
|
|||
<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.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
|
|
@ -102,10 +94,6 @@
|
|||
<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.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
|
|
@ -130,6 +118,10 @@
|
|||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-machine-authentication-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-personal-account</artifactId>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ package org.eclipse.che.multiuser.keycloak.server;
|
|||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
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 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.permission.server.AuthorizedSubject;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -123,8 +123,7 @@ public class KeycloakEnvironmentInitializationFilter
|
|||
|
||||
try {
|
||||
String username =
|
||||
claims.get(
|
||||
keycloakSettings.get().get(KeycloakConstants.USERNAME_CLAIM_SETTING), String.class);
|
||||
claims.get(keycloakSettings.get().get(OIDC_USERNAME_CLAIM_SETTING), String.class);
|
||||
if (username == null) { // fallback to unique id promised by spec
|
||||
// https://openid.net/specs/openid-connect-basic-1_0.html#ClaimStability
|
||||
username = claims.getIssuer() + ":" + claims.getSubject();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import javax.inject.Singleton;
|
|||
import org.eclipse.che.api.core.ApiException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import org.eclipse.che.commons.lang.Pair;
|
|||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ public class KeycloakServiceClient {
|
|||
Pattern.compile("<div id=\"kc-error-message\">(\\s*)<p class=\"instruction\">(.+?)</p>");
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
private JwtParser jwtParser;
|
||||
private final JwtParser jwtParser;
|
||||
|
||||
@Inject
|
||||
public KeycloakServiceClient(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
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.FIXED_REDIRECT_URL_FOR_DASHBOARD;
|
||||
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.JWKS_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.PASSWORD_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.TOKEN_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_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 java.util.Collections;
|
||||
|
|
@ -37,6 +37,7 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
|
||||
/** @author Max Shaposhnik (mshaposh@redhat.com) */
|
||||
@Singleton
|
||||
|
|
@ -54,7 +55,7 @@ public class KeycloakSettings {
|
|||
@Nullable @Named(REALM_SETTING) String realm,
|
||||
@Named(CLIENT_ID_SETTING) String clientId,
|
||||
@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,
|
||||
@Nullable @Named(OSO_ENDPOINT_SETTING) String osoEndpoint,
|
||||
@Nullable @Named(GITHUB_ENDPOINT_SETTING) String gitHubEndpoint,
|
||||
|
|
@ -64,7 +65,8 @@ public class KeycloakSettings {
|
|||
|
||||
Map<String, String> settings = Maps.newHashMap();
|
||||
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(REALM_SETTING, realm);
|
||||
|
||||
|
|
@ -80,9 +82,8 @@ public class KeycloakSettings {
|
|||
serverURL + "/realms/" + realm + "/protocol/openid-connect/token");
|
||||
}
|
||||
|
||||
if (oidcInfo.getEndSessionPublicEndpoint() != null) {
|
||||
settings.put(LOGOUT_ENDPOINT_SETTING, oidcInfo.getEndSessionPublicEndpoint());
|
||||
}
|
||||
oidcInfo.getEndSessionPublicEndpoint().ifPresent(e -> settings.put(LOGOUT_ENDPOINT_SETTING, e));
|
||||
|
||||
if (oidcInfo.getTokenPublicEndpoint() != null) {
|
||||
settings.put(TOKEN_ENDPOINT_SETTING, oidcInfo.getTokenPublicEndpoint());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* available under the terms of the Eclipse Public License 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 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;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCSigningKeyResolver;
|
||||
|
||||
/** Resolves signing key based on id from JWT header */
|
||||
@Singleton
|
||||
public class KeycloakSigningKeyResolver extends SigningKeyResolverAdapter {
|
||||
|
||||
private final JwkProvider jwkProvider;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KeycloakSigningKeyResolver.class);
|
||||
|
||||
public class KeycloakSigningKeyResolver extends OIDCSigningKeyResolver {
|
||||
@Inject
|
||||
KeycloakSigningKeyResolver(JwkProvider jwkProvider) {
|
||||
this.jwkProvider = jwkProvider;
|
||||
super(jwkProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -54,19 +44,4 @@ public class KeycloakSigningKeyResolver extends SigningKeyResolverAdapter {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,20 @@ package org.eclipse.che.multiuser.keycloak.server.deploy;
|
|||
import com.auth0.jwk.JwkProvider;
|
||||
import com.google.inject.AbstractModule;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.SigningKeyResolver;
|
||||
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
|
||||
import org.eclipse.che.api.user.server.TokenValidator;
|
||||
import org.eclipse.che.api.user.server.spi.ProfileDao;
|
||||
import org.eclipse.che.multiuser.api.account.personal.PersonalAccountUserManager;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakConfigurationService;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwkProvider;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwtParserProvider;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakOIDCInfoProvider;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakSigningKeyResolver;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator;
|
||||
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.oidc.OIDCInfo;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCJwkProvider;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCJwtParserProvider;
|
||||
import org.eclipse.che.security.oauth.OAuthAPI;
|
||||
|
||||
public class KeycloakModule extends AbstractModule {
|
||||
|
|
@ -38,9 +40,10 @@ public class KeycloakModule extends AbstractModule {
|
|||
bind(KeycloakConfigurationService.class);
|
||||
|
||||
bind(ProfileDao.class).to(KeycloakProfileDao.class);
|
||||
bind(JwkProvider.class).toProvider(KeycloakJwkProvider.class);
|
||||
bind(JwtParser.class).toProvider(KeycloakJwtParserProvider.class);
|
||||
bind(OIDCInfo.class).toProvider(OIDCInfoProvider.class).asEagerSingleton();
|
||||
bind(JwkProvider.class).toProvider(OIDCJwkProvider.class);
|
||||
bind(SigningKeyResolver.class).to(KeycloakSigningKeyResolver.class);
|
||||
bind(JwtParser.class).toProvider(OIDCJwtParserProvider.class);
|
||||
bind(OIDCInfo.class).toProvider(KeycloakOIDCInfoProvider.class).asEagerSingleton();
|
||||
bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class);
|
||||
|
||||
bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
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.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.anyBoolean;
|
||||
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.permission.server.AuthorizedSubject;
|
||||
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.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
|
|
@ -119,7 +118,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
|
|||
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
|
||||
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()))
|
||||
.thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS));
|
||||
filter =
|
||||
|
|
@ -149,7 +148,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
|
|||
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
|
||||
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()))
|
||||
.thenReturn(mock(UserImpl.class, RETURNS_DEEP_STUBS));
|
||||
filter =
|
||||
|
|
@ -210,7 +209,7 @@ public class KeycloakEnvironmentInitializationFilterTest {
|
|||
Claims claims = new DefaultClaims(claimParams).setSubject("id");
|
||||
DefaultJws<Claims> jws = new DefaultJws<>(new DefaultJwsHeader(), claims, "");
|
||||
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
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
|
||||
when(jwtParser.parseClaimsJws(anyString())).thenReturn(jws);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import org.eclipse.che.api.core.rest.Service;
|
|||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakErrorResponse;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.dto.KeycloakTokenResponse;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.everrest.assured.EverrestJetty;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
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.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.FIXED_REDIRECT_URL_FOR_DASHBOARD;
|
||||
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.JWKS_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.PASSWORD_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.TOKEN_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.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.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.Listeners;
|
||||
|
|
@ -185,7 +187,8 @@ public class KeycloakSettingsTest {
|
|||
public void shouldBeUsedConfigurationFromExternalOIDCProviderWithoutFixedRedirectLinks() {
|
||||
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.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH);
|
||||
when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH);
|
||||
|
|
@ -206,7 +209,7 @@ public class KeycloakSettingsTest {
|
|||
oidcInfo);
|
||||
|
||||
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(REALM_SETTING), CHE_REALM);
|
||||
assertNull(publicSettings.get(AUTH_SERVER_URL_SETTING));
|
||||
|
|
@ -229,7 +232,8 @@ public class KeycloakSettingsTest {
|
|||
public void shouldBeUsedConfigurationFromExternalAuthServer() {
|
||||
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.getUserInfoPublicEndpoint()).thenReturn(SERVER_AUTH_URL + USER_INFO_PATH);
|
||||
when(oidcInfo.getTokenPublicEndpoint()).thenReturn(SERVER_AUTH_URL + TOKEN_URL_PATH);
|
||||
|
|
@ -250,7 +254,7 @@ public class KeycloakSettingsTest {
|
|||
oidcInfo);
|
||||
|
||||
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(REALM_SETTING), CHE_REALM);
|
||||
assertEquals(publicSettings.get(AUTH_SERVER_URL_SETTING), SERVER_AUTH_URL);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static org.testng.Assert.assertNull;
|
|||
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
@ -86,9 +87,8 @@ public class OIDCInfoProviderTest {
|
|||
.willReturn(
|
||||
aResponse().withHeader("Content-Type", "text/html").withBody("broken json")));
|
||||
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.oidcProviderUrl = serverUrl;
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(null, null, serverUrl, CHE_REALM);
|
||||
|
||||
oidcInfoProvider.get();
|
||||
}
|
||||
|
|
@ -100,9 +100,8 @@ public class OIDCInfoProviderTest {
|
|||
.willReturn(
|
||||
aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig)));
|
||||
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.serverURL = serverUrl;
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(serverUrl, null, null, CHE_REALM);
|
||||
OIDCInfo oidcInfo = oidcInfoProvider.get();
|
||||
|
||||
assertEquals(
|
||||
|
|
@ -110,7 +109,7 @@ public class OIDCInfoProviderTest {
|
|||
oidcInfo.getTokenPublicEndpoint());
|
||||
assertEquals(
|
||||
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
|
||||
oidcInfo.getEndSessionPublicEndpoint());
|
||||
oidcInfo.getEndSessionPublicEndpoint().get());
|
||||
assertNull(oidcInfo.getUserInfoInternalEndpoint());
|
||||
assertNull(oidcInfo.getJwksInternalUri());
|
||||
}
|
||||
|
|
@ -150,10 +149,8 @@ public class OIDCInfoProviderTest {
|
|||
.withHeader("Content-Type", "text/html")
|
||||
.withBody(OPEN_ID_CONF_TEMPLATE)));
|
||||
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.serverURL = serverPublicUrl;
|
||||
oidcInfoProvider.serverInternalURL = serverUrl;
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(serverPublicUrl, serverUrl, null, CHE_REALM);
|
||||
OIDCInfo oidcInfo = oidcInfoProvider.get();
|
||||
|
||||
assertEquals(
|
||||
|
|
@ -161,7 +158,7 @@ public class OIDCInfoProviderTest {
|
|||
oidcInfo.getTokenPublicEndpoint());
|
||||
assertEquals(
|
||||
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
|
||||
oidcInfo.getEndSessionPublicEndpoint());
|
||||
oidcInfo.getEndSessionPublicEndpoint().get());
|
||||
assertEquals(
|
||||
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
|
||||
oidcInfo.getUserInfoPublicEndpoint());
|
||||
|
|
@ -215,10 +212,8 @@ public class OIDCInfoProviderTest {
|
|||
.withHeader("Content-Type", "text/html")
|
||||
.withBody(OPEN_ID_CONF_TEMPLATE)));
|
||||
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.serverURL = serverPublicUrl;
|
||||
oidcInfoProvider.serverInternalURL = serverInternalUrl;
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(serverPublicUrl, serverInternalUrl, null, CHE_REALM);
|
||||
OIDCInfo oidcInfo = oidcInfoProvider.get();
|
||||
|
||||
assertEquals(
|
||||
|
|
@ -226,7 +221,7 @@ public class OIDCInfoProviderTest {
|
|||
oidcInfo.getTokenPublicEndpoint());
|
||||
assertEquals(
|
||||
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
|
||||
oidcInfo.getEndSessionPublicEndpoint());
|
||||
oidcInfo.getEndSessionPublicEndpoint().get());
|
||||
assertEquals(
|
||||
serverPublicUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
|
||||
oidcInfo.getUserInfoPublicEndpoint());
|
||||
|
|
@ -253,11 +248,8 @@ public class OIDCInfoProviderTest {
|
|||
.willReturn(
|
||||
aResponse().withHeader("Content-Type", "text/html").withBody(openIdConfig)));
|
||||
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.serverURL = TEST_URL;
|
||||
oidcInfoProvider.serverInternalURL = TEST_URL;
|
||||
oidcInfoProvider.oidcProviderUrl = OIDCProviderUrl;
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(TEST_URL, TEST_URL, OIDCProviderUrl, CHE_REALM);
|
||||
OIDCInfo oidcInfo = oidcInfoProvider.get();
|
||||
|
||||
assertEquals(
|
||||
|
|
@ -265,7 +257,7 @@ public class OIDCInfoProviderTest {
|
|||
oidcInfo.getTokenPublicEndpoint());
|
||||
assertEquals(
|
||||
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/logout",
|
||||
oidcInfo.getEndSessionPublicEndpoint());
|
||||
oidcInfo.getEndSessionPublicEndpoint().get());
|
||||
assertEquals(
|
||||
serverUrl + "/realms/" + CHE_REALM + "/protocol/openid-connect/userinfo",
|
||||
oidcInfo.getUserInfoInternalEndpoint());
|
||||
|
|
@ -278,17 +270,17 @@ public class OIDCInfoProviderTest {
|
|||
expectedExceptions = RuntimeException.class,
|
||||
expectedExceptionsMessageRegExp = "Either the '.*' or '.*' or '.*' property should be set")
|
||||
public void shouldThrowErrorWhenAuthServerWasNotSet() {
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.realm = CHE_REALM;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(null, null, null, CHE_REALM);
|
||||
oidcInfoProvider.get();
|
||||
}
|
||||
|
||||
@Test(
|
||||
expectedExceptions = RuntimeException.class,
|
||||
expectedExceptionsMessageRegExp = "The '.*' property should be set")
|
||||
expectedExceptionsMessageRegExp = "The '.*' property must be set")
|
||||
public void shouldThrowErrorWhenRealmPropertyWasNotSet() {
|
||||
OIDCInfoProvider oidcInfoProvider = new OIDCInfoProvider();
|
||||
oidcInfoProvider.serverURL = TEST_URL;
|
||||
KeycloakOIDCInfoProvider oidcInfoProvider =
|
||||
new KeycloakOIDCInfoProvider(null, null, null, null);
|
||||
oidcInfoProvider.get();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,22 +15,13 @@ package org.eclipse.che.multiuser.keycloak.shared;
|
|||
public class KeycloakConstants {
|
||||
|
||||
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 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_FIXED_REDIRECT_URLS_SETTING =
|
||||
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 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 PROFILE_ENDPOINT_SETTING =
|
||||
|
|
@ -48,8 +39,4 @@ public class KeycloakConstants {
|
|||
KEYCLOAK_SETTING_PREFIX + "redirect_url.dashboard";
|
||||
public static final String FIXED_REDIRECT_URL_FOR_IDE =
|
||||
KEYCLOAK_SETTING_PREFIX + "redirect_url.ide";
|
||||
|
||||
public static String getEndpoint(String apiEndpoint) {
|
||||
return apiEndpoint + KEYCLOAK_SETTINGS_ENDPOINT_PATH;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@
|
|||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-keycloak-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
*/
|
||||
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.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.core.db.cascade.CascadeEventSubscriber;
|
||||
import org.eclipse.che.inject.ConfigurationException;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.eclipse.che.api.core.NotFoundException;
|
|||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.rest.DefaultHttpJsonRequest;
|
||||
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
|
||||
import org.eclipse.che.multiuser.oidc.OIDCInfo;
|
||||
import org.mockito.Mock;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -9,9 +9,12 @@
|
|||
* Contributors:
|
||||
* 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 {
|
||||
|
||||
private final String tokenPublicEndpoint;
|
||||
|
|
@ -38,7 +41,6 @@ public class OIDCInfo {
|
|||
this.userInfoInternalEndpoint = userInfoInternalEndpoint;
|
||||
this.jwksPublicUri = jwksPublicUri;
|
||||
this.jwksInternalUri = jwksInternalUri;
|
||||
|
||||
this.authServerURL = authServerURL;
|
||||
this.authServerPublicURL = authServerPublicURL;
|
||||
}
|
||||
|
|
@ -48,11 +50,6 @@ public class OIDCInfo {
|
|||
return tokenPublicEndpoint;
|
||||
}
|
||||
|
||||
/** @return public log out url. */
|
||||
public String getEndSessionPublicEndpoint() {
|
||||
return endSessionPublicEndpoint;
|
||||
}
|
||||
|
||||
/** @return public url to get user profile information. */
|
||||
public String getUserInfoPublicEndpoint() {
|
||||
return userInfoPublicEndpoint;
|
||||
|
|
@ -85,4 +82,21 @@ public class OIDCInfo {
|
|||
public String getAuthServerPublicURL() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,9 @@
|
|||
* Contributors:
|
||||
* 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 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.JsonParser;
|
||||
|
|
@ -34,32 +30,35 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. These
|
||||
* information is useful to provide access to the Keycloak api.
|
||||
* OIDCInfoProvider retrieves OpenID Connect (OIDC) configuration for well-known endpoint. This
|
||||
* information is useful to provide access to the OIDC api.
|
||||
*/
|
||||
public class OIDCInfoProvider implements Provider<OIDCInfo> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OIDCInfoProvider.class);
|
||||
|
||||
@Inject
|
||||
@Nullable
|
||||
@Named(AUTH_SERVER_URL_SETTING)
|
||||
private static final String OIDC_SETTING_PREFIX = "che.oidc.";
|
||||
|
||||
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;
|
||||
|
||||
@Inject
|
||||
@Nullable
|
||||
@Named(AUTH_SERVER_URL_INTERNAL_SETTING)
|
||||
protected String serverInternalURL;
|
||||
|
||||
@Inject
|
||||
@Nullable
|
||||
@Named(OIDC_PROVIDER_SETTING)
|
||||
protected String oidcProviderUrl;
|
||||
|
||||
@Inject
|
||||
@Nullable
|
||||
@Named(REALM_SETTING)
|
||||
protected String realm;
|
||||
public OIDCInfoProvider(
|
||||
@Nullable @Named(AUTH_SERVER_URL_SETTING) String serverURL,
|
||||
@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. */
|
||||
@Override
|
||||
|
|
@ -84,7 +83,9 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
|
|||
String userInfoPublicEndpoint =
|
||||
setPublicUrl((String) openIdConfiguration.get("userinfo_endpoint"));
|
||||
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 jwksInternalUri = setInternalUrl(jwksPublicUri);
|
||||
String userInfoInternalEndpoint = setInternalUrl(userInfoPublicEndpoint);
|
||||
|
|
@ -107,7 +108,7 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
|
|||
}
|
||||
|
||||
private String getWellKnownEndpoint(String serverAuthUrl) {
|
||||
String wellKnownEndpoint = firstNonNull(oidcProviderUrl, serverAuthUrl + "/realms/" + realm);
|
||||
String wellKnownEndpoint = firstNonNull(oidcProviderUrl, constructServerAuthUrl(serverAuthUrl));
|
||||
if (!wellKnownEndpoint.endsWith("/")) {
|
||||
wellKnownEndpoint = wellKnownEndpoint + "/";
|
||||
}
|
||||
|
|
@ -115,7 +116,11 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
|
|||
return wellKnownEndpoint;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
protected String constructServerAuthUrl(String serverAuthUrl) {
|
||||
return serverAuthUrl;
|
||||
}
|
||||
|
||||
protected void validate() {
|
||||
if (serverURL == null && serverInternalURL == null && oidcProviderUrl == null) {
|
||||
throw new RuntimeException(
|
||||
"Either the '"
|
||||
|
|
@ -126,10 +131,6 @@ public class OIDCInfoProvider implements Provider<OIDCInfo> {
|
|||
+ OIDC_PROVIDER_SETTING
|
||||
+ "' property should be set");
|
||||
}
|
||||
|
||||
if (oidcProviderUrl == null && realm == null) {
|
||||
throw new RuntimeException("The '" + REALM_SETTING + "' property should be set");
|
||||
}
|
||||
}
|
||||
|
||||
private String setInternalUrl(String endpointUrl) {
|
||||
|
|
@ -9,13 +9,12 @@
|
|||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
package org.eclipse.che.multiuser.oidc;
|
||||
|
||||
import com.auth0.jwk.GuavaCachedJwkProvider;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.auth0.jwk.UrlJwkProvider;
|
||||
import com.google.common.base.Strings;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -23,14 +22,14 @@ import javax.inject.Provider;
|
|||
import org.eclipse.che.inject.ConfigurationException;
|
||||
|
||||
/** 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;
|
||||
|
||||
@Inject
|
||||
public KeycloakJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException {
|
||||
public OIDCJwkProvider(OIDCInfo oidcInfo) throws MalformedURLException {
|
||||
final String jwksUrl =
|
||||
isNullOrEmpty(oidcInfo.getJwksInternalUri())
|
||||
Strings.isNullOrEmpty(oidcInfo.getJwksInternalUri())
|
||||
? oidcInfo.getJwksPublicUri()
|
||||
: oidcInfo.getJwksInternalUri();
|
||||
|
||||
|
|
@ -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
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
|
|
@ -9,30 +9,32 @@
|
|||
* Contributors:
|
||||
* 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.Jwts;
|
||||
import io.jsonwebtoken.SigningKeyResolver;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
||||
|
||||
/** Provides instance of {@link JwtParser} */
|
||||
@Singleton
|
||||
public class KeycloakJwtParserProvider implements Provider<JwtParser> {
|
||||
|
||||
public class OIDCJwtParserProvider implements Provider<JwtParser> {
|
||||
private final JwtParser jwtParser;
|
||||
|
||||
@Inject
|
||||
public KeycloakJwtParserProvider(
|
||||
@Named(KeycloakConstants.ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
|
||||
KeycloakSigningKeyResolver keycloakSigningKeyResolver) {
|
||||
public OIDCJwtParserProvider(
|
||||
@Named(OIDC_ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
|
||||
SigningKeyResolver signingKeyResolver) {
|
||||
this.jwtParser =
|
||||
Jwts.parser()
|
||||
Jwts.parserBuilder()
|
||||
.setSigningKeyResolver(signingKeyResolver)
|
||||
.setAllowedClockSkewSeconds(allowedClockSkewSec)
|
||||
.setSigningKeyResolver(keycloakSigningKeyResolver);
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}};
|
||||
}
|
||||
}
|
||||
|
|
@ -32,5 +32,6 @@
|
|||
<module>machine-auth</module>
|
||||
<module>personal-account</module>
|
||||
<module>integration-tests</module>
|
||||
<module>oidc</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
|||
15
pom.xml
15
pom.xml
|
|
@ -271,6 +271,11 @@
|
|||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>${com.squareup.okhttp3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>${com.squareup.okhttp3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
|
|
@ -307,6 +312,11 @@
|
|||
<artifactId>commons-lang</artifactId>
|
||||
<version>${commons-lang.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>openshift-server-mock</artifactId>
|
||||
<version>${io.fabric8.kubernetes-client}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.mweirauch</groupId>
|
||||
<artifactId>micrometer-jvm-extras</artifactId>
|
||||
|
|
@ -1115,6 +1125,11 @@
|
|||
<artifactId>che-multiuser-machine-authentication-shared</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-oidc</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-devfile</artifactId>
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@
|
|||
<name>Che Core :: API :: InfraProxy</name>
|
||||
<description>Provides direct HTTP access to the underlying infrastructure web API.</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@
|
|||
*/
|
||||
package org.eclipse.che.api.infraproxy.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
|
|
@ -24,7 +22,6 @@ import jakarta.ws.rs.PUT;
|
|||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -52,39 +49,17 @@ public class InfrastructureApiService extends Service {
|
|||
|
||||
private final boolean allowed;
|
||||
private final RuntimeInfrastructure runtimeInfrastructure;
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@Context private MediaType mediaType;
|
||||
|
||||
private static boolean determineAllowed(
|
||||
String infra, String identityProvider, boolean allowedForKubernetes) {
|
||||
if ("openshift".equals(infra)) {
|
||||
return identityProvider != null && identityProvider.startsWith("openshift");
|
||||
}
|
||||
return allowedForKubernetes;
|
||||
private static boolean determineAllowed(String identityProvider) {
|
||||
return identityProvider != null;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public InfrastructureApiService(
|
||||
@Nullable @Named("che.infra.openshift.oauth_identity_provider") String identityProvider,
|
||||
@Named("che.infra.kubernetes.enable_unsupported_k8s") boolean allowedForKubernetes,
|
||||
RuntimeInfrastructure runtimeInfrastructure) {
|
||||
this(
|
||||
System.getenv("CHE_INFRASTRUCTURE_ACTIVE"),
|
||||
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);
|
||||
this.runtimeInfrastructure = runtimeInfrastructure;
|
||||
this.allowed = determineAllowed(identityProvider);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
|
|
|||
|
|
@ -37,49 +37,13 @@ public class InfrastructureApiServiceTest {
|
|||
|
||||
@BeforeMethod
|
||||
public void setup() throws Exception {
|
||||
apiService =
|
||||
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);
|
||||
apiService = new InfrastructureApiService("openshift-identityProvider", infra);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolvesCallWhenAllowedForKubernetesOnKubernetes() throws Exception {
|
||||
// given
|
||||
apiService =
|
||||
new InfrastructureApiService("kubernetes", true, "not-openshift-identityProvider", infra);
|
||||
apiService = new InfrastructureApiService("not-openshift-identityProvider", infra);
|
||||
when(infra.sendDirectInfrastructureRequest(any(), any(), any(), any()))
|
||||
.thenReturn(
|
||||
jakarta.ws.rs.core.Response.ok()
|
||||
|
|
@ -98,24 +62,6 @@ public class InfrastructureApiServiceTest {
|
|||
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
|
||||
public void testGet() throws Exception {
|
||||
// given
|
||||
|
|
|
|||
Loading…
Reference in New Issue