Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add a predefined secret to store credentials #68

Merged
merged 9 commits into from
Aug 17, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -107,44 +110,55 @@ private void ensureImplicitRolesWithBindings(Client k8sClient) {
// 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));
}

/**
Expand Down Expand Up @@ -180,11 +194,16 @@ private void ensureExplicitClusterRoleBindings(Client k8sClient) {
*
* @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.
Expand All @@ -209,17 +228,9 @@ private void createWorkspaceServiceAccount(Client k8sClient) {
.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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,17 @@ public KubernetesNamespace(
* @param annotations annotations that should be set to the namespace
* @throws InfrastructureException if any exception occurs during namespace preparation or if the
* namespace doesn't exist and {@code canCreate} is {@code false}.
* @return {@code true} if the namespace didn't exist and namespace creation was invoked, {@code
* false} if the namespace was already created in the previous calls.
*/
void prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations)
boolean prepare(boolean canCreate, Map<String, String> labels, Map<String, String> annotations)
throws InfrastructureException {
KubernetesClient client = clientFactory.create(workspaceId);
Namespace namespace = get(name, client);
boolean needToCreateNewNamespace = false;

if (namespace == null) {
needToCreateNewNamespace = true;
if (!canCreate) {
throw new InfrastructureException(
format("Creating the namespace '%s' is not allowed, yet it was not found.", name));
Expand All @@ -157,6 +161,7 @@ void prepare(boolean canCreate, Map<String, String> labels, Map<String, String>
}
label(namespace, labels);
annotate(namespace, annotations);
return needToCreateNewNamespace;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -30,6 +31,8 @@
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;
Expand Down Expand Up @@ -336,10 +339,25 @@ public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws Infrastr
Map<String, String> namespaceAnnotationsEvaluated =
evaluateAnnotationPlaceholders(resolutionCtx);

namespace.prepare(
canCreateNamespace(identity),
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
boolean newNamespace =
namespace.prepare(
canCreateNamespace(identity),
labelNamespaces ? namespaceLabels : emptyMap(),
annotateNamespaces ? namespaceAnnotationsEvaluated : emptyMap());
if (newNamespace) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this will be true only in case that we've created the namespace in previous namespace.prepare call. So what if namespace already exists? Don't we need the secret then? The namespace could be prepared by admins or created by older Che version. What if user removed/modified the secret ? Don't we need to reconcile ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, reworked to check if the secret exists.

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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ public 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)
.endMetadata()
.withRules(
new PolicyRuleBuilder()
.withResources(resources)
.withResourceNames(resourceNames)
.withApiGroups(apiGroups)
.withVerbs(verbs)
.build())
Expand Down
Loading