chore: Remote workspace-stop roles (#659)

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/661/head
Anatolii Bazko 2024-03-05 12:38:09 +01:00 committed by GitHub
parent 60262e3a72
commit 7dc7a61511
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1 additions and 350 deletions

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2012-2023 Red Hat, Inc.
# Copyright (c) 2012-2024 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/
@ -91,10 +91,6 @@ che.workspace.server.liveness_probes=wsagent/http,exec-agent/http,terminal,theia
# The default is: 10MB=10485760.
che.workspace.startup_debug_log_limit_bytes=10485760
# If set to `true`, 'stop-workspace' role with the edit privileges is granted to the 'che' ServiceAccount if OpenShift OAuth is enabled.
# This configuration is mainly required for workspace idling when the OpenShift OAuth is enabled.
che.workspace.stop.role.enabled=true
# Specifies whether {prod-short} is deployed with DevWorkspaces enabled.
# This property is set by the {prod-short} Operator if it also installed the support for DevWorkspaces.
# This property is used to advertise this fact to the {prod-short} dashboard.

View File

@ -50,10 +50,6 @@
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-networking</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-rbac</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-client</artifactId>

View File

@ -87,7 +87,6 @@ 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;
@ -116,7 +115,6 @@ public class OpenShiftInfraModule extends AbstractModule {
namespaceConfigurators.addBinding().to(OAuthTokenSecretsConfigurator.class);
namespaceConfigurators.addBinding().to(PreferencesConfigMapConfigurator.class);
namespaceConfigurators.addBinding().to(OpenShiftWorkspaceServiceAccountConfigurator.class);
namespaceConfigurators.addBinding().to(OpenShiftStopWorkspaceRoleConfigurator.class);
namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class);
bind(AuthorizationChecker.class).to(OpenShiftAuthorizationCheckerImpl.class);

View File

@ -1,140 +0,0 @@
/*
* 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.project.configurator;
import static com.google.common.base.Strings.isNullOrEmpty;
import io.fabric8.kubernetes.api.model.rbac.*;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
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.environment.CheInstallationLocation;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class creates the necessary role and rolebindings to allow the che serviceaccount to stop
* user workspaces.
*
* @author Tom George
*/
@Singleton
public class OpenShiftStopWorkspaceRoleConfigurator implements NamespaceConfigurator {
private final CheServerKubernetesClientFactory cheClientFactory;
private final String installationLocation;
private final boolean stopWorkspaceRoleEnabled;
private final String oAuthIdentityProvider;
private static final Logger LOG =
LoggerFactory.getLogger(OpenShiftStopWorkspaceRoleConfigurator.class);
@Inject
public OpenShiftStopWorkspaceRoleConfigurator(
CheServerKubernetesClientFactory cheClientFactory,
CheInstallationLocation installationLocation,
@Named("che.workspace.stop.role.enabled") boolean stopWorkspaceRoleEnabled,
@Nullable @Named("che.infra.openshift.oauth_identity_provider") String oAuthIdentityProvider)
throws InfrastructureException {
this.cheClientFactory = cheClientFactory;
this.installationLocation = installationLocation.getInstallationLocationNamespace();
this.stopWorkspaceRoleEnabled = stopWorkspaceRoleEnabled;
this.oAuthIdentityProvider = oAuthIdentityProvider;
}
@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String projectName)
throws InfrastructureException {
if (isNullOrEmpty(oAuthIdentityProvider)) {
return;
}
try {
if (stopWorkspaceRoleEnabled && installationLocation != null) {
KubernetesClient client = cheClientFactory.create();
String stopWorkspacesRoleName = "workspace-stop";
client
.rbac()
.roles()
.inNamespace(projectName)
.createOrReplace(createStopWorkspacesRole(stopWorkspacesRoleName));
client
.rbac()
.roleBindings()
.inNamespace(projectName)
.createOrReplace(createStopWorkspacesRoleBinding(stopWorkspacesRoleName));
}
} catch (KubernetesClientException e) {
LOG.warn(
"Stop workspace Role and RoleBinding will not be provisioned to the '{}' namespace. 'che.workspace.stop.role.enabled' property is set to '{}', {}",
installationLocation,
stopWorkspaceRoleEnabled,
e.getMessage());
}
}
protected Role createStopWorkspacesRole(String name) {
return new RoleBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withRules(
new PolicyRuleBuilder()
.withApiGroups("")
.withResources("pods")
.withVerbs("get", "list", "watch", "delete")
.build(),
new PolicyRuleBuilder()
.withApiGroups("")
.withResources("configmaps", "services", "secrets")
.withVerbs("delete", "list", "get")
.build(),
new PolicyRuleBuilder()
.withApiGroups("route.openshift.io")
.withResources("routes")
.withVerbs("delete", "list")
.build(),
new PolicyRuleBuilder()
.withApiGroups("apps")
.withResources("deployments", "replicasets")
.withVerbs("delete", "list", "get", "patch")
.build())
.build();
}
protected RoleBinding createStopWorkspacesRoleBinding(String name) {
return new RoleBindingBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withNewRoleRef()
.withKind("Role")
.withName(name)
.endRoleRef()
.withSubjects(
new SubjectBuilder()
.withKind("ServiceAccount")
.withName("che")
.withNamespace(installationLocation)
.build())
.build();
}
}

View File

@ -1,199 +0,0 @@
/*
* 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.project.configurator;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
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.rbac.*;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.RbacAPIGroupDSL;
import io.fabric8.kubernetes.client.dsl.Resource;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.CheInstallationLocation;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Test for {@link
* org.eclipse.che.workspace.infrastructure.openshift.project.configurator.OpenShiftStopWorkspaceRoleConfigurator}
*
* <p>#author Tom George
*/
@Listeners(MockitoTestNGListener.class)
public class OpenShiftStopWorkspaceRoleConfiguratorTest {
@Mock private CheInstallationLocation cheInstallationLocation;
private OpenShiftStopWorkspaceRoleConfigurator stopWorkspaceRoleProvisioner;
@Mock private CheServerKubernetesClientFactory cheClientFactory;
@Mock private KubernetesClient client;
@Mock private MixedOperation<Role, RoleList, Resource<Role>> mixedRoleOperation;
@Mock
private MixedOperation<RoleBinding, RoleBindingList, Resource<RoleBinding>>
mixedRoleBindingOperation;
@Mock private NonNamespaceOperation<Role, RoleList, Resource<Role>> nonNamespaceRoleOperation;
@Mock
private NonNamespaceOperation<RoleBinding, RoleBindingList, Resource<RoleBinding>>
nonNamespaceRoleBindingOperation;
@Mock private Resource<Role> roleResource;
@Mock private Resource<RoleBinding> roleBindingResource;
@Mock private Role mockRole;
@Mock private RoleBinding mockRoleBinding;
@Mock private RbacAPIGroupDSL rbacAPIGroupDSL;
private final Role expectedRole =
new RoleBuilder()
.withNewMetadata()
.withName("workspace-stop")
.endMetadata()
.withRules(
new PolicyRuleBuilder()
.withApiGroups("")
.withResources("pods")
.withVerbs("get", "list", "watch", "delete")
.build(),
new PolicyRuleBuilder()
.withApiGroups("")
.withResources("configmaps", "services", "secrets")
.withVerbs("delete", "list", "get")
.build(),
new PolicyRuleBuilder()
.withApiGroups("route.openshift.io")
.withResources("routes")
.withVerbs("delete", "list")
.build(),
new PolicyRuleBuilder()
.withApiGroups("apps")
.withResources("deployments", "replicasets")
.withVerbs("delete", "list", "get", "patch")
.build())
.build();
private final RoleBinding expectedRoleBinding =
new RoleBindingBuilder()
.withNewMetadata()
.withName("workspace-stop")
.endMetadata()
.withNewRoleRef()
.withKind("Role")
.withName("workspace-stop")
.endRoleRef()
.withSubjects(
new SubjectBuilder()
.withKind("ServiceAccount")
.withName("che")
.withNamespace("che")
.build())
.build();
@BeforeMethod
public void setUp() throws Exception {
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che");
stopWorkspaceRoleProvisioner =
new OpenShiftStopWorkspaceRoleConfigurator(
cheClientFactory, cheInstallationLocation, true, "yes");
lenient().when(cheClientFactory.create()).thenReturn(client);
lenient().when(client.rbac()).thenReturn(rbacAPIGroupDSL);
lenient().when(rbacAPIGroupDSL.roles()).thenReturn(mixedRoleOperation);
lenient().when(rbacAPIGroupDSL.roleBindings()).thenReturn(mixedRoleBindingOperation);
lenient()
.when(mixedRoleOperation.inNamespace(anyString()))
.thenReturn(nonNamespaceRoleOperation);
lenient()
.when(mixedRoleBindingOperation.inNamespace(anyString()))
.thenReturn(nonNamespaceRoleBindingOperation);
lenient().when(nonNamespaceRoleOperation.withName(anyString())).thenReturn(roleResource);
lenient()
.when(nonNamespaceRoleBindingOperation.withName(anyString()))
.thenReturn(roleBindingResource);
lenient().when(roleResource.get()).thenReturn(null);
lenient().when(nonNamespaceRoleOperation.createOrReplace(any())).thenReturn(mockRole);
lenient()
.when(nonNamespaceRoleBindingOperation.createOrReplace(any()))
.thenReturn(mockRoleBinding);
}
@Test
public void shouldCreateRole() {
assertEquals(
stopWorkspaceRoleProvisioner.createStopWorkspacesRole("workspace-stop"), expectedRole);
}
@Test
public void shouldCreateRoleBinding() throws InfrastructureException {
assertEquals(
stopWorkspaceRoleProvisioner.createStopWorkspacesRoleBinding("workspace-stop"),
expectedRoleBinding);
}
@Test
public void shouldCreateRoleAndRoleBindingWhenRoleDoesNotYetExist()
throws InfrastructureException {
stopWorkspaceRoleProvisioner.configure(null, "developer-che");
verify(client.rbac().roles().inNamespace("developer-che")).createOrReplace(expectedRole);
verify(client.rbac().roleBindings().inNamespace("developer-che"))
.createOrReplace(expectedRoleBinding);
}
@Test
public void shouldNotCreateRoleBindingWhenStopWorkspaceRolePropertyIsDisabled()
throws InfrastructureException {
OpenShiftStopWorkspaceRoleConfigurator disabledStopWorkspaceRoleProvisioner =
new OpenShiftStopWorkspaceRoleConfigurator(
cheClientFactory, cheInstallationLocation, false, "yes");
disabledStopWorkspaceRoleProvisioner.configure(null, "developer-che");
verify(client, never()).rbac();
}
@Test
public void shouldNotCreateRoleBindingWhenInstallationLocationIsNull()
throws InfrastructureException {
lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn(null);
OpenShiftStopWorkspaceRoleConfigurator
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation =
new OpenShiftStopWorkspaceRoleConfigurator(
cheClientFactory, cheInstallationLocation, true, "yes");
stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.configure(null, "developer-che");
verify(client, never()).rbac();
}
@Test
public void shouldNotCallStopWorkspaceRoleProvisionWhenIdentityProviderIsDefined()
throws Exception {
when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("something");
OpenShiftStopWorkspaceRoleConfigurator configurator =
new OpenShiftStopWorkspaceRoleConfigurator(
cheClientFactory, cheInstallationLocation, true, null);
configurator.configure(null, "something");
verify(cheClientFactory, times(0)).create();
}
}