feat: Advanced authorization
Signed-off-by: Anatolii Bazko <abazko@redhat.com>pull/619/head
parent
b5e1e2a4a2
commit
c32efbb508
|
|
@ -663,3 +663,16 @@ che.oauth2.gitlab.clientid_filepath=NULL
|
|||
|
||||
# Location of the file with GitLab client secret.
|
||||
che.oauth2.gitlab.clientsecret_filepath=NULL
|
||||
|
||||
### Advanced authorization
|
||||
# Comma separated list of users allowed to access Che.
|
||||
che.infra.kubernetes.advanced_authorization.allowed_users=NULL
|
||||
|
||||
# Comma separated list of groups of users allowed to access Che.
|
||||
che.infra.kubernetes.advanced_authorization.allowed_groups=NULL
|
||||
|
||||
# Comma separated list of users disallowed to access Che.
|
||||
che.infra.kubernetes.advanced_authorization.disabled_users=NULL
|
||||
|
||||
# Comma separated list of groups of users disallowed to access Che.
|
||||
che.infra.kubernetes.advanced_authorization.disabled_groups=NULL
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2018 Red Hat, Inc.
|
||||
* Copyright (c) 2012-2023 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/
|
||||
|
|
@ -11,6 +11,13 @@
|
|||
*/
|
||||
package org.eclipse.che.commons.lang;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/** Set of useful String methods */
|
||||
public class StringUtils {
|
||||
|
||||
|
|
@ -91,4 +98,13 @@ public class StringUtils {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Parse string to set of strings. String should be comma separated. Whitespaces are trimmed. */
|
||||
public static Set<String> strToSet(String str) {
|
||||
if (!isNullOrEmpty(str)) {
|
||||
return Sets.newHashSet(Splitter.on(",").trimResults().omitEmptyStrings().split(str));
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ import org.eclipse.che.api.workspace.server.spi.provision.env.CheApiInternalEnvV
|
|||
import org.eclipse.che.api.workspace.server.wsplugins.ChePluginsApplier;
|
||||
import org.eclipse.che.api.workspace.shared.Constants;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.KubernetesNamespaceService;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.KubernetesAuthorizationCheckerImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa.JpaKubernetesRuntimeCacheModule;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier;
|
||||
|
|
@ -110,6 +113,9 @@ public class KubernetesInfraModule extends AbstractModule {
|
|||
namespaceConfigurators.addBinding().to(SshKeysConfigurator.class);
|
||||
namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class);
|
||||
|
||||
bind(AuthorizationChecker.class).to(KubernetesAuthorizationCheckerImpl.class);
|
||||
bind(PermissionsCleaner.class).asEagerSingleton();
|
||||
|
||||
bind(KubernetesNamespaceService.class);
|
||||
|
||||
MapBinder<String, InternalEnvironmentFactory> factories =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
|
||||
/** This {@link AuthorizationChecker} checks if user is allowed to use Che. */
|
||||
public interface AuthorizationChecker {
|
||||
|
||||
boolean isAuthorized(String username) throws InfrastructureException;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.RuntimeInfrastructure;
|
||||
|
||||
/**
|
||||
* An exception thrown by {@link RuntimeInfrastructure} and related components. Indicates that an
|
||||
* user is not authorized to use Che.
|
||||
*
|
||||
* @author Anatolii Bazko
|
||||
*/
|
||||
public class AuthorizationException extends InfrastructureException {
|
||||
public AuthorizationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import static org.eclipse.che.commons.lang.StringUtils.strToSet;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
|
||||
/** This {@link KubernetesAuthorizationCheckerImpl} checks if user is allowed to use Che. */
|
||||
@Singleton
|
||||
public class KubernetesAuthorizationCheckerImpl implements AuthorizationChecker {
|
||||
|
||||
private final Set<String> allowedUsers;
|
||||
private final Set<String> disabledUsers;
|
||||
|
||||
@Inject
|
||||
public KubernetesAuthorizationCheckerImpl(
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.allowed_users")
|
||||
String allowedUsers,
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.disabled_users")
|
||||
String disabledUsers) {
|
||||
this.allowedUsers = strToSet(allowedUsers);
|
||||
this.disabledUsers = strToSet(disabledUsers);
|
||||
}
|
||||
|
||||
public boolean isAuthorized(String username) {
|
||||
return isAllowedUser(username) && !isDisabledUser(username);
|
||||
}
|
||||
|
||||
private boolean isAllowedUser(String username) {
|
||||
return allowedUsers.isEmpty() || allowedUsers.contains(username);
|
||||
}
|
||||
|
||||
private boolean isDisabledUser(String username) {
|
||||
return !disabledUsers.isEmpty() && disabledUsers.contains(username);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import static org.eclipse.che.commons.lang.StringUtils.strToSet;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
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.commons.annotation.Nullable;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
|
||||
/** This {@link PermissionsCleaner} cleans up all user's permissions. */
|
||||
@Singleton
|
||||
public class PermissionsCleaner {
|
||||
|
||||
private final Set<String> userClusterRoles;
|
||||
private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory;
|
||||
|
||||
@Inject
|
||||
public PermissionsCleaner(
|
||||
@Nullable @Named("che.infra.kubernetes.user_cluster_roles") String userClusterRoles,
|
||||
CheServerKubernetesClientFactory cheServerKubernetesClientFactory) {
|
||||
this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory;
|
||||
this.userClusterRoles = strToSet(userClusterRoles);
|
||||
}
|
||||
|
||||
public void cleanUp(String namespaceName) throws InfrastructureException {
|
||||
KubernetesClient client = cheServerKubernetesClientFactory.create();
|
||||
for (String userClusterRole : userClusterRoles) {
|
||||
client.rbac().roleBindings().inNamespace(namespaceName).withName(userClusterRole).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,9 @@ import org.eclipse.che.inject.ConfigurationException;
|
|||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
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.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationException;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -98,6 +101,8 @@ public class KubernetesNamespaceFactory {
|
|||
private final PreferenceManager preferenceManager;
|
||||
protected final Set<NamespaceConfigurator> namespaceConfigurators;
|
||||
protected final KubernetesSharedPool sharedPool;
|
||||
protected final AuthorizationChecker authorizationChecker;
|
||||
protected final PermissionsCleaner permissionsCleaner;
|
||||
|
||||
@Inject
|
||||
public KubernetesNamespaceFactory(
|
||||
|
|
@ -110,7 +115,9 @@ public class KubernetesNamespaceFactory {
|
|||
Set<NamespaceConfigurator> namespaceConfigurators,
|
||||
CheServerKubernetesClientFactory cheServerKubernetesClientFactory,
|
||||
PreferenceManager preferenceManager,
|
||||
KubernetesSharedPool sharedPool)
|
||||
KubernetesSharedPool sharedPool,
|
||||
AuthorizationChecker authorizationChecker,
|
||||
PermissionsCleaner permissionsCleaner)
|
||||
throws ConfigurationException {
|
||||
this.namespaceCreationAllowed = namespaceCreationAllowed;
|
||||
this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory;
|
||||
|
|
@ -120,6 +127,8 @@ public class KubernetesNamespaceFactory {
|
|||
this.labelNamespaces = labelNamespaces;
|
||||
this.annotateNamespaces = annotateNamespaces;
|
||||
this.namespaceConfigurators = ImmutableSet.copyOf(namespaceConfigurators);
|
||||
this.authorizationChecker = authorizationChecker;
|
||||
this.permissionsCleaner = permissionsCleaner;
|
||||
|
||||
//noinspection UnstableApiUsage
|
||||
Splitter.MapSplitter csvMapSplitter = Splitter.on(",").withKeyValueSeparator("=");
|
||||
|
|
@ -281,6 +290,9 @@ public class KubernetesNamespaceFactory {
|
|||
|
||||
var subject = EnvironmentContext.getCurrent().getSubject();
|
||||
var userName = subject.getUserName();
|
||||
|
||||
validateAuthorization(namespace.getName(), userName);
|
||||
|
||||
NamespaceResolutionContext resolutionCtx =
|
||||
new NamespaceResolutionContext(identity.getWorkspaceId(), subject.getUserId(), userName);
|
||||
Map<String, String> namespaceAnnotationsEvaluated =
|
||||
|
|
@ -573,6 +585,27 @@ public class KubernetesNamespaceFactory {
|
|||
}
|
||||
}
|
||||
|
||||
protected void validateAuthorization(String namespaceName, String userName)
|
||||
throws InfrastructureException {
|
||||
if (!authorizationChecker.isAuthorized(userName)) {
|
||||
try {
|
||||
permissionsCleaner.cleanUp(namespaceName);
|
||||
} catch (InfrastructureException | KubernetesClientException e) {
|
||||
LOG.error(
|
||||
"Failed to clean up permissions for user '{}' in namespace '{}'. Cause: {}",
|
||||
userName,
|
||||
namespaceName,
|
||||
e.getMessage(),
|
||||
e);
|
||||
}
|
||||
|
||||
throw new AuthorizationException(
|
||||
format(
|
||||
"User '%s' is not authorized to create a project. Please contact your system administrator.",
|
||||
userName));
|
||||
}
|
||||
}
|
||||
|
||||
protected String evalPlaceholders(String namespace, NamespaceResolutionContext ctx) {
|
||||
checkArgument(!isNullOrEmpty(namespace));
|
||||
String evaluated = namespace;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class KubernetesAuthorizationCheckerTest {
|
||||
@Test(dataProvider = "advancedAuthorizationData")
|
||||
public void advancedAuthorization(
|
||||
String testUserName, String allowedUsers, String disabledUsers, boolean expectedIsAuthorized)
|
||||
throws InfrastructureException {
|
||||
// give
|
||||
AuthorizationChecker authorizationChecker =
|
||||
new KubernetesAuthorizationCheckerImpl(allowedUsers, disabledUsers);
|
||||
|
||||
// when
|
||||
boolean isAuthorized = authorizationChecker.isAuthorized(testUserName);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(isAuthorized, expectedIsAuthorized);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] advancedAuthorizationData() {
|
||||
return new Object[][] {
|
||||
{"user1", "", "", true},
|
||||
{"user1", "user1", "", true},
|
||||
{"user1", "user1", "user2", true},
|
||||
{"user1", "user1", "user1", false},
|
||||
{"user2", "user1", "", false},
|
||||
{"user2", "user1", "user2", false},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,8 @@ import org.eclipse.che.inject.ConfigurationException;
|
|||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
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.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
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;
|
||||
|
|
@ -126,6 +128,8 @@ public class KubernetesNamespaceFactoryTest {
|
|||
private KubernetesClient k8sClient;
|
||||
@Mock private PreferenceManager preferenceManager;
|
||||
@Mock Appender mockedAppender;
|
||||
@Mock AuthorizationChecker authorizationChecker;
|
||||
@Mock PermissionsCleaner permissionsCleaner;
|
||||
|
||||
@Mock private NonNamespaceOperation namespaceOperation;
|
||||
|
||||
|
|
@ -151,6 +155,7 @@ public class KubernetesNamespaceFactoryTest {
|
|||
|
||||
lenient().when(namespaceOperation.withName(any())).thenReturn(namespaceResource);
|
||||
lenient().when(namespaceResource.get()).thenReturn(mock(Namespace.class));
|
||||
lenient().when(authorizationChecker.isAuthorized(anyString())).thenReturn(true);
|
||||
|
||||
lenient().doReturn(namespaceListResource).when(namespaceOperation).withLabels(anyMap());
|
||||
lenient().when(namespaceListResource.list()).thenReturn(namespaceList);
|
||||
|
|
@ -179,7 +184,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
namespaceFactory.checkIfNamespaceIsAllowed("jondoe-che");
|
||||
}
|
||||
|
|
@ -204,7 +211,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
namespaceFactory.checkIfNamespaceIsAllowed("any-namespace");
|
||||
}
|
||||
|
|
@ -222,7 +231,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
assertEquals("che-kube-admin", namespaceFactory.normalizeNamespaceName("kube:admin"));
|
||||
}
|
||||
|
|
@ -245,7 +256,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
namespaceFactory.checkIfNamespaceIsAllowed("any-namespace");
|
||||
}
|
||||
|
|
@ -265,7 +278,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -313,7 +328,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
|
||||
// when
|
||||
|
|
@ -354,7 +371,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
|
||||
// when
|
||||
|
|
@ -382,7 +401,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
// when
|
||||
namespaceFactory.list();
|
||||
|
|
@ -413,7 +434,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
List<KubernetesNamespaceMeta> availableNamespaces = namespaceFactory.list();
|
||||
assertEquals(availableNamespaces.size(), 1);
|
||||
|
|
@ -439,7 +462,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
List<KubernetesNamespaceMeta> availableNamespaces = namespaceFactory.list();
|
||||
assertEquals(availableNamespaces.size(), 1);
|
||||
|
|
@ -467,7 +492,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
Set.of(new PreferencesConfigMapConfigurator(cheServerKubernetesClientFactory)),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
when(toReturnNamespace.getName()).thenReturn("namespaceName");
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
|
@ -508,7 +535,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
namespaceConfigurators,
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
|
|
@ -543,7 +572,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
|
@ -575,7 +606,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
|
@ -608,7 +641,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
throwOnTryToGetNamespaceByName(
|
||||
"jondoe-che", new KubernetesClientException("connection refused"));
|
||||
|
||||
|
|
@ -631,7 +666,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
throwOnTryToGetNamespacesList(new KubernetesClientException("connection refused"));
|
||||
|
||||
namespaceFactory.list();
|
||||
|
|
@ -659,7 +696,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
|
@ -688,7 +727,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
|
||||
|
|
@ -723,7 +764,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
Set.of(serviceAccountCfg),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
|
|
@ -761,7 +804,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
Set.of(serviceAccountConfigurator),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
|
|
@ -836,7 +881,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
Set.of(serviceAccountConfigurator),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
|
|
@ -881,7 +928,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
"serviceAccount", "", cheServerKubernetesClientFactory)),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("workspace123");
|
||||
|
|
@ -945,7 +994,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
WorkspaceImpl workspace =
|
||||
new WorkspaceImplBuilder().setId("workspace123").setAttributes(emptyMap()).build();
|
||||
|
|
@ -968,7 +1019,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
Map<String, String> prefs = new HashMap<>();
|
||||
prefs.put(WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE, "che-123");
|
||||
|
|
@ -996,7 +1049,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
Map<String, String> prefs = new HashMap<>();
|
||||
// returned but ignored
|
||||
|
|
@ -1025,7 +1080,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
Map<String, String> prefs = new HashMap<>();
|
||||
// returned but ignored
|
||||
|
|
@ -1054,7 +1111,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
doReturn(empty()).when(namespaceFactory).fetchNamespace(anyString());
|
||||
|
||||
String namespace =
|
||||
|
|
@ -1079,7 +1138,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
WorkspaceImpl workspace =
|
||||
new WorkspaceImplBuilder()
|
||||
|
|
@ -1106,7 +1167,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
WorkspaceImpl workspace =
|
||||
new WorkspaceImplBuilder()
|
||||
|
|
@ -1154,7 +1217,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
String namespace =
|
||||
namespaceFactory.evaluateNamespaceName(
|
||||
|
|
@ -1178,7 +1243,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-che");
|
||||
|
|
@ -1215,7 +1282,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
|
||||
|
|
@ -1251,7 +1320,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
|
||||
|
|
@ -1299,7 +1370,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
namespaceFactory.list();
|
||||
|
||||
|
|
@ -1322,7 +1395,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool));
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner));
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
|
||||
prepareNamespace(toReturnNamespace);
|
||||
|
|
@ -1351,7 +1426,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
assertEquals(expected, namespaceFactory.normalizeNamespaceName(raw));
|
||||
}
|
||||
|
||||
|
|
@ -1368,7 +1445,9 @@ public class KubernetesNamespaceFactoryTest {
|
|||
emptySet(),
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
pool);
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
|
||||
assertEquals(
|
||||
63,
|
||||
|
|
|
|||
|
|
@ -161,6 +161,17 @@
|
|||
<artifactId>mockwebserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-server-mock</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>openshift-server-mock</artifactId>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientTermi
|
|||
import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesEnvironmentProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.StartSynchronizerFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.server.KubernetesNamespaceService;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.cache.jpa.JpaKubernetesRuntimeCacheModule;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.DockerimageComponentToWorkspaceApplier;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.devfile.KubernetesComponentToWorkspaceApplier;
|
||||
|
|
@ -80,6 +82,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.PluginBroke
|
|||
import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.SidecarToolingProvisioner;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.brokerphases.BrokerEnvironmentFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.wsplugins.events.BrokerService;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.authorization.OpenShiftAuthorizationCheckerImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.devfile.OpenshiftComponentToWorkspaceApplier;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment;
|
||||
import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory;
|
||||
|
|
@ -118,6 +121,9 @@ public class OpenShiftInfraModule extends AbstractModule {
|
|||
namespaceConfigurators.addBinding().to(SshKeysConfigurator.class);
|
||||
namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class);
|
||||
|
||||
bind(AuthorizationChecker.class).to(OpenShiftAuthorizationCheckerImpl.class);
|
||||
bind(PermissionsCleaner.class).asEagerSingleton();
|
||||
|
||||
bind(KubernetesNamespaceService.class);
|
||||
|
||||
MapBinder<String, InternalEnvironmentFactory> factories =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import static org.eclipse.che.commons.lang.StringUtils.strToSet;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.openshift.api.model.Group;
|
||||
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.commons.annotation.Nullable;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker;
|
||||
|
||||
/** This {@link OpenShiftAuthorizationCheckerImpl} checks if user is allowed to use Che. */
|
||||
@Singleton
|
||||
public class OpenShiftAuthorizationCheckerImpl implements AuthorizationChecker {
|
||||
|
||||
private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory;
|
||||
|
||||
private final Set<String> allowedUsers;
|
||||
private final Set<String> allowedGroups;
|
||||
private final Set<String> disabledUsers;
|
||||
private final Set<String> disabledGroups;
|
||||
|
||||
@Inject
|
||||
public OpenShiftAuthorizationCheckerImpl(
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.allowed_users")
|
||||
String allowedUsers,
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.allowed_groups")
|
||||
String allowedGroups,
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.disabled_users")
|
||||
String disabledUsers,
|
||||
@Nullable @Named("che.infra.kubernetes.advanced_authorization.disabled_groups")
|
||||
String disabledGroups,
|
||||
CheServerKubernetesClientFactory cheServerKubernetesClientFactory) {
|
||||
this.allowedUsers = strToSet(allowedUsers);
|
||||
this.allowedGroups = strToSet(allowedGroups);
|
||||
this.disabledUsers = strToSet(disabledUsers);
|
||||
this.disabledGroups = strToSet(disabledGroups);
|
||||
this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory;
|
||||
}
|
||||
|
||||
public boolean isAuthorized(String username) throws InfrastructureException {
|
||||
return isAllowedUser(cheServerKubernetesClientFactory.create(), username)
|
||||
&& !isDisabledUser(cheServerKubernetesClientFactory.create(), username);
|
||||
}
|
||||
|
||||
private boolean isAllowedUser(KubernetesClient client, String username) {
|
||||
// All users from all groups are allowed by default
|
||||
if (allowedUsers.isEmpty() && allowedGroups.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowedUsers.contains(username)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String groupName : allowedGroups) {
|
||||
Group group = client.resources(Group.class).withName(groupName).get();
|
||||
if (group != null && group.getUsers().contains(username)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDisabledUser(KubernetesClient client, String username) {
|
||||
// All users from all groups are allowed by default
|
||||
if (disabledUsers.isEmpty() && disabledGroups.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disabledUsers.contains(username)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String groupName : disabledGroups) {
|
||||
Group group = client.resources(Group.class).withName(groupName).get();
|
||||
if (group != null && group.getUsers().contains(username)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,8 @@ import org.eclipse.che.commons.env.EnvironmentContext;
|
|||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
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.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
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;
|
||||
|
|
@ -81,6 +83,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
CheServerOpenshiftClientFactory cheServerOpenshiftClientFactory,
|
||||
PreferenceManager preferenceManager,
|
||||
KubernetesSharedPool sharedPool,
|
||||
AuthorizationChecker authorizationChecker,
|
||||
PermissionsCleaner permissionsCleaner,
|
||||
@Nullable @Named("che.infra.openshift.oauth_identity_provider")
|
||||
String oAuthIdentityProvider) {
|
||||
super(
|
||||
|
|
@ -93,7 +97,9 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
namespaceConfigurators,
|
||||
cheServerKubernetesClientFactory,
|
||||
preferenceManager,
|
||||
sharedPool);
|
||||
sharedPool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner);
|
||||
this.initWithCheServerSa = initWithCheServerSa;
|
||||
this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory;
|
||||
this.cheServerOpenshiftClientFactory = cheServerOpenshiftClientFactory;
|
||||
|
|
@ -105,6 +111,9 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
|
|||
OpenShiftProject osProject = get(identity);
|
||||
var subject = EnvironmentContext.getCurrent().getSubject();
|
||||
var userName = subject.getUserName();
|
||||
|
||||
validateAuthorization(osProject.getName(), userName);
|
||||
|
||||
NamespaceResolutionContext resolutionCtx =
|
||||
new NamespaceResolutionContext(identity.getWorkspaceId(), subject.getUserId(), userName);
|
||||
Map<String, String> namespaceAnnotationsEvaluated =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2023 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.authorization;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
|
||||
import io.fabric8.openshift.api.model.Group;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
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.DataProvider;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class OpenShiftAuthorizationCheckerTest {
|
||||
|
||||
@Mock private CheServerKubernetesClientFactory clientFactory;
|
||||
private KubernetesClient client;
|
||||
private KubernetesServer serverMock;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() throws InfrastructureException {
|
||||
serverMock = new KubernetesServer(true, true);
|
||||
serverMock.before();
|
||||
client = spy(serverMock.getClient());
|
||||
lenient().when(clientFactory.create()).thenReturn(client);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "advancedAuthorizationData")
|
||||
public void advancedAuthorization(
|
||||
String testUserName,
|
||||
List<Group> groups,
|
||||
String allowedUsers,
|
||||
String allowedGroups,
|
||||
String disabledUsers,
|
||||
String disabledGroups,
|
||||
boolean expectedIsAuthorized)
|
||||
throws InfrastructureException {
|
||||
// give
|
||||
OpenShiftAuthorizationCheckerImpl authorizationChecker =
|
||||
new OpenShiftAuthorizationCheckerImpl(
|
||||
allowedUsers, allowedGroups, disabledUsers, disabledGroups, clientFactory);
|
||||
groups.forEach(group -> client.resources(Group.class).create(group));
|
||||
|
||||
// when
|
||||
boolean isAuthorized = authorizationChecker.isAuthorized(testUserName);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(isAuthorized, expectedIsAuthorized);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] advancedAuthorizationData() {
|
||||
Group groupWithUser1 =
|
||||
new Group(
|
||||
"v1",
|
||||
"Group",
|
||||
new ObjectMetaBuilder().withName("groupWithUser1").build(),
|
||||
List.of("user1"));
|
||||
Group groupWithUser2 =
|
||||
new Group(
|
||||
"v1",
|
||||
"Group",
|
||||
new ObjectMetaBuilder().withName("groupWithUser2").build(),
|
||||
List.of("user2"));
|
||||
|
||||
return new Object[][] {
|
||||
{"user1", Collections.emptyList(), "", "", "", "", true},
|
||||
{"user1", Collections.emptyList(), "user1", "", "", "", true},
|
||||
{"user1", Collections.emptyList(), "user1", "", "user2", "", true},
|
||||
{"user1", List.of(groupWithUser2), "user1", "", "", "groupWithUser2", true},
|
||||
{"user1", List.of(groupWithUser1), "", "groupWithUser1", "", "", true},
|
||||
{"user2", List.of(groupWithUser1), "user2", "groupWithUser1", "", "", true},
|
||||
{
|
||||
"user1",
|
||||
List.of(groupWithUser1, groupWithUser2),
|
||||
"",
|
||||
"groupWithUser1",
|
||||
"",
|
||||
"groupWithUser2",
|
||||
true
|
||||
},
|
||||
{"user1", Collections.emptyList(), "user1", "", "user1", "", false},
|
||||
{"user2", Collections.emptyList(), "user1", "", "", "", false},
|
||||
{"user2", Collections.emptyList(), "user1", "", "user2", "", false},
|
||||
{"user2", List.of(groupWithUser1), "", "groupWithUser1", "", "", false},
|
||||
{"user1", Collections.emptyList(), "", "", "user1", "", false},
|
||||
{"user1", List.of(groupWithUser1), "", "", "", "groupWithUser1", false},
|
||||
{"user1", List.of(groupWithUser1), "", "groupWithUser1", "", "groupWithUser1", false},
|
||||
{
|
||||
"user2",
|
||||
List.of(groupWithUser1, groupWithUser2),
|
||||
"",
|
||||
"groupWithUser1",
|
||||
"",
|
||||
"groupWithUser2",
|
||||
false
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,8 @@ import org.eclipse.che.commons.subject.SubjectImpl;
|
|||
import org.eclipse.che.inject.ConfigurationException;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.AuthorizationChecker;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.authorization.PermissionsCleaner;
|
||||
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.NamespaceConfigurator;
|
||||
|
|
@ -113,6 +115,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
@Mock private WorkspaceManager workspaceManager;
|
||||
@Mock private PreferenceManager preferenceManager;
|
||||
@Mock private KubernetesSharedPool pool;
|
||||
@Mock private AuthorizationChecker authorizationChecker;
|
||||
@Mock private PermissionsCleaner permissionsCleaner;
|
||||
|
||||
@Mock private ProjectOperation projectOperation;
|
||||
|
||||
|
|
@ -134,6 +138,7 @@ public class OpenShiftProjectFactoryTest {
|
|||
lenient().when(cheServerOpenshiftClientFactory.createOC()).thenReturn(osClient);
|
||||
lenient().when(cheServerKubernetesClientFactory.create()).thenReturn(osClient);
|
||||
lenient().when(osClient.projects()).thenReturn(projectOperation);
|
||||
lenient().when(authorizationChecker.isAuthorized(anyString())).thenReturn(true);
|
||||
|
||||
lenient()
|
||||
.when(workspaceManager.getWorkspace(any()))
|
||||
|
|
@ -174,6 +179,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
projectFactory.checkIfNamespaceIsAllowed(USER_NAME + "-che");
|
||||
|
|
@ -204,6 +211,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
try {
|
||||
projectFactory.checkIfNamespaceIsAllowed("any-namespace");
|
||||
|
|
@ -234,6 +243,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +280,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
|
||||
|
|
@ -305,6 +318,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "u123", null, false));
|
||||
|
||||
|
|
@ -337,6 +352,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
// when
|
||||
|
|
@ -379,6 +396,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
List<KubernetesNamespaceMeta> availableNamespaces = projectFactory.list();
|
||||
|
|
@ -415,6 +434,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
List<KubernetesNamespaceMeta> availableNamespaces = projectFactory.list();
|
||||
|
|
@ -451,6 +472,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
projectFactory.list();
|
||||
|
|
@ -477,6 +500,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
projectFactory.list();
|
||||
|
|
@ -508,6 +533,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
prepareProject(toReturnProject);
|
||||
|
|
@ -542,6 +569,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
|
||||
|
|
@ -584,6 +613,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
prepareProject(toReturnProject);
|
||||
|
|
@ -628,6 +659,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
OAUTH_IDENTITY_PROVIDER));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
when(toReturnProject.getName()).thenReturn("workspace123");
|
||||
|
|
@ -677,6 +710,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
|
||||
String namespace =
|
||||
|
|
@ -709,6 +744,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER);
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
projectFactory.list();
|
||||
|
|
@ -735,6 +772,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
|
||||
|
|
@ -775,6 +814,8 @@ public class OpenShiftProjectFactoryTest {
|
|||
cheServerOpenshiftClientFactory,
|
||||
preferenceManager,
|
||||
pool,
|
||||
authorizationChecker,
|
||||
permissionsCleaner,
|
||||
NO_OAUTH_IDENTITY_PROVIDER));
|
||||
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue