Do not mount ssh keys as secrets if the name is not a valid Kubernetes config map key name and not a valid host name (#19031)
* Do not mount ssh keys as secrets if the name is not a valid Kubernetes config map key name and not a valid hostname Signed-off-by: Sergii Kabashniuk <skabashniuk@redhat.com>7.28.x
parent
f652d93881
commit
5f3c188c59
|
|
@ -68,5 +68,9 @@ public final class Warnings {
|
|||
NOT_ABLE_TO_PROVISION_WORKSPACE_DEPLOYMENT_LABELS_OR_ANNOTATIONS_MESSAGE =
|
||||
"Not able to provision workspace %s deployment labels or annotations because of invalid configuration. Reason: '%s'";
|
||||
|
||||
public static final int SSH_KEYS_WILL_NOT_BE_MOUNTED = 4300;
|
||||
public static final String SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE =
|
||||
"Ssh keys %s have invalid names and can't be mounted to workspace %s.";
|
||||
|
||||
private Warnings() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import io.fabric8.kubernetes.api.model.apps.Deployment;
|
|||
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations;
|
||||
|
||||
/**
|
||||
|
|
@ -46,6 +47,9 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations;
|
|||
public class KubernetesObjectUtil {
|
||||
|
||||
private static final String STORAGE_PARAM = "storage";
|
||||
private static final String VALID_CONFIG_MAP_KEY_NAME = "[-._a-zA-Z0-9]+";
|
||||
private static final Pattern VALID_CONFIG_MAP_KEY_NAME_PATTERN =
|
||||
Pattern.compile(VALID_CONFIG_MAP_KEY_NAME);
|
||||
|
||||
/** Checks if the specified object has the specified label. */
|
||||
public static boolean isLabeled(HasMetadata source, String key, String value) {
|
||||
|
|
@ -253,4 +257,9 @@ public class KubernetesObjectUtil {
|
|||
.getOrDefault(CREATE_IN_CHE_INSTALLATION_NAMESPACE, FALSE.toString())
|
||||
.equals(TRUE.toString());
|
||||
}
|
||||
|
||||
/** Checks the provided name is a valid kubernetes config map key name */
|
||||
public static boolean isValidConfigMapKeyName(String configMapKeyName) {
|
||||
return VALID_CONFIG_MAP_KEY_NAME_PATTERN.matcher(configMapKeyName).matches();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.provision;
|
|||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_SSH_KEYS;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.Warnings.NOT_ABLE_TO_PROVISION_SSH_KEYS_MESSAGE;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesObjectUtil.isValidConfigMapKeyName;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
|
||||
|
|
@ -29,12 +32,14 @@ import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
|
|||
import io.fabric8.kubernetes.api.model.VolumeBuilder;
|
||||
import io.fabric8.kubernetes.api.model.VolumeMount;
|
||||
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
|
|
@ -47,8 +52,10 @@ import org.eclipse.che.api.workspace.server.model.impl.WarningImpl;
|
|||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.commons.annotation.Traced;
|
||||
import org.eclipse.che.commons.tracing.TracingTags;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.Warnings;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment.PodRole;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -89,13 +96,18 @@ public class SshKeysProvisioner implements ConfigurationProvisioner<KubernetesEn
|
|||
|
||||
private static final String SSH_SECRET_TYPE = "opaque";
|
||||
|
||||
public static final Pattern VALID_DOMAIN_PATTERN =
|
||||
Pattern.compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$");
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SshKeysProvisioner.class);
|
||||
|
||||
private final SshManager sshManager;
|
||||
private final RuntimeEventsPublisher runtimeEventsPublisher;
|
||||
|
||||
@Inject
|
||||
public SshKeysProvisioner(SshManager sshManager) {
|
||||
public SshKeysProvisioner(SshManager sshManager, RuntimeEventsPublisher runtimeEventsPublisher) {
|
||||
this.sshManager = sshManager;
|
||||
this.runtimeEventsPublisher = runtimeEventsPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -108,11 +120,35 @@ public class SshKeysProvisioner implements ConfigurationProvisioner<KubernetesEn
|
|||
List<SshPairImpl> vcsSshPairs = getVcsSshPairs(k8sEnv, identity);
|
||||
List<SshPairImpl> systemSshPairs = getSystemSshPairs(k8sEnv, identity);
|
||||
|
||||
List<SshPairImpl> all = new ArrayList<>(vcsSshPairs);
|
||||
all.addAll(systemSshPairs);
|
||||
List<SshPairImpl> allSshPairs = new ArrayList<>(vcsSshPairs);
|
||||
allSshPairs.addAll(systemSshPairs);
|
||||
|
||||
doProvisionSshKeys(all, k8sEnv, workspaceId);
|
||||
doProvisionVcsSshConfig(vcsSshPairs, k8sEnv, workspaceId);
|
||||
List<String> invalidSshKeyNames =
|
||||
allSshPairs
|
||||
.stream()
|
||||
.filter(keyPair -> !isValidSshKeyPair(keyPair))
|
||||
.map(SshPairImpl::getName)
|
||||
.collect(toList());
|
||||
|
||||
if (!invalidSshKeyNames.isEmpty()) {
|
||||
String message =
|
||||
format(
|
||||
Warnings.SSH_KEYS_WILL_NOT_BE_MOUNTED_MESSAGE,
|
||||
invalidSshKeyNames.toString(),
|
||||
identity.getWorkspaceId());
|
||||
LOG.warn(message);
|
||||
k8sEnv.addWarning(new WarningImpl(Warnings.SSH_KEYS_WILL_NOT_BE_MOUNTED, message));
|
||||
runtimeEventsPublisher.sendRuntimeLogEvent(message, ZonedDateTime.now().toString(), identity);
|
||||
}
|
||||
|
||||
doProvisionSshKeys(
|
||||
allSshPairs.stream().filter(this::isValidSshKeyPair).collect(toList()),
|
||||
k8sEnv,
|
||||
workspaceId);
|
||||
doProvisionVcsSshConfig(
|
||||
vcsSshPairs.stream().filter(this::isValidSshKeyPair).collect(toList()),
|
||||
k8sEnv,
|
||||
workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -298,6 +334,12 @@ public class SshKeysProvisioner implements ConfigurationProvisioner<KubernetesEn
|
|||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isValidSshKeyPair(SshPairImpl keyPair) {
|
||||
return isValidConfigMapKeyName(keyPair.getName())
|
||||
&& VALID_DOMAIN_PATTERN.matcher(keyPair.getName()).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ssh configuration entry which includes host, identity file location and Host Key
|
||||
* checking policy
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.namespace;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class KubernetesObjectUtilTest {
|
||||
@Test(dataProvider = "testNames")
|
||||
public void shouldTestisValidConfigMapKeyName(String nameToTest, boolean isValid) {
|
||||
assertEquals(KubernetesObjectUtil.isValidConfigMapKeyName(nameToTest), isValid);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] testNames() {
|
||||
return new Object[][] {
|
||||
new Object[] {"foo.bar", true},
|
||||
new Object[] {"https://foo.bar", false},
|
||||
new Object[] {"_fef_123-ah_*zz**", false},
|
||||
new Object[] {"_fef_123-ah_z.z", true},
|
||||
new Object[] {"a-b#-hello", false},
|
||||
new Object[] {"a---------b", true},
|
||||
new Object[] {"--ab--", true}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import static org.mockito.Mockito.times;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
|
|
@ -40,9 +41,11 @@ import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
|||
import org.eclipse.che.api.ssh.server.SshManager;
|
||||
import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment;
|
||||
import org.eclipse.che.workspace.infrastructure.kubernetes.util.RuntimeEventsPublisher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
|
@ -66,23 +69,25 @@ public class SshKeySecretProvisionerTest {
|
|||
|
||||
@Mock private Container container;
|
||||
|
||||
@Mock RuntimeEventsPublisher runtimeEventsPublisher;
|
||||
|
||||
private final String someUser = "someuser";
|
||||
|
||||
private SshKeysProvisioner sshKeysProvisioner;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() {
|
||||
when(runtimeIdentity.getOwnerId()).thenReturn(someUser);
|
||||
when(runtimeIdentity.getWorkspaceId()).thenReturn("wksp");
|
||||
lenient().when(runtimeIdentity.getOwnerId()).thenReturn(someUser);
|
||||
lenient().when(runtimeIdentity.getWorkspaceId()).thenReturn("wksp");
|
||||
k8sEnv = KubernetesEnvironment.builder().build();
|
||||
ObjectMeta podMeta = new ObjectMetaBuilder().withName("wksp").build();
|
||||
when(pod.getMetadata()).thenReturn(podMeta);
|
||||
when(pod.getSpec()).thenReturn(podSpec);
|
||||
when(podSpec.getVolumes()).thenReturn(new ArrayList<>());
|
||||
when(podSpec.getContainers()).thenReturn(Collections.singletonList(container));
|
||||
when(container.getVolumeMounts()).thenReturn(new ArrayList<>());
|
||||
lenient().when(pod.getMetadata()).thenReturn(podMeta);
|
||||
lenient().when(pod.getSpec()).thenReturn(podSpec);
|
||||
lenient().when(podSpec.getVolumes()).thenReturn(new ArrayList<>());
|
||||
lenient().when(podSpec.getContainers()).thenReturn(Collections.singletonList(container));
|
||||
lenient().when(container.getVolumeMounts()).thenReturn(new ArrayList<>());
|
||||
k8sEnv.addPod(pod);
|
||||
sshKeysProvisioner = new SshKeysProvisioner(sshManager);
|
||||
sshKeysProvisioner = new SshKeysProvisioner(sshManager, runtimeEventsPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -98,6 +103,64 @@ public class SshKeySecretProvisionerTest {
|
|||
assertEquals(k8sEnv.getSecrets().size(), 1);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "invalidNames")
|
||||
public void shouldNotMountKeysWithInvalidKeyNames(String invalidName) throws Exception {
|
||||
// given
|
||||
when(sshManager.getPairs(someUser, "vcs"))
|
||||
.thenReturn(
|
||||
ImmutableList.of(
|
||||
new SshPairImpl(someUser, "vcs", invalidName, "public", "private"),
|
||||
new SshPairImpl(someUser, "vcs", "gothub.mk", "public", "private"),
|
||||
new SshPairImpl(someUser, "vcs", "boombaket.barabaket.com", "public", "private")));
|
||||
// when
|
||||
sshKeysProvisioner.provision(k8sEnv, runtimeIdentity);
|
||||
// then
|
||||
Secret secret = k8sEnv.getSecrets().get("wksp-sshprivatekeys");
|
||||
assertNotNull(secret);
|
||||
assertEquals(secret.getData().size(), 2);
|
||||
assertTrue(secret.getData().containsKey("gothub.mk"));
|
||||
assertTrue(secret.getData().containsKey("boombaket.barabaket.com"));
|
||||
assertFalse(secret.getData().containsKey(invalidName));
|
||||
verify(runtimeEventsPublisher, times(1))
|
||||
.sendRuntimeLogEvent(anyString(), anyString(), eq(runtimeIdentity));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "invalidNames")
|
||||
public void shouldIgnoreInvalidKeyNames(String invalidName) throws Exception {
|
||||
assertFalse(
|
||||
sshKeysProvisioner.isValidSshKeyPair(
|
||||
new SshPairImpl(someUser, "vcs", invalidName, "public", "private")));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "validNames")
|
||||
public void shouldAcceptValidKeyNames(String validName) throws Exception {
|
||||
assertTrue(
|
||||
sshKeysProvisioner.isValidSshKeyPair(
|
||||
new SshPairImpl(someUser, "vcs", validName, "public", "private")));
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] invalidNames() {
|
||||
return new Object[][] {
|
||||
new Object[] {"https://foo.bar"},
|
||||
new Object[] {"_fef_123-ah_*zz**"},
|
||||
new Object[] {"_fef_123-ah_z.z"},
|
||||
new Object[] {"a-b#-hello"},
|
||||
new Object[] {"--ab--"}
|
||||
};
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] validNames() {
|
||||
return new Object[][] {
|
||||
new Object[] {"foo.bar"},
|
||||
new Object[] {"fef-123-ahzz"},
|
||||
new Object[] {"fef0123-ahz.z"},
|
||||
new Object[] {"a-b.hello"},
|
||||
new Object[] {"a--a----------b"}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addSshKeysConfigInPod() throws Exception {
|
||||
String keyName1 = UUID.randomUUID().toString();
|
||||
|
|
|
|||
Loading…
Reference in New Issue