From 60e17b309ad61fbd74fad1e0c85fa07a9d238489 Mon Sep 17 00:00:00 2001 From: Tom George Date: Wed, 1 Apr 2020 17:37:43 -0500 Subject: [PATCH 1/4] Add a workspace-stop role and rolebinding to allow che service account to stop a workspace in another namespace Signed-off-by: Tom George --- .../OpenShiftWorkspaceServiceAccount.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java index 92bed0c2f8a..90cdc3f9057 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java @@ -69,6 +69,15 @@ class OpenShiftWorkspaceServiceAccount { void prepare() throws InfrastructureException { OpenShiftClient osClient = clientFactory.createOC(workspaceId); + String stopWorkspacesRoleName = "workspace-stop"; + if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() == null) { + createStopWorkspacesRole(osClient, stopWorkspacesRoleName); + } + osClient + .roleBindings() + .inNamespace(projectName) + .createOrReplace(createStopWorkspacesRoleBinding()); + if (osClient.serviceAccounts().inNamespace(projectName).withName(serviceAccountName).get() == null) { createWorkspaceServiceAccount(osClient); @@ -142,6 +151,37 @@ private void createViewRole(OpenShiftClient osClient, String name) { osClient.roles().inNamespace(projectName).create(viewRole); } + private void createStopWorkspacesRole(OpenShiftClient osClient, String name) { + OpenshiftRole stopRole = + new OpenshiftRoleBuilder() + .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(); + osClient.roles().inNamespace(projectName).create(stopRole); + } + private OpenshiftRoleBinding createViewRoleBinding() { return new OpenshiftRoleBindingBuilder() .withNewMetadata() @@ -178,6 +218,25 @@ private OpenshiftRoleBinding createExecRoleBinding() { .build(); } + private OpenshiftRoleBinding createStopWorkspacesRoleBinding() { + return new OpenshiftRoleBindingBuilder() + .withNewMetadata() + .withName(serviceAccountName + "-stop") + .withNamespace(projectName) + .endMetadata() + .withNewRoleRef() + .withName("workspace-stop") + .withNamespace(projectName) + .endRoleRef() + .withSubjects( + new ObjectReferenceBuilder() + .withKind("ServiceAccount") + .withName("che") + .withNamespace("che") + .build()) + .build(); + } + private OpenshiftRoleBinding createCustomRoleBinding(String clusterRoleName) { return new OpenshiftRoleBindingBuilder() .withNewMetadata() From 3fa97573914bcdbebcc432536fc642d6c97d24d3 Mon Sep 17 00:00:00 2001 From: Tom George Date: Thu, 9 Apr 2020 14:58:30 -0500 Subject: [PATCH 2/4] Add OpenShiftCheInstallationLocation to get ns of Che installation, and OpenShiftStopWorkspaceRoleProvisioner to provision necessary permissions for stopping workspaces Signed-off-by: Tom George --- infrastructures/openshift/pom.xml | 4 + .../OpenShiftCheInstallationLocation.java | 50 +++++ .../project/OpenShiftProjectFactory.java | 6 +- .../OpenShiftWorkspaceServiceAccount.java | 59 ------ ...OpenShiftStopWorkspaceRoleProvisioner.java | 102 +++++++++ .../project/OpenShiftProjectFactoryTest.java | 75 ++++++- ...ShiftStopWorkspaceRoleProvisionerTest.java | 194 ++++++++++++++++++ 7 files changed, 424 insertions(+), 66 deletions(-) create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java create mode 100644 infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java create mode 100644 infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java diff --git a/infrastructures/openshift/pom.xml b/infrastructures/openshift/pom.xml index 49c6dfd145d..7d2e84f925e 100644 --- a/infrastructures/openshift/pom.xml +++ b/infrastructures/openshift/pom.xml @@ -55,6 +55,10 @@ io.opentracing opentracing-api + + javax.annotation + javax.annotation-api + javax.inject javax.inject diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java new file mode 100644 index 00000000000..86e96076919 --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2018 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.environment; + +import javax.annotation.PostConstruct; +import javax.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OpenShiftCheInstallationLocation checks the KUBERNETES_NAMESPACE and POD_NAMESPACE environment variables + * to determine what namespace Che is installed in. Users should use this class to retrieve the installation + * namespace name. + * + * @author Tom George + */ +@Singleton +public class OpenShiftCheInstallationLocation { + + private static final Logger LOG = LoggerFactory.getLogger(OpenShiftCheInstallationLocation.class); + + private String installationLocationNamespace; + + /** @return The name of the namespace where Che is installed */ + public String getInstallationLocationNamespace() { + return installationLocationNamespace; + } + + @PostConstruct + public void postConstruct() { + String kubernetesNamespace = System.getenv("KUBERNETES_NAMESPACE"); + String podNamespace = System.getenv("POD_NAMESPACE"); + + if (kubernetesNamespace == null && podNamespace == null) { + LOG.info( + "Neither KUBERNETES_NAMESPACE nor POD_NAMESPACE is defined. Unable to determine Che installation location"); + } + installationLocationNamespace = + kubernetesNamespace == null ? podNamespace : kubernetesNamespace; + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactory.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactory.java index 37111212169..e8f4783e424 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactory.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactory.java @@ -39,6 +39,7 @@ import org.eclipse.che.workspace.infrastructure.openshift.Constants; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientConfigFactory; 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; @@ -52,6 +53,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory { private static final Logger LOG = LoggerFactory.getLogger(OpenShiftProjectFactory.class); private final OpenShiftClientFactory clientFactory; + private final OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner; @Inject public OpenShiftProjectFactory( @@ -63,6 +65,7 @@ public OpenShiftProjectFactory( boolean allowUserDefinedNamespaces, OpenShiftClientFactory clientFactory, OpenShiftClientConfigFactory clientConfigFactory, + OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner, UserManager userManager, KubernetesSharedPool sharedPool) { super( @@ -81,6 +84,7 @@ public OpenShiftProjectFactory( + "OAuth to personalize credentials that will be used for cluster access."); } this.clientFactory = clientFactory; + this.stopWorkspaceRoleProvisioner = stopWorkspaceRoleProvisioner; } public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws InfrastructureException { @@ -93,7 +97,7 @@ public OpenShiftProject getOrCreate(RuntimeIdentity identity) throws Infrastruct doCreateServiceAccount(osProject.getWorkspaceId(), osProject.getName()); osWorkspaceServiceAccount.prepare(); } - + stopWorkspaceRoleProvisioner.provision(osProject.getName()); return osProject; } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java index 90cdc3f9057..92bed0c2f8a 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java @@ -69,15 +69,6 @@ class OpenShiftWorkspaceServiceAccount { void prepare() throws InfrastructureException { OpenShiftClient osClient = clientFactory.createOC(workspaceId); - String stopWorkspacesRoleName = "workspace-stop"; - if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() == null) { - createStopWorkspacesRole(osClient, stopWorkspacesRoleName); - } - osClient - .roleBindings() - .inNamespace(projectName) - .createOrReplace(createStopWorkspacesRoleBinding()); - if (osClient.serviceAccounts().inNamespace(projectName).withName(serviceAccountName).get() == null) { createWorkspaceServiceAccount(osClient); @@ -151,37 +142,6 @@ private void createViewRole(OpenShiftClient osClient, String name) { osClient.roles().inNamespace(projectName).create(viewRole); } - private void createStopWorkspacesRole(OpenShiftClient osClient, String name) { - OpenshiftRole stopRole = - new OpenshiftRoleBuilder() - .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(); - osClient.roles().inNamespace(projectName).create(stopRole); - } - private OpenshiftRoleBinding createViewRoleBinding() { return new OpenshiftRoleBindingBuilder() .withNewMetadata() @@ -218,25 +178,6 @@ private OpenshiftRoleBinding createExecRoleBinding() { .build(); } - private OpenshiftRoleBinding createStopWorkspacesRoleBinding() { - return new OpenshiftRoleBindingBuilder() - .withNewMetadata() - .withName(serviceAccountName + "-stop") - .withNamespace(projectName) - .endMetadata() - .withNewRoleRef() - .withName("workspace-stop") - .withNamespace(projectName) - .endRoleRef() - .withSubjects( - new ObjectReferenceBuilder() - .withKind("ServiceAccount") - .withName("che") - .withNamespace("che") - .build()) - .build(); - } - private OpenshiftRoleBinding createCustomRoleBinding(String clusterRoleName) { return new OpenshiftRoleBindingBuilder() .withNewMetadata() diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java new file mode 100644 index 00000000000..76c6c7135eb --- /dev/null +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2012-2018 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.provision; + +import com.google.inject.Inject; +import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; +import io.fabric8.openshift.api.model.*; +import io.fabric8.openshift.client.OpenShiftClient; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftCheInstallationLocation; + +/** + * This class creates the necessary role and rolebindings to allow the che serviceaccount to stop + * user workspaces. + * + * @author Tom George + */ +public class OpenShiftStopWorkspaceRoleProvisioner { + + private final OpenShiftClientFactory clientFactory; + private final OpenShiftCheInstallationLocation installationLocation; + + @Inject + public OpenShiftStopWorkspaceRoleProvisioner( + OpenShiftClientFactory clientFactory, OpenShiftCheInstallationLocation installationLocation) { + this.clientFactory = clientFactory; + this.installationLocation = installationLocation; + } + + public void provision(String projectName) throws InfrastructureException { + OpenShiftClient osClient = clientFactory.createOC(); + String stopWorkspacesRoleName = "workspace-stop"; + if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() == null) { + osClient + .roles() + .inNamespace(projectName) + .createOrReplace(createStopWorkspacesRole(stopWorkspacesRoleName)); + } + osClient + .roleBindings() + .inNamespace(projectName) + .createOrReplace(createStopWorkspacesRoleBinding(projectName)); + } + + protected OpenshiftRole createStopWorkspacesRole(String name) { + return new OpenshiftRoleBuilder() + .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 OpenshiftRoleBinding createStopWorkspacesRoleBinding(String projectName) { + return new OpenshiftRoleBindingBuilder() + .withNewMetadata() + .withName("che-workspace-stop") + .withNamespace(projectName) + .endMetadata() + .withNewRoleRef() + .withName("workspace-stop") + .withNamespace(projectName) + .endRoleRef() + .withSubjects( + new ObjectReferenceBuilder() + .withKind("ServiceAccount") + .withName("che") + .withNamespace(installationLocation.getInstallationLocationNamespace()) + .build()) + .build(); + } +} diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java index 6c9f7efc026..9b4a020289c 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java @@ -54,9 +54,11 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; +import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientConfigFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; +import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftStopWorkspaceRoleProvisioner; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -76,6 +78,7 @@ public class OpenShiftProjectFactoryTest { @Mock private OpenShiftClientConfigFactory configFactory; @Mock private OpenShiftClientFactory clientFactory; + @Mock private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner; @Mock private WorkspaceManager workspaceManager; @Mock private UserManager userManager; @Mock private KubernetesSharedPool pool; @@ -113,7 +116,16 @@ public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfName throws Exception { projectFactory = new OpenShiftProjectFactory( - "legacy", "", "", "defaultNs", false, clientFactory, configFactory, userManager, pool); + "legacy", + "", + "", + "defaultNs", + false, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); projectFactory.checkIfNamespaceIsAllowed("defaultNs"); } @@ -124,7 +136,16 @@ public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfName throws Exception { projectFactory = new OpenShiftProjectFactory( - "legacy", "", "", "defaultNs", true, clientFactory, configFactory, userManager, pool); + "legacy", + "", + "", + "defaultNs", + true, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); projectFactory.checkIfNamespaceIsAllowed("any-namespace"); } @@ -138,7 +159,16 @@ public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfName throws Exception { projectFactory = new OpenShiftProjectFactory( - "legacy", "", "", "defaultNs", false, clientFactory, configFactory, userManager, pool); + "legacy", + "", + "", + "defaultNs", + false, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); projectFactory.checkIfNamespaceIsAllowed("any-namespace"); } @@ -151,7 +181,16 @@ public void shouldNotThrowExceptionIfDefaultNamespaceIsSpecifiedOnCheckingIfName throws Exception { projectFactory = new OpenShiftProjectFactory( - "projectName", "", "", null, false, clientFactory, configFactory, userManager, pool); + "projectName", + "", + "", + null, + false, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); } @Test @@ -182,6 +221,7 @@ public void shouldReturnDefaultProjectWhenItExistsAndUserDefinedIsNotAllowed() t false, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool); @@ -213,6 +253,7 @@ public void shouldReturnDefaultProjectWhenItDoesNotExistAndUserDefinedIsNotAllow false, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool); @@ -244,6 +285,7 @@ public void shouldThrowExceptionWhenFailedToGetInfoAboutDefaultNamespace() throw false, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool); @@ -260,7 +302,16 @@ public void shouldReturnListOfExistingProjectsAlongWithDefaultIfUserDefinedIsAll projectFactory = new OpenShiftProjectFactory( - "predefined", "", "", "default", true, clientFactory, configFactory, userManager, pool); + "predefined", + "", + "", + "default", + true, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); List availableNamespaces = projectFactory.list(); @@ -291,7 +342,16 @@ public void shouldReturnListOfExistingProjectsAlongWithNonExistingDefaultIfUserD projectFactory = new OpenShiftProjectFactory( - "predefined", "", "", "default", true, clientFactory, configFactory, userManager, pool); + "predefined", + "", + "", + "default", + true, + clientFactory, + configFactory, + stopWorkspaceRoleProvisioner, + userManager, + pool); List availableNamespaces = projectFactory.list(); assertEquals(availableNamespaces.size(), 2); @@ -324,6 +384,7 @@ public void shouldThrowExceptionWhenFailedToGetNamespaces() throws Exception { true, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool); @@ -350,6 +411,7 @@ public void shouldRequireNamespacePriorExistenceIfDifferentFromDefaultAndUserDef false, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); @@ -380,6 +442,7 @@ public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndProjectIsNotP false, clientFactory, configFactory, + stopWorkspaceRoleProvisioner, userManager, pool)); OpenShiftProject toReturnProject = mock(OpenShiftProject.class); diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java new file mode 100644 index 00000000000..26bc3810a98 --- /dev/null +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2012-2018 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.provision; + +import static org.mockito.Mockito.*; +import static org.testng.Assert.assertEquals; + +import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; +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.Resource; +import io.fabric8.openshift.api.model.*; +import io.fabric8.openshift.client.OpenShiftClient; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; +import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftCheInstallationLocation; +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 OpenShiftStopWorkspaceRoleProvisioner} + * + *

#author Tom George + */ +@Listeners(MockitoTestNGListener.class) +public class OpenShiftStopWorkspaceRoleProvisionerTest { + + @Mock private OpenShiftCheInstallationLocation cheInstallationLocation; + private OpenShiftStopWorkspaceRoleProvisioner stopWorkspaceRoleProvisioner; + + @Mock private OpenShiftClientFactory clientFactory; + @Mock private OpenShiftClient osClient; + @Mock private KubernetesClient kubernetesClient; + + @Mock + private MixedOperation< + OpenshiftRole, + OpenshiftRoleList, + DoneableOpenshiftRole, + Resource> + mixedRoleOperation; + + @Mock + private MixedOperation< + OpenshiftRoleBinding, + OpenshiftRoleBindingList, + DoneableOpenshiftRoleBinding, + Resource> + mixedRoleBindingOperation; + + @Mock + private NonNamespaceOperation< + OpenshiftRole, + OpenshiftRoleList, + DoneableOpenshiftRole, + Resource> + nonNamespaceRoleOperation; + + @Mock + private NonNamespaceOperation< + OpenshiftRoleBinding, + OpenshiftRoleBindingList, + DoneableOpenshiftRoleBinding, + Resource> + nonNamespaceRoleBindingOperation; + + @Mock private Resource roleResource; + @Mock private Resource roleBindingResource; + @Mock private OpenshiftRole mockRole; + @Mock private OpenshiftRoleBinding mockRoleBinding; + + private final OpenshiftRole expectedRole = + new OpenshiftRoleBuilder() + .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 OpenshiftRoleBinding expectedRoleBinding = + new OpenshiftRoleBindingBuilder() + .withNewMetadata() + .withName("che-workspace-stop") + .withNamespace("developer-che") + .endMetadata() + .withNewRoleRef() + .withName("workspace-stop") + .withNamespace("developer-che") + .endRoleRef() + .withSubjects( + new ObjectReferenceBuilder() + .withKind("ServiceAccount") + .withName("che") + .withNamespace("che") + .build()) + .build(); + + @BeforeMethod + public void setUp() throws Exception { + lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che"); + stopWorkspaceRoleProvisioner = + new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation); + lenient().when(clientFactory.createOC()).thenReturn(osClient); + lenient().when(osClient.roles()).thenReturn(mixedRoleOperation); + lenient().when(osClient.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() { + when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che"); + assertEquals( + stopWorkspaceRoleProvisioner.createStopWorkspacesRoleBinding("developer-che"), + expectedRoleBinding); + } + + @Test + public void shouldCreateRoleAndRoleBindingWhenRoleDoesNotYetExist() + throws InfrastructureException { + stopWorkspaceRoleProvisioner.provision("developer-che"); + verify(osClient, times(2)).roles(); + verify(osClient.roles(), times(2)).inNamespace("developer-che"); + verify(osClient.roles().inNamespace("developer-che")).withName("workspace-stop"); + verify(osClient.roles().inNamespace("developer-che")).createOrReplace(expectedRole); + verify(osClient).roleBindings(); + verify(osClient.roleBindings()).inNamespace("developer-che"); + verify(osClient.roleBindings().inNamespace("developer-che")) + .createOrReplace(expectedRoleBinding); + } + + @Test + public void shouldCreateRoleBindingWhenRoleAlreadyExists() throws InfrastructureException { + lenient().when(roleResource.get()).thenReturn(expectedRole); + stopWorkspaceRoleProvisioner.provision("developer-che"); + verify(osClient, times(1)).roles(); + verify(osClient).roleBindings(); + verify(osClient.roleBindings()).inNamespace("developer-che"); + verify(osClient.roleBindings().inNamespace("developer-che")) + .createOrReplace(expectedRoleBinding); + } +} From 650f2056d0d1e072e0e8dfeb0ac2cf7d158f395a Mon Sep 17 00:00:00 2001 From: Tom George Date: Mon, 20 Apr 2020 09:59:18 -0500 Subject: [PATCH 3/4] Do nothing if installation location cannot be determined, inject environment variables into OpenShiftCheInstallationLocation Signed-off-by: Tom George --- infrastructures/openshift/pom.xml | 4 --- .../OpenShiftCheInstallationLocation.java | 32 ++++++++--------- ...OpenShiftStopWorkspaceRoleProvisioner.java | 34 ++++++++++++------- .../project/OpenShiftProjectFactoryTest.java | 1 - 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/infrastructures/openshift/pom.xml b/infrastructures/openshift/pom.xml index 7d2e84f925e..49c6dfd145d 100644 --- a/infrastructures/openshift/pom.xml +++ b/infrastructures/openshift/pom.xml @@ -55,10 +55,6 @@ io.opentracing opentracing-api - - javax.annotation - javax.annotation-api - javax.inject javax.inject diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java index 86e96076919..8b292f10b1b 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/environment/OpenShiftCheInstallationLocation.java @@ -11,15 +11,16 @@ */ package org.eclipse.che.workspace.infrastructure.openshift.environment; -import javax.annotation.PostConstruct; +import com.google.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * OpenShiftCheInstallationLocation checks the KUBERNETES_NAMESPACE and POD_NAMESPACE environment variables - * to determine what namespace Che is installed in. Users should use this class to retrieve the installation - * namespace name. + * OpenShiftCheInstallationLocation checks the KUBERNETES_NAMESPACE and POD_NAMESPACE environment + * variables to determine what namespace Che is installed in. Users should use this class to + * retrieve the installation namespace name. * * @author Tom George */ @@ -28,23 +29,20 @@ public class OpenShiftCheInstallationLocation { private static final Logger LOG = LoggerFactory.getLogger(OpenShiftCheInstallationLocation.class); - private String installationLocationNamespace; + @Inject(optional = true) + @Named("env.KUBERNETES_NAMESPACE") + private String kubernetesNamespace = null; + + @Inject(optional = true) + @Named("env.POD_NAMESPACE") + private String podNamespace = null; /** @return The name of the namespace where Che is installed */ public String getInstallationLocationNamespace() { - return installationLocationNamespace; - } - - @PostConstruct - public void postConstruct() { - String kubernetesNamespace = System.getenv("KUBERNETES_NAMESPACE"); - String podNamespace = System.getenv("POD_NAMESPACE"); - if (kubernetesNamespace == null && podNamespace == null) { - LOG.info( - "Neither KUBERNETES_NAMESPACE nor POD_NAMESPACE is defined. Unable to determine Che installation location"); + LOG.warn( + "Neither KUBERNETES_NAMESPACE nor POD_NAMESPACE is defined. Unable to determine Che installation location"); } - installationLocationNamespace = - kubernetesNamespace == null ? podNamespace : kubernetesNamespace; + return kubernetesNamespace == null ? podNamespace : kubernetesNamespace; } } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java index 76c6c7135eb..03e4fc72ebc 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java @@ -18,6 +18,8 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftCheInstallationLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class creates the necessary role and rolebindings to allow the che serviceaccount to stop @@ -28,28 +30,36 @@ public class OpenShiftStopWorkspaceRoleProvisioner { private final OpenShiftClientFactory clientFactory; - private final OpenShiftCheInstallationLocation installationLocation; + private final String installationLocation; + + private static final Logger LOG = LoggerFactory.getLogger(OpenShiftCheInstallationLocation.class); @Inject public OpenShiftStopWorkspaceRoleProvisioner( OpenShiftClientFactory clientFactory, OpenShiftCheInstallationLocation installationLocation) { this.clientFactory = clientFactory; - this.installationLocation = installationLocation; + this.installationLocation = installationLocation.getInstallationLocationNamespace(); } public void provision(String projectName) throws InfrastructureException { - OpenShiftClient osClient = clientFactory.createOC(); - String stopWorkspacesRoleName = "workspace-stop"; - if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() == null) { + if (installationLocation != null) { + OpenShiftClient osClient = clientFactory.createOC(); + String stopWorkspacesRoleName = "workspace-stop"; + if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() + == null) { + osClient + .roles() + .inNamespace(projectName) + .createOrReplace(createStopWorkspacesRole(stopWorkspacesRoleName)); + } osClient - .roles() + .roleBindings() .inNamespace(projectName) - .createOrReplace(createStopWorkspacesRole(stopWorkspacesRoleName)); + .createOrReplace(createStopWorkspacesRoleBinding(projectName)); + } else { + LOG.warn( + "Could not determine Che installation location. Did not provision stop workspace Role and RoleBinding."); } - osClient - .roleBindings() - .inNamespace(projectName) - .createOrReplace(createStopWorkspacesRoleBinding(projectName)); } protected OpenshiftRole createStopWorkspacesRole(String name) { @@ -95,7 +105,7 @@ protected OpenshiftRoleBinding createStopWorkspacesRoleBinding(String projectNam new ObjectReferenceBuilder() .withKind("ServiceAccount") .withName("che") - .withNamespace(installationLocation.getInstallationLocationNamespace()) + .withNamespace(installationLocation) .build()) .build(); } diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java index 9b4a020289c..33efa1fa238 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftProjectFactoryTest.java @@ -54,7 +54,6 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; -import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespace; import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientConfigFactory; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; From 32e2cc91c5fea427c4e4ccb78ba7c32823ab91ea Mon Sep 17 00:00:00 2001 From: Ilya Buziuk Date: Tue, 21 Apr 2020 12:38:22 +0200 Subject: [PATCH 4/4] che #15906 Adding 'che.workspace.stop.role.enabled' property in order to have a possibility to disable the 'OpenShiftStopWorkspaceRoleProvisioner' Signed-off-by: Ilya Buziuk --- .../webapp/WEB-INF/classes/che/che.properties | 4 +++ ...OpenShiftStopWorkspaceRoleProvisioner.java | 15 ++++++++--- ...ShiftStopWorkspaceRoleProvisionerTest.java | 26 ++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index bb47d175dea..e23237cbd55 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -157,6 +157,10 @@ che.workspace.server.liveness_probes=wsagent/http,exec-agent/http,terminal,theia # default 10MB=10485760 che.workspace.startup_debug_log_limit_bytes=10485760 +# If true, 'stop-workspace' role with the edit privileges will be granted to the 'che' ServiceAccount. +# This configuration is mainly required for workspace idling when the OpenShift OAuth is enabled. +che.workspace.stop.role.enabled=true + ### Templates # Folder that contains JSON files with code templates and samples diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java index 03e4fc72ebc..97ec21dd88c 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisioner.java @@ -11,10 +11,11 @@ */ package org.eclipse.che.workspace.infrastructure.openshift.provision; -import com.google.inject.Inject; import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; import io.fabric8.openshift.api.model.*; import io.fabric8.openshift.client.OpenShiftClient; +import javax.inject.Inject; +import javax.inject.Named; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftCheInstallationLocation; @@ -31,18 +32,22 @@ public class OpenShiftStopWorkspaceRoleProvisioner { private final OpenShiftClientFactory clientFactory; private final String installationLocation; + private final boolean stopWorkspaceRoleEnabled; private static final Logger LOG = LoggerFactory.getLogger(OpenShiftCheInstallationLocation.class); @Inject public OpenShiftStopWorkspaceRoleProvisioner( - OpenShiftClientFactory clientFactory, OpenShiftCheInstallationLocation installationLocation) { + OpenShiftClientFactory clientFactory, + OpenShiftCheInstallationLocation installationLocation, + @Named("che.workspace.stop.role.enabled") boolean stopWorkspaceRoleEnabled) { this.clientFactory = clientFactory; this.installationLocation = installationLocation.getInstallationLocationNamespace(); + this.stopWorkspaceRoleEnabled = stopWorkspaceRoleEnabled; } public void provision(String projectName) throws InfrastructureException { - if (installationLocation != null) { + if (stopWorkspaceRoleEnabled && installationLocation != null) { OpenShiftClient osClient = clientFactory.createOC(); String stopWorkspacesRoleName = "workspace-stop"; if (osClient.roles().inNamespace(projectName).withName(stopWorkspacesRoleName).get() @@ -58,7 +63,9 @@ public void provision(String projectName) throws InfrastructureException { .createOrReplace(createStopWorkspacesRoleBinding(projectName)); } else { LOG.warn( - "Could not determine Che installation location. Did not provision stop workspace Role and RoleBinding."); + "Stop workspace Role and RoleBinding will not be provisioned to the '{}' namespace. 'che.workspace.stop.role.enabled' property is set to '{}'", + installationLocation, + stopWorkspaceRoleEnabled); } } diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java index 26bc3810a98..81bcb637b20 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftStopWorkspaceRoleProvisionerTest.java @@ -132,7 +132,7 @@ public class OpenShiftStopWorkspaceRoleProvisionerTest { public void setUp() throws Exception { lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn("che"); stopWorkspaceRoleProvisioner = - new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation); + new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true); lenient().when(clientFactory.createOC()).thenReturn(osClient); lenient().when(osClient.roles()).thenReturn(mixedRoleOperation); lenient().when(osClient.roleBindings()).thenReturn(mixedRoleBindingOperation); @@ -191,4 +191,28 @@ public void shouldCreateRoleBindingWhenRoleAlreadyExists() throws Infrastructure verify(osClient.roleBindings().inNamespace("developer-che")) .createOrReplace(expectedRoleBinding); } + + @Test + public void shouldNotCreateRoleBindingWhenStopWorkspaceRolePropertyIsDisabled() + throws InfrastructureException { + OpenShiftStopWorkspaceRoleProvisioner disabledStopWorkspaceRoleProvisioner = + new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, false); + disabledStopWorkspaceRoleProvisioner.provision("developer-che"); + verify(osClient, never()).roles(); + verify(osClient, never()).roleBindings(); + verify(osClient.roleBindings(), never()).inNamespace("developer-che"); + } + + @Test + public void shouldNotCreateRoleBindingWhenInstallationLocationIsNull() + throws InfrastructureException { + lenient().when(cheInstallationLocation.getInstallationLocationNamespace()).thenReturn(null); + OpenShiftStopWorkspaceRoleProvisioner + stopWorkspaceRoleProvisionerWithoutValidInstallationLocation = + new OpenShiftStopWorkspaceRoleProvisioner(clientFactory, cheInstallationLocation, true); + stopWorkspaceRoleProvisionerWithoutValidInstallationLocation.provision("developer-che"); + verify(osClient, never()).roles(); + verify(osClient, never()).roleBindings(); + verify(osClient.roleBindings(), never()).inNamespace("developer-che"); + } }