fix: Add a predefined secret to store credentials (#68)

* Create a predefined K8s secret per nemaespace to store credentials.
* Add a specific secret role for the credentials secret. This role allows to edit only this predefined secret. Other secrets are not controlled by this role.
pull/85/head
Igor Vinokur 2021-08-17 12:22:17 +03:00 committed by GitHub
parent ab4a840fe3
commit 5934e97fb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 395 additions and 42 deletions

View File

@ -11,6 +11,7 @@
*/
package org.eclipse.che.workspace.infrastructure.kubernetes.namespace;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import io.fabric8.kubernetes.api.model.HasMetadata;
@ -47,6 +48,8 @@ public abstract class AbstractWorkspaceServiceAccount<
public static final String EXEC_ROLE_NAME = "exec";
public static final String VIEW_ROLE_NAME = "workspace-view";
public static final String METRICS_ROLE_NAME = "workspace-metrics";
public static final String SECRETS_ROLE_NAME = "workspace-secrets";
public static final String CREDENTIALS_SECRET_NAME = "workspace-credentials-secret";
protected final String namespace;
protected final String serviceAccountName;
@ -107,44 +110,55 @@ public abstract class AbstractWorkspaceServiceAccount<
// exec role
ensureRoleWithBinding(
k8sClient,
EXEC_ROLE_NAME,
singletonList("pods/exec"),
singletonList(""),
singletonList("create"),
buildRole(
EXEC_ROLE_NAME,
singletonList("pods/exec"),
emptyList(),
singletonList(""),
singletonList("create")),
serviceAccountName + "-exec");
// view role
ensureRoleWithBinding(
k8sClient,
VIEW_ROLE_NAME,
Arrays.asList("pods", "services"),
singletonList(""),
singletonList("list"),
buildRole(
VIEW_ROLE_NAME,
Arrays.asList("pods", "services"),
emptyList(),
singletonList(""),
singletonList("list")),
serviceAccountName + "-view");
// metrics role
ensureRoleWithBinding(
k8sClient,
METRICS_ROLE_NAME,
Arrays.asList("pods", "nodes"),
singletonList("metrics.k8s.io"),
Arrays.asList("list", "get", "watch"),
buildRole(
METRICS_ROLE_NAME,
Arrays.asList("pods", "nodes"),
emptyList(),
singletonList("metrics.k8s.io"),
Arrays.asList("list", "get", "watch")),
serviceAccountName + "-metrics");
// credentials-secret role
ensureRoleWithBinding(
k8sClient,
buildRole(
SECRETS_ROLE_NAME,
singletonList("secrets"),
singletonList(CREDENTIALS_SECRET_NAME),
singletonList(""),
Arrays.asList("get", "patch")),
serviceAccountName + "-secrets");
}
private void ensureRoleWithBinding(
Client k8sClient,
String roleName,
List<String> resources,
List<String> apiGroups,
List<String> verbs,
String bindingName) {
ensureRole(k8sClient, roleName, resources, apiGroups, verbs);
private void ensureRoleWithBinding(Client k8sClient, R role, String bindingName) {
ensureRole(k8sClient, role);
//noinspection unchecked
roleBindings
.apply(k8sClient)
.inNamespace(namespace)
.createOrReplace(createRoleBinding(roleName, bindingName, false));
.createOrReplace(createRoleBinding(role.getMetadata().getName(), bindingName, false));
}
/**
@ -180,11 +194,16 @@ public abstract class AbstractWorkspaceServiceAccount<
*
* @param name the name of the role
* @param resources the resources the role grants access to
* @param resourceNames specific resource names witch the role grants access to.
* @param verbs the verbs the role allows
* @return the role object for the given type of Client
*/
protected abstract R buildRole(
String name, List<String> resources, List<String> apiGroups, List<String> verbs);
String name,
List<String> resources,
List<String> resourceNames,
List<String> apiGroups,
List<String> verbs);
/**
* Builds a new role binding but does not persist it.
@ -209,17 +228,9 @@ public abstract class AbstractWorkspaceServiceAccount<
.build());
}
private void ensureRole(
Client k8sClient,
String name,
List<String> resources,
List<String> apiGroups,
List<String> verbs) {
private void ensureRole(Client k8sClient, R role) {
//noinspection unchecked
roles
.apply(k8sClient)
.inNamespace(namespace)
.createOrReplace(buildRole(name, resources, apiGroups, verbs));
roles.apply(k8sClient).inNamespace(namespace).createOrReplace(role);
}
public interface ClientFactory<C extends KubernetesClient> {

View File

@ -20,6 +20,7 @@ import static java.util.Collections.singletonList;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.NamespaceNameValidator.METADATA_NAME_MAX_LENGTH;
import com.google.common.annotations.VisibleForTesting;
@ -30,6 +31,8 @@ import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import java.util.Collections;
import java.util.HashMap;
@ -341,6 +344,25 @@ public class KubernetesNamespaceFactory {
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
if (namespace
.secrets()
.get()
.stream()
.noneMatch(s -> s.getMetadata().getName().equals(CREDENTIALS_SECRET_NAME))) {
Secret secret =
new SecretBuilder()
.withType("opaque")
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.endMetadata()
.build();
clientFactory
.create()
.secrets()
.inNamespace(identity.getInfrastructureNamespace())
.create(secret);
}
if (!isNullOrEmpty(serviceAccountName)) {
KubernetesWorkspaceServiceAccount workspaceServiceAccount =
doCreateServiceAccount(namespace.getWorkspaceId(), namespace.getName());

View File

@ -76,6 +76,20 @@ public class KubernetesSecrets {
}
}
/**
* Get all secrets.
*
* @return namespace secrets list
* @throws InfrastructureException when any exception occurs
*/
public List<Secret> get() throws InfrastructureException {
try {
return clientFactory.create(workspaceId).secrets().inNamespace(namespace).list().getItems();
} catch (KubernetesClientException e) {
throw new KubernetesInfrastructureException(e);
}
}
/**
* Deletes all existing secrets.
*

View File

@ -50,7 +50,11 @@ public class KubernetesWorkspaceServiceAccount
@Override
protected Role buildRole(
String name, List<String> resources, List<String> apiGroups, List<String> verbs) {
String name,
List<String> resources,
List<String> resourceNames,
List<String> apiGroups,
List<String> verbs) {
return new RoleBuilder()
.withNewMetadata()
.withName(name)
@ -58,6 +62,7 @@ public class KubernetesWorkspaceServiceAccount
.withRules(
new PolicyRuleBuilder()
.withResources(resources)
.withResourceNames(resourceNames)
.withApiGroups(apiGroups)
.withVerbs(verbs)
.build())

View File

@ -16,6 +16,8 @@ import static java.util.Collections.singletonList;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_INFRASTRUCTURE_NAMESPACE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.SECRETS_ROLE_NAME;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory.NAMESPACE_TEMPLATE_ATTRIBUTE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
@ -41,15 +43,19 @@ import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.Namespace;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.NamespaceList;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.ServiceAccountList;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.api.model.rbac.ClusterRoleBuilder;
import io.fabric8.kubernetes.api.model.rbac.PolicyRule;
import io.fabric8.kubernetes.api.model.rbac.Role;
import io.fabric8.kubernetes.api.model.rbac.RoleBindingList;
import io.fabric8.kubernetes.api.model.rbac.RoleList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
@ -118,8 +124,7 @@ public class KubernetesNamespaceFactoryTest {
@Mock private PreferenceManager preferenceManager;
@Mock Appender mockedAppender;
@Mock
private NonNamespaceOperation<Namespace, NamespaceList, Resource<Namespace>> namespaceOperation;
@Mock private NonNamespaceOperation namespaceOperation;
@Mock private Resource<Namespace> namespaceResource;
@ -454,6 +459,82 @@ public class KubernetesNamespaceFactoryTest {
.get(PHASE_ATTRIBUTE)); // no phase - means such namespace does not exist
}
@Test
public void shouldCreateCredentialsSecretIfNotExists() throws Exception {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"",
"",
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
when(toReturnNamespace.secrets()).thenReturn(secrets);
when(secrets.get()).thenReturn(Collections.emptyList());
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(k8sClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
namespaceFactory.getOrCreate(identity);
// then
ArgumentCaptor<Secret> secretsCaptor = ArgumentCaptor.forClass(Secret.class);
verify(namespaceOperation).create(secretsCaptor.capture());
Secret secret = secretsCaptor.getValue();
Assert.assertEquals(secret.getMetadata().getName(), CREDENTIALS_SECRET_NAME);
Assert.assertEquals(secret.getType(), "opaque");
}
@Test
public void shouldNotCreateCredentialsSecretIfExists() throws Exception {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"",
"",
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(k8sClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
namespaceFactory.getOrCreate(identity);
// then
verify(namespaceOperation, never()).create(any());
}
@Test(
expectedExceptions = InfrastructureException.class,
expectedExceptionsMessageRegExp =
@ -532,6 +613,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
// when
@ -564,6 +646,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
// when
@ -598,6 +681,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
@ -636,6 +720,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
@ -669,7 +754,7 @@ public class KubernetesNamespaceFactoryTest {
RoleList roles = k8sClient.rbac().roles().inNamespace("workspace123").list();
assertEquals(
Sets.newHashSet("workspace-view", "workspace-metrics", "exec"),
Sets.newHashSet("workspace-view", "workspace-metrics", "workspace-secrets", "exec"),
roles.getItems().stream().map(r -> r.getMetadata().getName()).collect(Collectors.toSet()));
RoleBindingList bindings = k8sClient.rbac().roleBindings().inNamespace("workspace123").list();
assertEquals(
@ -683,7 +768,67 @@ public class KubernetesNamespaceFactoryTest {
"serviceAccount-cluster0",
"serviceAccount-cluster1",
"serviceAccount-view",
"serviceAccount-exec"));
"serviceAccount-exec",
"serviceAccount-secrets"));
}
@Test
public void shouldCreateAndBindCredentialsSecretRole() throws Exception {
// given
namespaceFactory =
spy(
new KubernetesNamespaceFactory(
"serviceAccount",
"cr2, cr3",
"<username>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
clientFactory,
cheClientFactory,
userManager,
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
when(clientFactory.create(any())).thenReturn(k8sClient);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
namespaceFactory.getOrCreate(identity);
// then
Optional<Role> roleOptional =
k8sClient
.rbac()
.roles()
.inNamespace("workspace123")
.list()
.getItems()
.stream()
.filter(r -> r.getMetadata().getName().equals(SECRETS_ROLE_NAME))
.findAny();
assertTrue(roleOptional.isPresent());
PolicyRule rule = roleOptional.get().getRules().get(0);
assertEquals(rule.getResources(), singletonList("secrets"));
assertEquals(rule.getResourceNames(), singletonList(CREDENTIALS_SECRET_NAME));
assertEquals(rule.getApiGroups(), singletonList(""));
assertEquals(rule.getVerbs(), Arrays.asList("get", "patch"));
assertTrue(
k8sClient
.rbac()
.roleBindings()
.inNamespace("workspace123")
.list()
.getItems()
.stream()
.anyMatch(rb -> rb.getMetadata().getName().equals("serviceAccount-secrets")));
}
@Test
@ -706,6 +851,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getWorkspaceId()).thenReturn("workspace123");
when(toReturnNamespace.getName()).thenReturn("workspace123");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
@ -725,7 +871,7 @@ public class KubernetesNamespaceFactoryTest {
RoleList roles = k8sClient.rbac().roles().inNamespace("workspace123").list();
assertEquals(
Sets.newHashSet("workspace-view", "workspace-metrics", "exec"),
Sets.newHashSet("workspace-view", "workspace-metrics", "workspace-secrets", "exec"),
roles.getItems().stream().map(r -> r.getMetadata().getName()).collect(Collectors.toSet()));
Role role1 = roles.getItems().get(0);
Role role2 = roles.getItems().get(1);
@ -742,7 +888,11 @@ public class KubernetesNamespaceFactoryTest {
.stream()
.map(r -> r.getMetadata().getName())
.collect(Collectors.toSet()),
Sets.newHashSet("serviceAccount-metrics", "serviceAccount-view", "serviceAccount-exec"));
Sets.newHashSet(
"serviceAccount-metrics",
"serviceAccount-view",
"serviceAccount-exec",
"serviceAccount-secrets"));
}
@Test
@ -1036,6 +1186,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getName()).thenReturn("jondoe-che");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
KubernetesNamespaceMetaImpl namespaceMeta =
@ -1075,6 +1226,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
KubernetesNamespaceMetaImpl namespaceMeta =
@ -1113,6 +1265,7 @@ public class KubernetesNamespaceFactoryTest {
preferenceManager,
pool));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
when(toReturnNamespace.getName()).thenReturn("jondoe-cha-cha-cha");
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
KubernetesNamespaceMetaImpl namespaceMeta =
@ -1189,6 +1342,7 @@ public class KubernetesNamespaceFactoryTest {
pool));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
KubernetesNamespace toReturnNamespace = mock(KubernetesNamespace.class);
prepareNamespace(toReturnNamespace);
doReturn(toReturnNamespace).when(namespaceFactory).doCreateNamespaceAccess(any(), any());
// when
@ -1310,6 +1464,16 @@ public class KubernetesNamespaceFactoryTest {
when(namespaceList.getItems()).thenThrow(e);
}
private void prepareNamespace(KubernetesNamespace namespace) throws InfrastructureException {
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
when(namespace.secrets()).thenReturn(secrets);
Secret secretMock = mock(Secret.class);
ObjectMeta objectMeta = mock(ObjectMeta.class);
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
when(secretMock.getMetadata()).thenReturn(objectMeta);
when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
}
private Namespace createNamespace(String name, String phase) {
return new NamespaceBuilder()
.withNewMetadata()

View File

@ -16,11 +16,14 @@ import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Project;
import java.util.HashMap;
@ -120,6 +123,26 @@ public class OpenShiftProjectFactory extends KubernetesNamespaceFactory {
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
// create credentials secret
if (osProject
.secrets()
.get()
.stream()
.noneMatch(s -> s.getMetadata().getName().equals(CREDENTIALS_SECRET_NAME))) {
Secret secret =
new SecretBuilder()
.withType("opaque")
.withNewMetadata()
.withName(CREDENTIALS_SECRET_NAME)
.endMetadata()
.build();
clientFactory
.createOC()
.secrets()
.inNamespace(identity.getInfrastructureNamespace())
.create(secret);
}
if (!isNullOrEmpty(getServiceAccountName())) {
OpenShiftWorkspaceServiceAccount osWorkspaceServiceAccount =
doCreateServiceAccount(osProject.getWorkspaceId(), osProject.getName());

View File

@ -55,7 +55,11 @@ class OpenShiftWorkspaceServiceAccount
@Override
protected Role buildRole(
String name, List<String> resources, List<String> apiGroups, List<String> verbs) {
String name,
List<String> resources,
List<String> resourceNames,
List<String> apiGroups,
List<String> verbs) {
return new RoleBuilder()
.withNewMetadata()
.withName(name)
@ -63,6 +67,7 @@ class OpenShiftWorkspaceServiceAccount
.withRules(
new PolicyRuleBuilder()
.withResources(resources)
.withResourceNames(resourceNames)
.withApiGroups(apiGroups)
.withVerbs(verbs)
.build())

View File

@ -16,12 +16,12 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.DEFAULT_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta.PHASE_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.CREDENTIALS_SECRET_NAME;
import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DESCRIPTION_ANNOTATION;
import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DESCRIPTION_ATTRIBUTE;
import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DISPLAY_NAME_ANNOTATION;
import static org.eclipse.che.workspace.infrastructure.openshift.Constants.PROJECT_DISPLAY_NAME_ATTRIBUTE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
@ -35,9 +35,13 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.Status;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
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.Project;
import io.fabric8.openshift.api.model.ProjectBuilder;
@ -45,6 +49,7 @@ import io.fabric8.openshift.api.model.ProjectList;
import io.fabric8.openshift.client.OpenShiftClient;
import io.fabric8.openshift.client.dsl.ProjectOperation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -63,12 +68,15 @@ 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.namespace.KubernetesSecrets;
import org.eclipse.che.workspace.infrastructure.kubernetes.util.KubernetesSharedPool;
import org.eclipse.che.workspace.infrastructure.openshift.CheServerOpenshiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.OpenShiftClientFactory;
import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftStopWorkspaceRoleProvisioner;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
@ -529,6 +537,7 @@ public class OpenShiftProjectFactoryTest {
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
// when
@ -542,6 +551,92 @@ public class OpenShiftProjectFactoryTest {
verify(toReturnProject).prepare(eq(false), eq(false), any(), any());
}
@Test
public void shouldCreateCredentialsSecretIfNotExists() throws Exception {
// given
projectFactory =
spy(
new OpenShiftProjectFactory(
"",
null,
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class);
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
when(toReturnProject.secrets()).thenReturn(secrets);
when(secrets.get()).thenReturn(Collections.emptyList());
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
projectFactory.getOrCreate(identity);
// then
ArgumentCaptor<Secret> secretsCaptor = ArgumentCaptor.forClass(Secret.class);
verify(namespaceOperation).create(secretsCaptor.capture());
Secret secret = secretsCaptor.getValue();
Assert.assertEquals(secret.getMetadata().getName(), CREDENTIALS_SECRET_NAME);
Assert.assertEquals(secret.getType(), "opaque");
}
@Test
public void shouldNotCreateCredentialsSecretIfExist() throws Exception {
// given
projectFactory =
spy(
new OpenShiftProjectFactory(
"",
null,
"<userid>-che",
true,
true,
true,
NAMESPACE_LABELS,
NAMESPACE_ANNOTATIONS,
true,
clientFactory,
cheClientFactory,
cheServerOpenshiftClientFactory,
stopWorkspaceRoleProvisioner,
userManager,
preferenceManager,
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
NonNamespaceOperation namespaceOperation = mock(NonNamespaceOperation.class);
MixedOperation mixedOperation = mock(MixedOperation.class);
lenient().when(osClient.secrets()).thenReturn(mixedOperation);
lenient().when(mixedOperation.inNamespace(anyString())).thenReturn(namespaceOperation);
// when
RuntimeIdentity identity =
new RuntimeIdentityImpl("workspace123", null, USER_ID, "workspace123");
projectFactory.getOrCreate(identity);
// then
verify(namespaceOperation, never()).create(any());
}
@Test
public void shouldPrepareWorkspaceServiceAccountIfItIsConfiguredAndProjectIsNotPredefined()
throws Exception {
@ -567,6 +662,7 @@ public class OpenShiftProjectFactoryTest {
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123");
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
@ -609,6 +705,7 @@ public class OpenShiftProjectFactoryTest {
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123");
prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
OpenShiftWorkspaceServiceAccount serviceAccount = mock(OpenShiftWorkspaceServiceAccount.class);
@ -649,6 +746,7 @@ public class OpenShiftProjectFactoryTest {
pool,
NO_OAUTH_IDENTITY_PROVIDER));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
when(toReturnProject.getWorkspaceId()).thenReturn("workspace123");
when(toReturnProject.getName()).thenReturn("workspace123");
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
@ -769,6 +867,7 @@ public class OpenShiftProjectFactoryTest {
NO_OAUTH_IDENTITY_PROVIDER));
EnvironmentContext.getCurrent().setSubject(new SubjectImpl("jondoe", "123", null, false));
OpenShiftProject toReturnProject = mock(OpenShiftProject.class);
prepareProject(toReturnProject);
doReturn(toReturnProject).when(projectFactory).doCreateProjectAccess(any(), any());
// when
@ -806,6 +905,16 @@ public class OpenShiftProjectFactoryTest {
when(projectList.getItems()).thenReturn(projects);
}
private void prepareProject(OpenShiftProject project) throws InfrastructureException {
KubernetesSecrets secrets = mock(KubernetesSecrets.class);
when(project.secrets()).thenReturn(secrets);
Secret secretMock = mock(Secret.class);
ObjectMeta objectMeta = mock(ObjectMeta.class);
when(objectMeta.getName()).thenReturn(CREDENTIALS_SECRET_NAME);
when(secretMock.getMetadata()).thenReturn(objectMeta);
when(secrets.get()).thenReturn(Collections.singletonList(secretMock));
}
private void throwOnTryToGetProjectsList(Throwable e) throws Exception {
when(projectListResource.list()).thenThrow(e);
}