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
parent
ab4a840fe3
commit
5934e97fb2
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue