Skip to content

Commit

Permalink
fix: Add a predefined secret to store credentials (#68)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
vinokurig authored Aug 17, 2021
1 parent ab4a840 commit 5934e97
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 42 deletions.
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 @@ -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 @@ -341,6 +344,25 @@ public KubernetesNamespace getOrCreate(RuntimeIdentity identity) throws Infrastr
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ public List<Secret> get(LabelSelector labelSelector) throws InfrastructureExcept
}
}

/**
* 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.
*
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

0 comments on commit 5934e97

Please sign in to comment.