Skip to content

Commit

Permalink
commit
Browse files Browse the repository at this point in the history
  • Loading branch information
vinokurig committed Feb 10, 2025
1 parent 21fb7b2 commit cfaf300
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 413 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigUserDataConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigAutomauntSecretConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.OAuthTokenSecretsConfigurator;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator;
Expand Down Expand Up @@ -110,8 +109,7 @@ protected void configure() {
namespaceConfigurators.addBinding().to(WorkspaceServiceAccountConfigurator.class);
namespaceConfigurators.addBinding().to(UserProfileConfigurator.class);
namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class);
namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class);
namespaceConfigurators.addBinding().to(GitconfigSecretConfigurator.class);
namespaceConfigurators.addBinding().to(GitconfigAutomauntSecretConfigurator.class);

bind(AuthorizationChecker.class).to(KubernetesAuthorizationCheckerImpl.class);
bind(PermissionsCleaner.class).asEagerSingleton();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright (c) 2012-2025 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.configurator;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableMap;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.eclipse.che.api.factory.server.scm.GitUserData;
import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher;
import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GitconfigAutomauntSecretConfigurator implements NamespaceConfigurator {
private final CheServerKubernetesClientFactory cheServerKubernetesClientFactory;
private final Set<GitUserDataFetcher> gitUserDataFetchers;
private static final Logger LOG =
LoggerFactory.getLogger(GitconfigAutomauntSecretConfigurator.class);
private static final String CONFIGMAP_DATA_KEY = "gitconfig";
private static final String GITCONFIG_SECRET_NAME = "devworkspace-gitconfig-automaunt-secret";
private static final Map<String, String> TOKEN_SECRET_LABELS =
ImmutableMap.of(
"app.kubernetes.io/part-of", "che.eclipse.org",
"app.kubernetes.io/component", "scm-personal-access-token");
private static final Map<String, String> GITCONFIG_SECRET_LABELS =
ImmutableMap.of(
"controller.devfile.io/mount-to-devworkspace",
"true",
"controller.devfile.io/watch-secret",
"true");
private static final Map<String, String> GITCONFIG_SECRET_ANNOTATIONS =
ImmutableMap.of(
"controller.devfile.io/mount-as", "subpath", "controller.devfile.io/mount-path", "/etc");
private final Pattern usernmaePattern =
Pattern.compile("\\[user](.|\\s)*name\\s*=\\s*(?<username>.*)");

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with '[user]' and containing many repetitions of ' '.
private final Pattern emailPattern =
Pattern.compile("\\[user](.|\\s)*email\\s*=\\s*(?<email>.*)");

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with '[user]' and containing many repetitions of ' '.
private final Pattern emptyStringPattern = Pattern.compile("[\"']\\s*[\"']");

@Inject
public GitconfigAutomauntSecretConfigurator(
CheServerKubernetesClientFactory cheServerKubernetesClientFactory,
Set<GitUserDataFetcher> gitUserDataFetchers) {
this.cheServerKubernetesClientFactory = cheServerKubernetesClientFactory;
this.gitUserDataFetchers = gitUserDataFetchers;
}

@Override
public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName)
throws InfrastructureException {
KubernetesClient client = cheServerKubernetesClientFactory.create();
Optional<String> gitconfigOptional = getGitconfig(client, namespaceName);
Optional<Pair<String, String>> usernameAndEmailFromGitconfigOptional = Optional.empty();
Optional<Pair<String, String>> usernameAndEmailFromFetcherOptional =
getUsernameAndEmailFromFetcher();
Optional<String> tokenFromGitconfigOptional = Optional.empty();
Optional<String> tokenFromSecretOptional = getTokenFromSecret(client, namespaceName);
if (gitconfigOptional.isPresent()) {
String gitconfig = gitconfigOptional.get();
usernameAndEmailFromGitconfigOptional = getUsernameAndEmailFromGitconfig(gitconfig);
tokenFromGitconfigOptional = getTokenFromGitconfig(gitconfig);
}
if (needUpdateGitconfigSecret(
usernameAndEmailFromGitconfigOptional,
usernameAndEmailFromFetcherOptional,
tokenFromGitconfigOptional,
tokenFromSecretOptional)) {
Secret gitconfigSecret = buildGitconfigSecret();
Optional<Pair<String, String>> usernameAndEmailOptional =
usernameAndEmailFromGitconfigOptional.isPresent()
? usernameAndEmailFromGitconfigOptional
: usernameAndEmailFromFetcherOptional;
Optional<String> gitconfigSectionsOptional =
generateGitconfigSections(usernameAndEmailOptional, tokenFromSecretOptional);
if (gitconfigSectionsOptional.isPresent()) {
gitconfigSecret.setData(
ImmutableMap.of(CONFIGMAP_DATA_KEY, encode(gitconfigSectionsOptional.get())));
client.secrets().inNamespace(namespaceName).createOrReplace(gitconfigSecret);
}
}
}

private Secret buildGitconfigSecret() {
return new SecretBuilder()
.withNewMetadata()
.withName(GITCONFIG_SECRET_NAME)
.withLabels(GITCONFIG_SECRET_LABELS)
.withAnnotations(GITCONFIG_SECRET_ANNOTATIONS)
.endMetadata()
.build();
}

private boolean needUpdateGitconfigSecret(
Optional<Pair<String, String>> usernameAndEmailFromGitconfigOptional,
Optional<Pair<String, String>> usernameAndEmailFromFetcher,
Optional<String> tokenFromGitconfigOptional,
Optional<String> tokenFromSecretOptional) {
if (tokenFromGitconfigOptional.isPresent() && tokenFromSecretOptional.isPresent()) {
return !tokenFromGitconfigOptional.get().equals(tokenFromSecretOptional.get());
} else
return tokenFromSecretOptional.isPresent()
|| (usernameAndEmailFromGitconfigOptional.isEmpty()
&& usernameAndEmailFromFetcher.isPresent());
}

private Optional<String> generateGitconfigSections(
Optional<Pair<String, String>> usernameAndEmailOptional, Optional<String> tokenOptional) {
Optional<String> userSectionOptional = Optional.empty();
Optional<String> httpSectionOPtional = Optional.empty();
if (usernameAndEmailOptional.isPresent()) {
userSectionOptional =
Optional.of(
generateUserSection(
usernameAndEmailOptional.get().first, usernameAndEmailOptional.get().second));
}
if (tokenOptional.isPresent()) {
httpSectionOPtional = Optional.of(generateHttpSection(tokenOptional.get()));
}
StringJoiner joiner = new StringJoiner("\n");
userSectionOptional.ifPresent(joiner::add);
httpSectionOPtional.ifPresent(joiner::add);
return joiner.length() > 0 ? Optional.of(joiner.toString()) : Optional.empty();
}

private Optional<Pair<String, String>> getUsernameAndEmailFromGitconfig(String gitconfig) {
if (gitconfig.contains("[user]")) {
Matcher usernameMatcher = usernmaePattern.matcher(gitconfig);
Matcher emailaMatcher = emailPattern.matcher(gitconfig);
if (usernameMatcher.find() && emailaMatcher.find()) {
String username = usernameMatcher.group("username");
String email = emailaMatcher.group("email");
if (!emptyStringPattern.matcher(username).matches()
&& !emptyStringPattern.matcher(email).matches()) {
return Optional.of(new Pair<>(username, email));
}
}
}
return Optional.empty();
}

private Optional<Pair<String, String>> getUsernameAndEmailFromFetcher() {
GitUserData gitUserData;
for (GitUserDataFetcher fetcher : gitUserDataFetchers) {
try {
gitUserData = fetcher.fetchGitUserData();
if (!isNullOrEmpty(gitUserData.getScmUsername())
&& !isNullOrEmpty(gitUserData.getScmUserEmail())) {
return Optional.of(
new Pair<>(gitUserData.getScmUsername(), gitUserData.getScmUserEmail()));
}
} catch (ScmUnauthorizedException
| ScmCommunicationException
| ScmConfigurationPersistenceException
| ScmItemNotFoundException
| ScmBadRequestException e) {
LOG.debug("No GitUserDataFetcher is configured. " + e.getMessage());
}
}
return Optional.empty();
}

private Optional<String> getTokenFromSecret(KubernetesClient client, String namespaceName) {
for (Secret tokenSecret :
client
.secrets()
.inNamespace(namespaceName)
.withLabels(TOKEN_SECRET_LABELS)
.list()
.getItems()) {
if ("azure-devops"
.equals(
tokenSecret
.getMetadata()
.getAnnotations()
.get("che.eclipse.org/scm-provider-name"))) {
return Optional.of(decode(tokenSecret.getData().get("token")));
}
}
return Optional.empty();
}

private Optional<String> getTokenFromGitconfig(String gitconfig) {
if (gitconfig.contains("[http]")) {
Matcher matcher =
Pattern.compile(
"\\[http]\\n\\s*extra[hH]eader\\s*=\\s*[\"']?Authorization: Basic (?<tokenEncoded>.*)[\"']?")
.matcher(gitconfig);
if (matcher.find()) {
// remove the first character which is ':' from the token value
return Optional.of(decode(matcher.group("tokenEncoded")).substring(1));
}
}
return Optional.empty();
}

private String generateHttpSection(String token) {
return "[http]\n\textraHeader = Authorization: Basic " + encode(":" + token);
}

private String generateUserSection(String username, String email) {
return String.format("[user]\n\tname = %1$s\n\temail = %2$s", username, email);
}

private Optional<String> getGitconfig(KubernetesClient client, String namespaceName) {
Secret gitconfigAutomauntSecret =
client.secrets().inNamespace(namespaceName).withName(GITCONFIG_SECRET_NAME).get();
if (gitconfigAutomauntSecret != null) {
String gitconfig = gitconfigAutomauntSecret.getData().get(CONFIGMAP_DATA_KEY);
if (!isNullOrEmpty(gitconfig)) {
return Optional.of(decode(gitconfig));
}
}
return Optional.empty();
}

private String encode(String value) {
return Base64.getEncoder().encodeToString(value.getBytes(UTF_8));
}

private String decode(String value) {
return new String(Base64.getDecoder().decode(value.getBytes(UTF_8)));
}
}
Loading

0 comments on commit cfaf300

Please # to comment.