diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactory.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactory.java index d1445466f6..9261b1263f 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactory.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactory.java @@ -31,14 +31,17 @@ public class KubernetesNamespaceFactory { private final String namespaceName; private final boolean isPredefined; + private final String serviceAccountName; private final KubernetesClientFactory clientFactory; @Inject public KubernetesNamespaceFactory( @Nullable @Named("che.infra.kubernetes.namespace") String namespaceName, + @Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName, KubernetesClientFactory clientFactory) { this.namespaceName = namespaceName; this.isPredefined = !isNullOrEmpty(namespaceName); + this.serviceAccountName = serviceAccountName; this.clientFactory = clientFactory; } @@ -64,6 +67,16 @@ public class KubernetesNamespaceFactory { final String namespaceName = isPredefined ? this.namespaceName : workspaceId; KubernetesNamespace namespace = doCreateNamespace(workspaceId, namespaceName); namespace.prepare(); + + if (!isPredefined() && !isNullOrEmpty(serviceAccountName)) { + // prepare service account for workspace only if account name is configured + // and project is not predefined + // since predefined project should be prepared during Che deployment + KubernetesWorkspaceServiceAccount workspaceServiceAccount = + doCreateServiceAccount(workspaceId, namespaceName); + workspaceServiceAccount.prepare(); + } + return namespace; } @@ -83,4 +96,11 @@ public class KubernetesNamespaceFactory { KubernetesNamespace doCreateNamespace(String workspaceId, String name) { return new KubernetesNamespace(clientFactory, name, workspaceId); } + + @VisibleForTesting + KubernetesWorkspaceServiceAccount doCreateServiceAccount( + String workspaceId, String namespaceName) { + return new KubernetesWorkspaceServiceAccount( + workspaceId, namespaceName, serviceAccountName, clientFactory); + } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesWorkspaceServiceAccount.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesWorkspaceServiceAccount.java new file mode 100644 index 0000000000..3ce5646731 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesWorkspaceServiceAccount.java @@ -0,0 +1,148 @@ +/* + * 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.kubernetes.namespace; + +import io.fabric8.kubernetes.api.model.rbac.KubernetesPolicyRuleBuilder; +import io.fabric8.kubernetes.api.model.rbac.KubernetesRole; +import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBinding; +import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBindingBuilder; +import io.fabric8.kubernetes.api.model.rbac.KubernetesRoleBuilder; +import io.fabric8.kubernetes.api.model.rbac.KubernetesSubjectBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; + +/** + * Holds logic for preparing workspace service account. + * + *

It checks that required service account, roles and role bindings exist and creates if needed. + * + * @author Sergii Leshchenko + */ +public class KubernetesWorkspaceServiceAccount { + + private final String namespace; + private final String serviceAccountName; + private final KubernetesClientFactory clientFactory; + private final String workspaceId; + + public KubernetesWorkspaceServiceAccount( + String workspaceId, + String namespace, + String serviceAccountName, + KubernetesClientFactory clientFactory) { + this.workspaceId = workspaceId; + this.namespace = namespace; + this.serviceAccountName = serviceAccountName; + this.clientFactory = clientFactory; + } + + /** + * Make sure that workspace service account exists and has `view` and `exec` role bindings. + * + *

Note that `view` role is used from cluster scope and `exec` role is created in the current + * namespace if does not exit. + * + * @throws InfrastructureException when any exception occurred + */ + void prepare() throws InfrastructureException { + KubernetesClient k8sClient = clientFactory.create(workspaceId); + + if (k8sClient.serviceAccounts().inNamespace(namespace).withName(serviceAccountName).get() + == null) { + createWorkspaceServiceAccount(k8sClient); + } + + String execRoleName = "exec"; + if (k8sClient.rbac().kubernetesRoles().inNamespace(namespace).withName(execRoleName).get() + == null) { + createExecRole(k8sClient, execRoleName); + } + + k8sClient + .rbac() + .kubernetesRoleBindings() + .inNamespace(namespace) + .createOrReplace(createExecRoleBinding()); + k8sClient + .rbac() + .kubernetesRoleBindings() + .inNamespace(namespace) + .createOrReplace(createViewRoleBinding()); + } + + private void createWorkspaceServiceAccount(KubernetesClient k8sClient) { + k8sClient + .serviceAccounts() + .inNamespace(namespace) + .createOrReplaceWithNew() + .withAutomountServiceAccountToken(true) + .withNewMetadata() + .withName(serviceAccountName) + .endMetadata() + .done(); + } + + private void createExecRole(KubernetesClient k8sClient, String name) { + KubernetesRole execRole = + new KubernetesRoleBuilder() + .withNewMetadata() + .withName(name) + .endMetadata() + .withRules( + new KubernetesPolicyRuleBuilder() + .withResources("pods/exec") + .withApiGroups("") + .withVerbs("create") + .build()) + .build(); + k8sClient.rbac().kubernetesRoles().inNamespace(namespace).create(execRole); + } + + private KubernetesRoleBinding createViewRoleBinding() { + return new KubernetesRoleBindingBuilder() + .withNewMetadata() + .withName(serviceAccountName + "-view") + .withNamespace(namespace) + .endMetadata() + .withNewRoleRef() + .withKind("ClusterRole") + .withName("view") + .endRoleRef() + .withSubjects( + new KubernetesSubjectBuilder() + .withKind("ServiceAccount") + .withName(serviceAccountName) + .withNamespace(namespace) + .build()) + .build(); + } + + private KubernetesRoleBinding createExecRoleBinding() { + return new KubernetesRoleBindingBuilder() + .withNewMetadata() + .withName(serviceAccountName + "-exec") + .withNamespace(namespace) + .endMetadata() + .withNewRoleRef() + .withKind("Role") + .withName("exec") + .endRoleRef() + .withSubjects( + new KubernetesSubjectBuilder() + .withKind("ServiceAccount") + .withName(serviceAccountName) + .withNamespace(namespace) + .build()) + .build(); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactoryTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactoryTest.java index 0b6abb1bf9..96f1f10296 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactoryTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/KubernetesNamespaceFactoryTest.java @@ -40,7 +40,7 @@ public class KubernetesNamespaceFactoryTest { @Test public void shouldReturnTrueIfNamespaceIsNotEmptyOnCheckingIfNamespaceIsPredefined() { // given - namespaceFactory = new KubernetesNamespaceFactory("predefined", clientFactory); + namespaceFactory = new KubernetesNamespaceFactory("predefined", "", clientFactory); // when boolean isPredefined = namespaceFactory.isPredefined(); @@ -52,7 +52,7 @@ public class KubernetesNamespaceFactoryTest { @Test public void shouldReturnTrueIfNamespaceIsEmptyOnCheckingIfNamespaceIsPredefined() { // given - namespaceFactory = new KubernetesNamespaceFactory("", clientFactory); + namespaceFactory = new KubernetesNamespaceFactory("", "", clientFactory); // when boolean isPredefined = namespaceFactory.isPredefined(); @@ -64,7 +64,7 @@ public class KubernetesNamespaceFactoryTest { @Test public void shouldReturnTrueIfNamespaceIsNullOnCheckingIfNamespaceIsPredefined() { // given - namespaceFactory = new KubernetesNamespaceFactory(null, clientFactory); + namespaceFactory = new KubernetesNamespaceFactory(null, "", clientFactory); // when boolean isPredefined = namespaceFactory.isPredefined(); @@ -76,7 +76,7 @@ public class KubernetesNamespaceFactoryTest { @Test public void shouldCreateAndPrepareNamespaceWithPredefinedValueIfItIsNotEmpty() throws Exception { // given - namespaceFactory = spy(new KubernetesNamespaceFactory("predefined", clientFactory)); + namespaceFactory = spy(new KubernetesNamespaceFactory("predefined", "", clientFactory)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); @@ -93,7 +93,7 @@ public class KubernetesNamespaceFactoryTest { public void shouldCreateAndPrepareNamespaceWithWorkspaceIdAsNameIfConfiguredNameIsNotPredefined() throws Exception { // given - namespaceFactory = spy(new KubernetesNamespaceFactory("", clientFactory)); + namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); @@ -111,7 +111,7 @@ public class KubernetesNamespaceFactoryTest { shouldCreateNamespaceAndDoNotPrepareNamespaceOnCreatingNamespaceWithWorkspaceIdAndNameSpecified() throws Exception { // given - namespaceFactory = spy(new KubernetesNamespaceFactory("", clientFactory)); + namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory)); KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); @@ -123,4 +123,63 @@ public class KubernetesNamespaceFactoryTest { verify(namespaceFactory).doCreateNamespace("workspace123", "name"); verify(toReturnNamespace, never()).prepare(); } + + @Test + public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndNamespaceIsNotPredefined() + throws Exception { + // given + namespaceFactory = spy(new KubernetesNamespaceFactory("", "serviceAccount", clientFactory)); + KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); + doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); + + KubernetesWorkspaceServiceAccount serviceAccount = + mock(KubernetesWorkspaceServiceAccount.class); + doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any()); + + // when + namespaceFactory.create("workspace123"); + + // then + verify(namespaceFactory).doCreateServiceAccount("workspace123", "workspace123"); + verify(serviceAccount).prepare(); + } + + @Test + public void shouldNotPrepareWorkspaceServiceAccountIfItIsConfiguredAndProjectIsPredefined() + throws Exception { + // given + namespaceFactory = + spy(new KubernetesNamespaceFactory("namespace", "serviceAccount", clientFactory)); + KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); + doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); + + KubernetesWorkspaceServiceAccount serviceAccount = + mock(KubernetesWorkspaceServiceAccount.class); + doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any()); + + // when + namespaceFactory.create("workspace123"); + + // then + verify(namespaceFactory, never()).doCreateServiceAccount(any(), any()); + } + + @Test + public void shouldNotPrepareWorkspaceServiceAccountIfItIsNotConfiguredAndProjectIsNotPredefined() + throws Exception { + // given + namespaceFactory = spy(new KubernetesNamespaceFactory("", "", clientFactory)); + KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class); + doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespace(any(), any()); + + KubernetesWorkspaceServiceAccount serviceAccount = + mock(KubernetesWorkspaceServiceAccount.class); + doReturn(serviceAccount).when(namespaceFactory).doCreateServiceAccount(any(), any()); + + // when + namespaceFactory.create("workspace123"); + + // then + verify(namespaceFactory, never()).doCreateServiceAccount(any(), any()); + } } 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 46e02f27fc..72b61e2840 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,7 +39,7 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory { @Nullable @Named("che.infra.openshift.project") String projectName, @Nullable @Named("che.infra.kubernetes.service_account_name") String serviceAccountName, OpenShiftClientFactory clientFactory) { - super(projectName, clientFactory); + super(projectName, serviceAccountName, clientFactory); this.projectName = projectName; this.serviceAccountName = serviceAccountName; this.clientFactory = clientFactory; @@ -64,9 +64,9 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory { // prepare service account for workspace only if account name is configured // and project is not predefined // since predefined project should be prepared during Che deployment - WorkspaceServiceAccount workspaceServiceAccount = + OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount = doCreateServiceAccount(workspaceId, projectName); - workspaceServiceAccount.prepare(); + osWorkspaceServiceAccount.prepare(); } return osProject; @@ -90,7 +90,8 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory { } @VisibleForTesting - WorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) { - return new WorkspaceServiceAccount(workspaceId, projectName, serviceAccountName, clientFactory); + OpenShiftWorkspaceServiceAccount doCreateServiceAccount(String workspaceId, String projectName) { + return new OpenShiftWorkspaceServiceAccount( + workspaceId, projectName, serviceAccountName, clientFactory); } } diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/WorkspaceServiceAccount.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java similarity index 95% rename from infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/WorkspaceServiceAccount.java rename to infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java index 4c24af93d5..1c0c751adb 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/WorkspaceServiceAccount.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/project/OpenShiftWorkspaceServiceAccount.java @@ -27,15 +27,17 @@ import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory *

It checks that required service account, roles and role bindings exist and creates if needed. * * @author Sergii Leshchenko + * @see + * org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesWorkspaceServiceAccount */ -class WorkspaceServiceAccount { +class OpenShiftWorkspaceServiceAccount { private final String projectName; private final String serviceAccountName; private final OpenShiftClientFactory clientFactory; private final String workspaceId; - WorkspaceServiceAccount( + OpenShiftWorkspaceServiceAccount( String workspaceId, String projectName, String serviceAccountName, 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 d3a52e09ed..d287102576 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 @@ -77,7 +77,7 @@ public class OpenShiftProjectFactoryTest { OpenShiftProject toReturnProject = mock(OpenShiftProject.class); doReturn(toReturnProject).when(projectFactory).doCreateProject(any(), any()); - WorkspaceServiceAccount serviceAccount = mock(WorkspaceServiceAccount.class); + OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class); doReturn(serviceAccount).when(projectFactory).doCreateServiceAccount(any(), any()); // when