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

CHE-14527: Support git clone for repos with self-signed SSL certs #15084

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions deploy/kubernetes/helm/che/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace


# If git-self-signed-cert is used then configure Che Server with certificate content
# to propagate it to trust store
{{- if .Values.global.useGitSelfSignedCerts }}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we can with minimal changes support multiple self-signed certs for different hosts?
On one hand it's quite easy to imagine config map like

data:
  my-git.com:9232: --BEGINCERT adssaxxxxxx --
  my-another-git.org:3716: --BEGINCERT adssaxxxxxx --

On another hand, it's not clear how to inject each property to the server. Ways I see:

  1. Just configure SecretName for Che Server, and then Che Server will ready it from K8s API and propagate to workspaces.
  2. Mount whole secret as a folder, where files names are hosts, and content - is ca.cert. And let Che Server know where these folder is mount.

- name: CHE_GIT_SELF__SIGNED__CERT
valueFrom:
configMapKeyRef:
name: {{ .Values.global.cheGitSelfSignedCertConfigMapName }}
key: ca.crt
optional: false
- name: CHE_GIT_SELF__SIGNED__CERT__HOST
valueFrom:
configMapKeyRef:
name: {{ .Values.global.cheGitSelfSignedCertConfigMapName }}
key: githost
optional: false
{{- end }}

{{- if .Values.global.tls.enabled }}

# If self-signed-cert is used then configure Che Server with certificate content
Expand Down
7 changes: 7 additions & 0 deletions deploy/kubernetes/helm/che/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ global:
useSelfSignedCerts: false
selfSignedCertSecretName: self-signed-cert


## If using git self-signed certificate is enabled
## then certificate from `cheGitSelfSignedCertConfigMapName` will be propagated to Che components'
## and provide particular configuration for Git
useGitSelfSignedCerts: true
cheGitSelfSignedCertConfigMapName: che-git-self-signed-cert

gitHubClientID: ""
gitHubClientSecret: ""
pvcClaim: "1Gi"
Expand Down
12 changes: 12 additions & 0 deletions deploy/openshift/templates/che-server-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ objects:
key: ca.crt
name: self-signed-certificate
optional: true
- name: CHE_GIT_SELF__SIGNED__CERT
valueFrom:
configMapKeyRef:
name: che-git-self-signed-cert
key: ca.crt
optional: false
- name: CHE_GIT_SELF__SIGNED__CERT__HOST
valueFrom:
configMapKeyRef:
name: che-git-self-signed-cert
key: githost
optional: false
- name: CHE_WORKSPACE_PLUGIN__REGISTRY__URL
value: "${CHE_WORKSPACE_PLUGIN__REGISTRY__URL}"
- name: CHE_WORKSPACE_DEVFILE__REGISTRY__URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitUserProfileProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.LogsVolumeMachineProvisioner;
Expand All @@ -31,6 +31,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSshKeysProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.RamLimitRequestProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter;
Expand Down Expand Up @@ -73,8 +74,9 @@ class KubernetesEnvironmentProvisionerImpl
private final ServiceAccountProvisioner serviceAccountProvisioner;
private final CertificateProvisioner certificateProvisioner;
private final VcsSshKeysProvisioner vcsSshKeysProvisioner;
private final GitUserProfileProvisioner gitUserProfileProvisioner;
private final GitConfigProvisioner gitConfigProvisioner;
private final PreviewUrlExposer<KubernetesEnvironment> previewUrlExposer;
private final VcsSslCertificateProvisioner vcsSslCertificateProvisioner;

@Inject
public KubernetesEnvironmentProvisionerImpl(
Expand All @@ -94,8 +96,9 @@ public KubernetesEnvironmentProvisionerImpl(
ServiceAccountProvisioner serviceAccountProvisioner,
CertificateProvisioner certificateProvisioner,
VcsSshKeysProvisioner vcsSshKeysProvisioner,
GitUserProfileProvisioner gitUserProfileProvisioner,
PreviewUrlExposer<KubernetesEnvironment> previewUrlExposer) {
GitConfigProvisioner gitConfigProvisioner,
PreviewUrlExposer<KubernetesEnvironment> previewUrlExposer,
VcsSslCertificateProvisioner vcsSslCertificateProvisioner) {
this.pvcEnabled = pvcEnabled;
this.volumesStrategy = volumesStrategy;
this.uniqueNamesProvisioner = uniqueNamesProvisioner;
Expand All @@ -112,7 +115,8 @@ public KubernetesEnvironmentProvisionerImpl(
this.serviceAccountProvisioner = serviceAccountProvisioner;
this.certificateProvisioner = certificateProvisioner;
this.vcsSshKeysProvisioner = vcsSshKeysProvisioner;
this.gitUserProfileProvisioner = gitUserProfileProvisioner;
this.vcsSslCertificateProvisioner = vcsSslCertificateProvisioner;
this.gitConfigProvisioner = gitConfigProvisioner;
this.previewUrlExposer = previewUrlExposer;
}

Expand Down Expand Up @@ -152,7 +156,8 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
serviceAccountProvisioner.provision(k8sEnv, identity);
certificateProvisioner.provision(k8sEnv, identity);
vcsSshKeysProvisioner.provision(k8sEnv, identity);
gitUserProfileProvisioner.provision(k8sEnv, identity);
vcsSslCertificateProvisioner.provision(k8sEnv, identity);
gitConfigProvisioner.provision(k8sEnv, identity);
LOG.debug("Provisioning Kubernetes environment done for workspace '{}'", workspaceId);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;

@Singleton
public class GitUserProfileProvisioner implements ConfigurationProvisioner<KubernetesEnvironment> {
public class GitConfigProvisioner implements ConfigurationProvisioner<KubernetesEnvironment> {

private final String GIT_CONFIG_MAP_NAME_SUFFIX = "-gitconfig";

Expand All @@ -60,14 +60,20 @@ public class GitUserProfileProvisioner implements ConfigurationProvisioner<Kuber
private static final String GIT_USER_NAME_PROPERTY = "git.user.name";
private static final String GIT_USER_EMAIL_PROPERTY = "git.user.email";
private static final String CONFIG_MAP_VOLUME_NAME = "gitconfigvolume";
private static final String HTTPS = "https://";

private PreferenceManager preferenceManager;
private UserManager userManager;
private VcsSslCertificateProvisioner vcsSslCertificateProvisioner;

@Inject
public GitUserProfileProvisioner(PreferenceManager preferenceManager, UserManager userManager) {
public GitConfigProvisioner(
PreferenceManager preferenceManager,
UserManager userManager,
VcsSslCertificateProvisioner vcsSslCertificateProvisioner) {
this.preferenceManager = preferenceManager;
this.userManager = userManager;
this.vcsSslCertificateProvisioner = vcsSslCertificateProvisioner;
}

@Override
Expand Down Expand Up @@ -178,6 +184,33 @@ private Optional<String> prepareGitConfigurationContent(String userName, String
config.append('\t').append("email = ").append(userEmail).append('\n');
}

if (vcsSslCertificateProvisioner.isConfigured()) {
String host = vcsSslCertificateProvisioner.getGitServerHost();

// Will add leading scheme (https://) if it not provide in configuration.
// If host not configured wil return empty string, it will means that
// provided certificate will used for all https connections.

StringBuilder gitServerHosts = new StringBuilder();
if (!isNullOrEmpty(host)) {
gitServerHosts.append(" \"");
if (!host.startsWith(HTTPS)) {
gitServerHosts.append(HTTPS);
}
gitServerHosts.append(host);
gitServerHosts.append("\"");
}

config
.append("[http")
.append(gitServerHosts.toString())
.append("]")
.append('\n')
.append('\t')
.append("sslCAInfo = ")
.append(vcsSslCertificateProvisioner.getCertPath());
}

return of(config.toString());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright (c) 2012-2018 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.provision;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Collections.singletonMap;

import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import java.util.Optional;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodData;

/**
* Mount configured self-signed certificate for git provider as file in each workspace machines if
* configured.
*
* @author Vitalii Parfonov
*/
@Singleton
public class VcsSslCertificateProvisioner
implements ConfigurationProvisioner<KubernetesEnvironment> {
static final String CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX = "-che-git-self-signed-cert";
static final String CHE_GIT_SELF_SIGNED_VOLUME = "che-git-self-signed-cert";
static final String CERT_MOUNT_PATH = "/etc/che/git/cert/";
static final String CA_CERT_FILE = "ca.crt";

@Inject(optional = true)
@Named("che.git.self_signed_cert")
private String certificate;

@Inject(optional = true)
@Named("che.git.self_signed_cert_host")
private String host;

public VcsSslCertificateProvisioner() {}

@VisibleForTesting
VcsSslCertificateProvisioner(String certificate, String host) {
this.certificate = certificate;
this.host = host;
}

/**
* @return true only if system configured for using self-signed certificate fot https git
* operation
*/
public boolean isConfigured() {
return !isNullOrEmpty(certificate);
}

/** @return path to the certificate file */
public String getCertPath() {
return CERT_MOUNT_PATH + CA_CERT_FILE;
}

/**
* Return given in configuration git server host.
*
* @return git server host for git config it configured
*/
public String getGitServerHost() {
Copy link
Member

Choose a reason for hiding this comment

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

I would not expect from this method to return server host with escaped ".
I think it's specific to gitconfig constructing and you append " in GitConfigProvisioner instead

return host;
}

@Override
public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity)
throws InfrastructureException {
if (!isConfigured()) {
return;
}
String selfSignedCertConfigMapName =
identity.getWorkspaceId() + CHE_GIT_SELF_SIGNED_CERT_CONFIG_MAP_SUFFIX;
k8sEnv
.getConfigMaps()
.put(
selfSignedCertConfigMapName,
new ConfigMapBuilder()
.withNewMetadata()
.withName(selfSignedCertConfigMapName)
.endMetadata()
.withData(singletonMap(CA_CERT_FILE, certificate))
.build());

for (PodData pod : k8sEnv.getPodsData().values()) {
Optional<Volume> certVolume =
pod.getSpec()
.getVolumes()
.stream()
.filter(v -> v.getName().equals(CHE_GIT_SELF_SIGNED_VOLUME))
.findAny();

if (!certVolume.isPresent()) {
pod.getSpec().getVolumes().add(buildCertVolume(selfSignedCertConfigMapName));
}

for (Container container : pod.getSpec().getInitContainers()) {
provisionCertVolumeMountIfNeeded(container);
}
for (Container container : pod.getSpec().getContainers()) {
provisionCertVolumeMountIfNeeded(container);
}
}
}

private void provisionCertVolumeMountIfNeeded(Container container) {
Optional<VolumeMount> certVolumeMount =
container
.getVolumeMounts()
.stream()
.filter(vm -> vm.getName().equals(CHE_GIT_SELF_SIGNED_VOLUME))
.findAny();
if (!certVolumeMount.isPresent()) {
container.getVolumeMounts().add(buildCertVolumeMount());
}
}

private VolumeMount buildCertVolumeMount() {
return new VolumeMountBuilder()
.withName(CHE_GIT_SELF_SIGNED_VOLUME)
.withNewReadOnly(true)
.withMountPath(CERT_MOUNT_PATH)
.build();
}

private Volume buildCertVolume(String configMapName) {
return new VolumeBuilder()
.withName(CHE_GIT_SELF_SIGNED_VOLUME)
.withConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.CertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitUserProfileProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.GitConfigProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ImagePullSecretProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.LogsVolumeMachineProvisioner;
Expand All @@ -29,6 +29,7 @@
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ServiceAccountProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.UniqueNamesProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSshKeysProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.VcsSslCertificateProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.RamLimitRequestProvisioner;
import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter;
Expand Down Expand Up @@ -66,8 +67,9 @@ public class KubernetesEnvironmentProvisionerTest {
@Mock private ServiceAccountProvisioner serviceAccountProvisioner;
@Mock private CertificateProvisioner certificateProvisioner;
@Mock private VcsSshKeysProvisioner vcsSshKeysProvisioner;
@Mock private GitUserProfileProvisioner gitUserProfileProvisioner;
@Mock private GitConfigProvisioner gitConfigProvisioner;
@Mock private PreviewUrlExposer previewUrlExposer;
@Mock private VcsSslCertificateProvisioner vcsSslCertificateProvisioner;

private KubernetesEnvironmentProvisioner<KubernetesEnvironment> k8sInfraProvisioner;

Expand All @@ -93,8 +95,9 @@ public void setUp() {
serviceAccountProvisioner,
certificateProvisioner,
vcsSshKeysProvisioner,
gitUserProfileProvisioner,
previewUrlExposer);
gitConfigProvisioner,
previewUrlExposer,
vcsSslCertificateProvisioner);
provisionOrder =
inOrder(
logsVolumeMachineProvisioner,
Expand All @@ -111,7 +114,7 @@ public void setUp() {
proxySettingsProvisioner,
serviceAccountProvisioner,
certificateProvisioner,
gitUserProfileProvisioner,
gitConfigProvisioner,
previewUrlExposer);
}

Expand All @@ -137,7 +140,7 @@ public void performsOrderedProvisioning() throws Exception {
provisionOrder.verify(proxySettingsProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(serviceAccountProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(certificateProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(gitUserProfileProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verify(gitConfigProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity));
provisionOrder.verifyNoMoreInteractions();
}
}
Loading