diff --git a/docs/pom.xml b/docs/pom.xml
index 38eabee8..b41839ae 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -6,8 +6,10 @@
org.springframework.cloud
spring-cloud-config
- 1.3.2.BUILD-SNAPSHOT
+ 1.4.0.BUILD-SNAPSHOT
+ ..
+
pom
Spring Cloud Config Docs
Spring Cloud Docs
diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc
index 503bb6f0..55146702 100644
--- a/docs/src/main/asciidoc/spring-cloud-config.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-config.adoc
@@ -360,7 +360,77 @@ by the default credential provider chain. AWS EC2 instances may use
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html[IAM Roles for EC2 Instances].
Note: The aws-java-sdk-core jar is an optional dependency. If the aws-java-sdk-core jar is not on your
-classpath, then the AWS Code Commit credential provider will not be created regardless of the git server URI.
+classpath, then the AWS Code Commit credential provider will not be created regardless of the git server URI.
+
+===== Git SSH configuration using properties
+
+By default, Spring Cloud Config Server uses SSH configuration files such as `~/.ssh/known_hosts` and `/etc/ssh/ssh_config` when connecting to Git repositories using an SSH URI.
+In cloud environments such as Cloud Foundry, the local filesystem may be ephemeral or not easily accessible. For cases such as these, SSH configuration can be set using
+Java properties. In order to activate property based SSH configuration, the property `spring.cloud.config.server.git.ignoreLocalSshSettings` must be set to `true`.
+Example:
+
+[source,yaml]
+----
+ spring:
+ cloud:
+ config:
+ server:
+ git:
+ uri: git@gitserver.com:team/repo1.git
+ ignoreLocalSshSettings: true
+ hostKey: someHostKey
+ hostKeyAlgorithm: ssh-rsa
+ privateKey: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX
+ IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF
+ ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud
+ 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i
+ oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W
+ DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd
+ fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b
+ BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG
+ EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj
+ 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8
+ +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe
+ pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG
+ ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ
+ xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW
+ dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi
+ PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX
+ VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z
+ FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R
+ gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4
+ VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV
+ cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee
+ KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N
+ CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs
+ q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J
+ 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT
+ -----END RSA PRIVATE KEY-----
+
+----
+
+.SSH Configuration properties
+|===
+|Property Name |Remarks
+
+|*ignoreLocalSshSettings*
+|If true, use property based SSH config instead of file based
+
+|*privateKey*
+|Valid SSH private key. Must be set if `ignoreLocalSshSettings` is true and Git URI is SSH format
+
+|*hostKey*
+|Valid SSH host key. Must be set if `hostKeyAlgorithm` is also set
+
+|*hostKeyAlgorithm*
+|One of `ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384 ,ecdsa-sha2-nistp521`. Must be set if `hostKey` is also set
+
+|*strictHostKeyChecking*
+|`true` or `false`. If false, ignore errors with host key
+|===
+
===== Placeholders in Git Search Paths
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerAutoConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerAutoConfiguration.java
index b1dff1ca..8a7a4140 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerAutoConfiguration.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/ConfigServerAutoConfiguration.java
@@ -29,7 +29,7 @@ import org.springframework.context.annotation.Import;
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
- ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
+ ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class, TransportConfiguration.class })
public class ConfigServerAutoConfiguration {
}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/TransportConfiguration.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/TransportConfiguration.java
new file mode 100644
index 00000000..f5199cd1
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/TransportConfiguration.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.config;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.Session;
+import org.eclipse.jgit.api.TransportConfigCallback;
+import org.eclipse.jgit.transport.*;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.config.server.ssh.PropertyBasedSshSessionFactory;
+import org.springframework.cloud.config.server.ssh.SshUriProperties;
+import org.springframework.cloud.config.server.ssh.SshUriPropertyProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configure a callback to set up a property based SSH settings before running a transport command (such as clone or fetch)
+ *
+ * @author Ollie Hughes
+ */
+@Configuration
+@EnableConfigurationProperties(SshUriProperties.class)
+public class TransportConfiguration {
+
+ @ConditionalOnMissingBean(TransportConfigCallback.class)
+ @Bean
+ public TransportConfigCallback propertiesBasedSshTransportCallback(final SshUriProperties sshUriProperties) {
+ if(sshUriProperties.isIgnoreLocalSshSettings()) {
+ return new PropertiesBasedSshTransportConfigCallback(sshUriProperties);
+ }
+ else return new FileBasedSshTransportConfigCallback(sshUriProperties);
+ }
+
+ /**
+ * Configure JGit transport command to use a SSH session factory that is configured using properties defined
+ * in {@link SshUriProperties}
+ */
+ public static class PropertiesBasedSshTransportConfigCallback implements TransportConfigCallback {
+
+ private SshUriProperties sshUriProperties;
+
+ public PropertiesBasedSshTransportConfigCallback(SshUriProperties sshUriProperties) {
+ this.sshUriProperties = sshUriProperties;
+ }
+
+ public SshUriProperties getSshUriProperties() {
+ return sshUriProperties;
+ }
+
+ @Override
+ public void configure(Transport transport) {
+ SshTransport sshTransport = (SshTransport) transport;
+ sshTransport.setSshSessionFactory(
+ new PropertyBasedSshSessionFactory(
+ new SshUriPropertyProcessor(sshUriProperties).getSshKeysByHostname(), new JSch()));
+ }
+ }
+
+ /**
+ * Configure JGit transport command to use a default SSH session factory based on local machines SSH config.
+ * Allow strict host key checking to be set.
+ */
+ public static class FileBasedSshTransportConfigCallback implements TransportConfigCallback {
+
+ private SshUriProperties sshUriProperties;
+
+ public FileBasedSshTransportConfigCallback(SshUriProperties sshUriProperties) {
+ this.sshUriProperties = sshUriProperties;
+ }
+
+ public SshUriProperties getSshUriProperties() {
+ return sshUriProperties;
+ }
+
+ @Override
+ public void configure(Transport transport) {
+ SshSessionFactory.setInstance(new JschConfigSessionFactory() {
+ @Override
+ protected void configure(OpenSshConfig.Host hc, Session session) {
+ session.setConfig("StrictHostKeyChecking",
+ sshUriProperties.isStrictHostKeyChecking() ? "yes" : "no");
+ }
+ });
+ }
+ }
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java
index c28370de..8df63428 100644
--- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
@@ -43,9 +42,6 @@ import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
-import org.eclipse.jgit.transport.JschConfigSessionFactory;
-import org.eclipse.jgit.transport.OpenSshConfig.Host;
-import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.FileUtils;
@@ -56,8 +52,6 @@ import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
-import com.jcraft.jsch.Session;
-
import static org.springframework.util.StringUtils.hasText;
/**
@@ -81,8 +75,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
*/
private int timeout = 5;
- private boolean initialized;
-
/**
* Flag to indicate that the repository should be cloned on startup (not on
* demand). Generally leads to slower startup but faster first query.
@@ -174,7 +166,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(getUri() != null, "You need to configure a uri for the git repository");
- initialize();
if (this.cloneOnStart) {
initClonedRepository();
}
@@ -184,7 +175,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
* Get the working directory ready.
*/
public String refresh(String label) {
- initialize();
Git git = null;
try {
git = createGitClient();
@@ -422,18 +412,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
}
}
- private void initialize() {
- if (!this.initialized) {
- SshSessionFactory.setInstance(new JschConfigSessionFactory() {
- @Override
- protected void configure(Host hc, Session session) {
- session.setConfig("StrictHostKeyChecking", isStrictHostKeyChecking() ? "yes" : "no");
- }
- });
- this.initialized = true;
- }
- }
-
private void configureCommand(TransportCommand, ?> command) {
command.setTimeout(this.timeout);
if (this.transportConfigCallback != null) {
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactory.java
new file mode 100644
index 00000000..93493a43
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.config.server.ssh;
+
+import com.jcraft.jsch.HostKey;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import org.eclipse.jgit.transport.JschConfigSessionFactory;
+import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.FS;
+
+import java.util.Map;
+
+
+/**
+ * In a cloud environment local SSH config files such as `.known_hosts` may not be suitable for providing
+ * configuration settings due to ephemeral filesystems. This flag enables SSH config to be provided as application
+ * properties
+ * @author William Tran
+ * @author Ollie Hughes
+ */
+public class PropertyBasedSshSessionFactory extends JschConfigSessionFactory {
+
+ private final Map sshKeysByHostname;
+ private final JSch jSch;
+
+ public PropertyBasedSshSessionFactory(Map sshKeysByHostname, JSch jSch) {
+ this.sshKeysByHostname = sshKeysByHostname;
+ this.jSch = jSch;
+ }
+
+ @Override
+ protected void configure(Host hc, Session session) {
+ SshUriProperties sshProperties = sshKeysByHostname.get(hc.getHostName());
+ String hostKeyAlgorithm = sshProperties.getHostKeyAlgorithm();
+ if (hostKeyAlgorithm != null) {
+ session.setConfig("server_host_key", hostKeyAlgorithm);
+ }
+ if (sshProperties.getHostKey() == null || !sshProperties.isStrictHostKeyChecking()) {
+ session.setConfig("StrictHostKeyChecking", "no");
+ } else {
+ session.setConfig("StrictHostKeyChecking", "yes");
+ }
+ }
+
+ @Override
+ protected Session createSession(Host hc, String user, String host, int port, FS fs) throws JSchException {
+ if (sshKeysByHostname.containsKey(host)) {
+ SshUriProperties sshUriProperties = sshKeysByHostname.get(host);
+ jSch.addIdentity(host, sshUriProperties.getPrivateKey().getBytes(), null, null);
+ if (sshUriProperties.getHostKey() != null) {
+ HostKey hostkey = new HostKey(host, Base64.decode(sshUriProperties.getHostKey()));
+ jSch.getHostKeyRepository().add(hostkey, null);
+ }
+ return jSch.getSession(user, host, port);
+ }
+ throw new JSchException("no keys configured for hostname " + host);
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshPropertyValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshPropertyValidator.java
new file mode 100644
index 00000000..011effe7
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshPropertyValidator.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.ssh;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+
+import static java.lang.String.format;
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * Validate SSH related properties
+ *
+ * @author Ollie Hughes
+ */
+@Component
+@EnableConfigurationProperties(SshUriProperties.class)
+public class SshPropertyValidator {
+
+ private final SshUriProperties sshUriProperties;
+ private final JSch jsch = new JSch();
+ private static final Set VALID_HOST_KEY_ALGORITHMS = new LinkedHashSet<>(Arrays.asList(
+ "ssh-dss","ssh-rsa","ecdsa-sha2-nistp256","ecdsa-sha2-nistp384","ecdsa-sha2-nistp521"));
+ private static final String GIT_PROPERTY_PREFIX = "spring.cloud.config.server.git.";
+
+ @Autowired
+ public SshPropertyValidator(SshUriProperties sshUriProperties) {
+ this.sshUriProperties = sshUriProperties;
+ }
+
+ static boolean isSshUri(Object uri) {
+ return uri != null && (uri.toString().startsWith("ssh") || uri.toString().startsWith("git"));
+ }
+
+ @PostConstruct
+ public void validateSshConfigurationProperties() {
+ List allRepoProperties = new ArrayList<>();
+ allRepoProperties.add(sshUriProperties);
+ Map repos = sshUriProperties.getRepos();
+ if (repos != null) {
+ allRepoProperties.addAll(repos.values());
+ }
+ for (SshUriProperties repoProperties : allRepoProperties) {
+ if(isSshUri(repoProperties.getUri()) && sshUriProperties.isIgnoreLocalSshSettings()){
+ validatePrivateKeyPresent();
+ validatePrivateKeyFormat();
+ validateAlgorithmSpecifiedWhenHostKeySet();
+ validateHostKeySpecifiedWhenAlgorithmSet();
+ validateHostKeyAlgorithmSupported();
+ }
+ }
+ }
+
+ protected void validatePrivateKeyFormat() {
+ try {
+ KeyPair.load(jsch, sshUriProperties.getPrivateKey().getBytes(), null);
+ } catch (JSchException e) {
+ throw new IllegalStateException(format("Property '%sprivateKey' contains an invalid value", GIT_PROPERTY_PREFIX));
+ }
+ }
+
+ protected void validateHostKeyAlgorithmSupported() {
+ if (hasText(sshUriProperties.getHostKeyAlgorithm())) {
+ Assert.state(VALID_HOST_KEY_ALGORITHMS.contains(sshUriProperties.getHostKeyAlgorithm()),
+ format("Property '%shostKeyAlgorithm' must be one of %s", GIT_PROPERTY_PREFIX, VALID_HOST_KEY_ALGORITHMS));
+ }
+ }
+
+ protected void validatePrivateKeyPresent() {
+ Assert.state(sshUriProperties.getPrivateKey() != null,
+ format("Property '%sprivateKey' must be set when '%signoreLocalSshSettings' is set to 'true'", GIT_PROPERTY_PREFIX, GIT_PROPERTY_PREFIX));
+ }
+
+ protected void validateHostKeySpecifiedWhenAlgorithmSet() {
+ if (hasText(sshUriProperties.getHostKeyAlgorithm())) {
+ Assert.state(hasText(sshUriProperties.getHostKey()),
+ format("Property '%shostKey' must be set when 'hostKeyAlgorithm' is specified", GIT_PROPERTY_PREFIX));
+ }
+ }
+
+ protected void validateAlgorithmSpecifiedWhenHostKeySet() {
+ if (hasText(sshUriProperties.getHostKey())) {
+ Assert.state(hasText(sshUriProperties.getHostKeyAlgorithm()),
+ format("Property '%shostKeyAlgorithm' must be set when 'hostKey' is specified", GIT_PROPERTY_PREFIX));
+ }
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java
new file mode 100644
index 00000000..8d036d5a
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.config.server.ssh;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Data container for property based SSH config
+ *
+ * @author Ollie Hughes
+ */
+@ConfigurationProperties("spring.cloud.config.server.git")
+public class SshUriProperties {
+ private String uri;
+ private String hostKeyAlgorithm;
+ private String hostKey;
+ private String privateKey;
+ private String username;
+ private String password;
+ private boolean ignoreLocalSshSettings;
+ private boolean strictHostKeyChecking = true;
+
+ private Map repos = new HashMap<>();
+
+ public SshUriProperties(String uri, String hostKeyAlgorithm, String hostKey, String privateKey, String username, String password, boolean ignoreLocalSshSettings, boolean strictHostKeyChecking, Map repos) {
+ this.uri = uri;
+ this.hostKeyAlgorithm = hostKeyAlgorithm;
+ this.hostKey = hostKey;
+ this.privateKey = privateKey;
+ this.username = username;
+ this.password = password;
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ this.repos = repos;
+ }
+
+ public SshUriProperties() {
+ }
+
+ public static SshUriPropertiesBuilder builder() {
+ return new SshUriPropertiesBuilder();
+ }
+
+ public boolean isSshUri() {
+ return uri != null && !uri.startsWith("http");
+ }
+
+ public String getHostname() {
+ if (getUri() == null) {
+ return null;
+ }
+
+ if (getUri().matches("^[a-z]+://.*")) {
+ return UriComponentsBuilder.fromUriString(uri).build().getHost();
+ }
+ else if (getUri().indexOf('@') < getUri().indexOf(':')) {
+ return getUri().substring(getUri().indexOf('@') + 1, uri.indexOf(':'));
+ }
+ else if (getUri().startsWith("ssh:") && getUri().indexOf('@') > 0) {
+ String postAt = getUri().substring(getUri().indexOf('@') + 1);
+ return postAt.substring(0, postAt.indexOf(":"));
+ }
+ else return null;
+ }
+
+ public String getUri() {
+ return this.uri;
+ }
+
+ public String getHostKeyAlgorithm() {
+ return this.hostKeyAlgorithm;
+ }
+
+ public String getHostKey() {
+ return this.hostKey;
+ }
+
+ public String getPrivateKey() {
+ return this.privateKey;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ public String getPassword() {
+ return this.password;
+ }
+
+ public boolean isIgnoreLocalSshSettings() {
+ return this.ignoreLocalSshSettings;
+ }
+
+ public boolean isStrictHostKeyChecking() {
+ return this.strictHostKeyChecking;
+ }
+
+ public Map getRepos() {
+ return this.repos;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public void setHostKeyAlgorithm(String hostKeyAlgorithm) {
+ this.hostKeyAlgorithm = hostKeyAlgorithm;
+ }
+
+ public void setHostKey(String hostKey) {
+ this.hostKey = hostKey;
+ }
+
+ public void setPrivateKey(String privateKey) {
+ this.privateKey = privateKey;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public void setIgnoreLocalSshSettings(boolean ignoreLocalSshSettings) {
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ }
+
+ public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ }
+
+ public void setRepos(Map repos) {
+ this.repos = repos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, hostKeyAlgorithm, hostKey, privateKey, username, password, ignoreLocalSshSettings, strictHostKeyChecking);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final SshUriProperties other = (SshUriProperties) obj;
+ return Objects.equals(this.uri, other.uri)
+ && Objects.equals(this.hostKeyAlgorithm, other.hostKeyAlgorithm)
+ && Objects.equals(this.hostKey, other.hostKey)
+ && Objects.equals(this.privateKey, other.privateKey)
+ && Objects.equals(this.username, other.username)
+ && Objects.equals(this.password, other.password)
+ && Objects.equals(this.ignoreLocalSshSettings, other.ignoreLocalSshSettings)
+ && Objects.equals(this.strictHostKeyChecking, other.strictHostKeyChecking);
+ }
+
+ public String toString() {
+ return "org.springframework.cloud.config.server.ssh.SshUriProperties(uri=" + this.getUri() + " hostKeyAlgorithm=" + this.getHostKeyAlgorithm() + ", hostKey=" + this.getHostKey() + ", privateKey=" + this.getPrivateKey() + ", username=" + this.getUsername() + ", password=" + this.getPassword() + ", ignoreLocalSshSettings=" + this.isIgnoreLocalSshSettings() + ", strictHostKeyChecking=" + this.isStrictHostKeyChecking() + ", repos=" + this.getRepos() + ")";
+ }
+
+ public static class SshUriPropertiesBuilder {
+ private String uri;
+ private String hostKeyAlgorithm;
+ private String hostKey;
+ private String privateKey;
+ private String username;
+ private String password;
+ private boolean ignoreLocalSshSettings;
+ private boolean strictHostKeyChecking = true;
+ private Map repos;
+
+ SshUriPropertiesBuilder() {
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder uri(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder hostKeyAlgorithm(String hostKeyAlgorithm) {
+ this.hostKeyAlgorithm = hostKeyAlgorithm;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder hostKey(String hostKey) {
+ this.hostKey = hostKey;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder privateKey(String privateKey) {
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder ignoreLocalSshSettings(boolean ignoreLocalSshSettings) {
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder strictHostKeyChecking(boolean strictHostKeyChecking) {
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ return this;
+ }
+
+ public SshUriProperties.SshUriPropertiesBuilder repos(Map repos) {
+ this.repos = repos;
+ return this;
+ }
+
+ public SshUriProperties build() {
+ return new SshUriProperties(uri, hostKeyAlgorithm, hostKey, privateKey, username, password, ignoreLocalSshSettings, strictHostKeyChecking, repos);
+ }
+
+ public String toString() {
+ return "org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriPropertiesBuilder(uri=" + this.uri + "hostKeyAlgorithm=" + this.hostKeyAlgorithm + ", hostKey=" + this.hostKey + ", privateKey=" + this.privateKey + ", username=" + this.username + ", password=" + this.password + ", ignoreLocalSshSettings=" + this.ignoreLocalSshSettings + ", strictHostKeyChecking=" + this.strictHostKeyChecking + ", repos=" + this.repos + ")";
+ }
+ }
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessor.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessor.java
new file mode 100644
index 00000000..791dcda9
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.cloud.config.server.ssh;
+
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.springframework.cloud.config.server.ssh.SshPropertyValidator.isSshUri;
+
+/**
+ * Check if Git repo properties refer to an SSH based transport then filter and extract the properties
+ * @author William Tran
+ * @author Ollie Hughes
+ */
+public class SshUriPropertyProcessor {
+
+ private final SshUriProperties sshUriProperties;
+
+ public SshUriPropertyProcessor(SshUriProperties sshUriProperties) {
+ this.sshUriProperties = sshUriProperties;
+ }
+
+ public Map getSshKeysByHostname() {
+ return extractNestedProperties(sshUriProperties);
+ }
+
+ private Map extractNestedProperties(SshUriProperties uriProperties) {
+ Map sshUriPropertyMap = new HashMap<>();
+ String parentUri = uriProperties.getUri();
+ if (isSshUri(parentUri) && getHostname(parentUri) != null) {
+ sshUriPropertyMap.put(getHostname(parentUri), uriProperties);
+ }
+ Map repos = uriProperties.getRepos();
+ if(repos != null) {
+ for (SshUriProperties repoProperties : repos.values()) {
+ String repoUri = repoProperties.getUri();
+ if (isSshUri(repoUri) && getHostname(repoUri) != null) {
+ sshUriPropertyMap.put(getHostname(repoUri), repoProperties);
+ }
+ }
+ }
+ return sshUriPropertyMap;
+ }
+
+ private String getHostname(String uri) {
+ if (uri == null) {
+ return null;
+ }
+ else if (uri.matches("^[a-z]+://.*")) {
+ return UriComponentsBuilder.fromUriString(uri).build().getHost();
+ }
+ else if (uri.indexOf('@') < uri.indexOf(':')) {
+ return uri.substring(uri.indexOf('@') + 1, uri.indexOf(':'));
+ }
+ else if (uri.startsWith("ssh:") && uri.indexOf('@') > 0) {
+ String postAt = uri.substring(uri.indexOf('@') + 1);
+ return postAt.substring(0, postAt.indexOf(":"));
+ }
+ else return null;
+ }
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/TransportConfigurationIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/TransportConfigurationIntegrationTests.java
new file mode 100644
index 00000000..b22b3166
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/TransportConfigurationIntegrationTests.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server;
+
+import com.jcraft.jsch.Session;
+import org.eclipse.jgit.api.TransportConfigCallback;
+import org.eclipse.jgit.transport.JschConfigSessionFactory;
+import org.eclipse.jgit.transport.OpenSshConfig;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.util.FS;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.config.server.config.TransportConfiguration;
+import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
+import org.springframework.cloud.config.server.ssh.SshPropertyValidator;
+import org.springframework.cloud.config.server.ssh.SshUriProperties;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Integration tests for property based SSH config support
+ * @author Ollie Hughes
+ */
+public class TransportConfigurationIntegrationTests {
+
+ @RunWith(SpringRunner.class)
+ @SpringBootTest(classes = {ConfigServerApplication.class, TransportConfiguration.class, SshPropertyValidator.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {
+ "spring.config.name:ssh/ssh-private-key-block",})
+ @ActiveProfiles({"test", "git"})
+ public static class PropertyBasedCallbackTest {
+
+ @Autowired
+ private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
+
+ @Test
+ public void propertyBasedTransportCallbackIsConfigured() throws Exception {
+ TransportConfigCallback transportConfigCallback = jGitEnvironmentRepository.getTransportConfigCallback();
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.PropertiesBasedSshTransportConfigCallback.class)));
+ }
+ }
+
+ @RunWith(SpringRunner.class)
+ @SpringBootTest(classes = {ConfigServerApplication.class, TransportConfiguration.class, SshPropertyValidator.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {
+ "spring.config.name:ssh/ssh-private-key-newline"
+ })
+ @ActiveProfiles({"test", "git"})
+ public static class PrivateKeyPropertyWithLineBreaks {
+
+ @Autowired
+ private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
+
+ @Test
+ public void privateKeyPropertyWithLineBreaks() throws Exception {
+ TransportConfigCallback transportConfigCallback = jGitEnvironmentRepository.getTransportConfigCallback();
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.PropertiesBasedSshTransportConfigCallback.class)));
+
+ TransportConfiguration.PropertiesBasedSshTransportConfigCallback configCallback =
+ (TransportConfiguration.PropertiesBasedSshTransportConfigCallback) transportConfigCallback;
+ assertThat(configCallback.getSshUriProperties().getPrivateKey(), is(equalTo(TestProperties.TEST_PRIVATE_KEY_1)));
+ }
+ }
+
+
+ @RunWith(SpringRunner.class)
+ @SpringBootTest(classes = {ConfigServerApplication.class, TransportConfiguration.class, SshPropertyValidator.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {
+ "spring.config.name:ssh/ssh-nested-settings"
+ })
+ @ActiveProfiles({"test", "git"})
+ public static class SshPropertiesWithinNestedRepo {
+
+ @Autowired
+ private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
+
+ @Test
+ public void sshPropertiesWithinNestedRepo() throws Exception {
+ TransportConfigCallback transportConfigCallback = jGitEnvironmentRepository.getTransportConfigCallback();
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.PropertiesBasedSshTransportConfigCallback.class)));
+
+ TransportConfiguration.PropertiesBasedSshTransportConfigCallback configCallback =
+ (TransportConfiguration.PropertiesBasedSshTransportConfigCallback) transportConfigCallback;
+ SshUriProperties sshUriProperties = configCallback.getSshUriProperties();
+ assertThat(sshUriProperties.getPrivateKey(), is(equalTo(TestProperties.TEST_PRIVATE_KEY_1)));
+
+ assertThat(sshUriProperties.getRepos().get("repo1"), is(notNullValue()));
+ assertThat(sshUriProperties.getRepos().get("repo1").getPrivateKey(), is(equalTo(TestProperties.TEST_PRIVATE_KEY_2)));
+ }
+ }
+
+ @RunWith(SpringRunner.class)
+ @SpringBootTest(classes = {ConfigServerApplication.class, TransportConfiguration.class, SshPropertyValidator.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ properties = {
+ "spring.cloud.config.server.git.uri=git@gitserver.com:team/repo.git",
+ "spring.cloud.config.server.git.ignoreLocalSshSettings=false",})
+ @ActiveProfiles({"test", "git"})
+ public static class FileBasedCallbackTest {
+
+ @Autowired
+ private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
+
+ @Test
+ public void fileBasedTransportCallbackIsConfigured() throws Exception {
+ TransportConfigCallback transportConfigCallback = jGitEnvironmentRepository.getTransportConfigCallback();
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.FileBasedSshTransportConfigCallback.class)));
+ }
+
+
+ @Test
+ public void strictHostKeyCheckShouldCheck() throws Exception {
+ String uri = "git+ssh://git@somegitserver/somegitrepo";
+ SshSessionFactory.setInstance(null);
+ jGitEnvironmentRepository.setUri(uri);
+ jGitEnvironmentRepository.setBasedir(new File("./mybasedir"));
+ assertTrue(jGitEnvironmentRepository.isStrictHostKeyChecking());
+ jGitEnvironmentRepository.setCloneOnStart(true);
+ try {
+ // this will throw but we don't care about connecting.
+ jGitEnvironmentRepository.afterPropertiesSet();
+ } catch (Exception e) {
+ final OpenSshConfig.Host hc = OpenSshConfig.get(FS.detect()).lookup("github.com");
+ JschConfigSessionFactory factory = (JschConfigSessionFactory) SshSessionFactory.getInstance();
+ // There's no public method that can be used to inspect the ssh
+ // configuration, so we'll reflect
+ // the configure method to allow us to check that the config
+ // property is set as expected.
+ Method configure = factory.getClass().getDeclaredMethod("configure", OpenSshConfig.Host.class,
+ Session.class);
+ configure.setAccessible(true);
+ Session session = mock(Session.class);
+ ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(String.class);
+ configure.invoke(factory, hc, session);
+ verify(session).setConfig(keyCaptor.capture(), valueCaptor.capture());
+ configure.setAccessible(false);
+ assertTrue("yes".equals(valueCaptor.getValue()));
+ }
+ }
+ }
+
+ private static class TestProperties {
+ private static final String TEST_PRIVATE_KEY_1 = "-----BEGIN RSA PRIVATE KEY-----\n" +
+ "MIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\n" +
+ "yyxp38YcuZG9cUDBAxby+K2TKmwHaC1R61QTwbPuCRdIPrDwRz+FLoegm3iDLCmn\n" +
+ "uP6rjZDneYsqfU1KSdrOwIbCnONfDdvYL/vnZC/o8DDMlk5Orw2SfHkT3pq0o8km\n" +
+ "ayBwN4Sf3bpyWTY0oZcmNeSCCoIdE59k8Pa7/t9bwY9caLj05C3DEsjucc7Ei/Eq\n" +
+ "TOyGyobtXwaya5CqKLUHes74Poz1aEP/yVFdUud91uezd8ZK1P1t5/ZKA3R6aHir\n" +
+ "+diDJ2/GQ2tD511FW46yw+EtBUJTO6ADVv4UnQIDAQABAoIBAF+5qwEfX82QfKFk\n" +
+ "jfADqFFexUDtl1biFKeJrpC2MKhn01wByH9uejrhFKQqW8UaKroLthyZ34DWIyGt\n" +
+ "lDnHGv0gSVF2LuAdNLdobJGt49e4+c9yD61vxzm97Eh8mRs08SM2q/VlF35E2fmI\n" +
+ "xdWusUImYzd8L9e+6tRd8zZl9UhG5vR5XIstKqxC6S0g79aAt0hasE4Gw1FKOf2V\n" +
+ "4mlL15atjQSKCPdOicuyc4zpjAtU1A9AfF51iG8oOUuJebPW8tCftfOQxaeGFgMG\n" +
+ "7M9aai1KzXR6M5IBAKEv31yBvz/SHTneP7oZXNLeC1GIR420PKybmeZdNK8BbEAu\n" +
+ "3reKgm0CgYEA03Sx8JoF5UBsIvFPpP1fjSlTgKryM5EJR6KQtj5e4YfyxccJepN8\n" +
+ "q4MrqDfNKleG/a1acEtDMhBNovU7Usp2QIP7zpAeioHBOhmE5WSieZGc3icOGWWq\n" +
+ "mRkdulSONruqWKv76ZoluxftekE03bDhZDNlcCgmrslEKB/ufHd2oc8CgYEAxPFa\n" +
+ "lKOdSeiYFV5CtvO8Ro8em6rGpSsVz4qkPxbeBqUDCb9KXHhq6YrhRxOIfQJKfT7M\n" +
+ "ZFCn8ArJXKgOGu+KsvwIErFHF9g2jJMG4DOUTpkQgi2yveihFxcmz/AltyVXgrnv\n" +
+ "ZWQbAerH77pdKKhNivLGgEv72GYawdYjYNjemdMCgYA2kEMmMahZyrDcp2YEzfit\n" +
+ "BT/t0K6kzcUWPgWXcSqsiZcEn+J7RbmCzFskkhmX1nQX23adyV3yejB+X0dKisHO\n" +
+ "zf/ZAmlPFkJVCqa3RquCMSfIT02dEhXeYZPBM/Zqeyxuqxpa4hLgX0FBLbhFiFHw\n" +
+ "uC5xrXql2XuD2xF//peXEwKBgQC+pa28Cg7vRxxCQzduB9CQtWc55j3aEjVQ7bNF\n" +
+ "54sS/5ZLT0Ra8677WZfuyDfuW9NkHvCZg4Ku2qJG8eCFrrGjxlrCTZ62tHVJ6+JS\n" +
+ "E1xUIdRbUIWhVZrr0VufG6hG/P0T7Y6Tpi6G0pKtvMkF3LcD9TS3adboix8H2ZXx\n" +
+ "4L7MRQKBgQC0OO3qqNXOjIVYWOoqXLybOY/Wqu9lxCAgGyCYaMcstnBI7W0MZTBr\n" +
+ "/syluvGsaFc1sE7MMGOOzKi1tF4YvDmSnzA/R1nmaPguuD9fOA+w7Pwkv5vLvuJq\n" +
+ "2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\n" +
+ "-----END RSA PRIVATE KEY-----";
+
+ private static final String TEST_PRIVATE_KEY_2 = "-----BEGIN RSA PRIVATE KEY-----\n" +
+ "MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX\n" +
+ "IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF\n" +
+ "ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud\n" +
+ "1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i\n" +
+ "oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W\n" +
+ "DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd\n" +
+ "fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b\n" +
+ "BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG\n" +
+ "EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj\n" +
+ "5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8\n" +
+ "+AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe\n" +
+ "pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG\n" +
+ "ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ\n" +
+ "xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW\n" +
+ "dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi\n" +
+ "PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX\n" +
+ "VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z\n" +
+ "FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R\n" +
+ "gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4\n" +
+ "VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV\n" +
+ "cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee\n" +
+ "KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N\n" +
+ "CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs\n" +
+ "q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J\n" +
+ "69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT\n" +
+ "-----END RSA PRIVATE KEY-----\n";
+ }
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java
index ef105132..0d67d4ac 100644
--- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java
@@ -33,7 +33,6 @@ import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -60,19 +59,14 @@ import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchResult;
-import org.eclipse.jgit.transport.JschConfigSessionFactory;
-import org.eclipse.jgit.transport.OpenSshConfig;
-import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.mockito.ArgumentCaptor;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.support.AwsCodeCommitCredentialProvider;
import org.springframework.cloud.config.server.support.GitCredentialsProviderFactory;
@@ -677,38 +671,6 @@ public class JGitEnvironmentRepositoryTests {
}
- @Test
- public void strictHostKeyCheckShouldCheck() throws Exception {
- String uri = "git+ssh://git@somegitserver/somegitrepo";
- SshSessionFactory.setInstance(null);
- JGitEnvironmentRepository envRepository = new JGitEnvironmentRepository(this.environment);
- envRepository.setUri(uri);
- envRepository.setBasedir(new File("./mybasedir"));
- assertTrue(envRepository.isStrictHostKeyChecking());
- envRepository.setCloneOnStart(true);
- try {
- // this will throw but we don't care about connecting.
- envRepository.afterPropertiesSet();
- } catch (Exception e) {
- final OpenSshConfig.Host hc = OpenSshConfig.get(FS.detect()).lookup("github.com");
- JschConfigSessionFactory factory = (JschConfigSessionFactory) SshSessionFactory.getInstance();
- // There's no public method that can be used to inspect the ssh
- // configuration, so we'll reflect
- // the configure method to allow us to check that the config
- // property is set as expected.
- Method configure = factory.getClass().getDeclaredMethod("configure", OpenSshConfig.Host.class,
- Session.class);
- configure.setAccessible(true);
- Session session = mock(Session.class);
- ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(String.class);
- configure.invoke(factory, hc, session);
- verify(session).setConfig(keyCaptor.capture(), valueCaptor.capture());
- configure.setAccessible(false);
- assertTrue("yes".equals(valueCaptor.getValue()));
- }
- }
-
@Test
public void shouldPrintStacktraceIfDebugEnabled() throws Exception {
final Log mockLogger = mock(Log.class);
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java
new file mode 100644
index 00000000..fbcb3461
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.ssh;
+
+import com.jcraft.jsch.*;
+import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for property based SSH config processor
+ * @author William Tran
+ * @author Ollie Hughes
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class PropertyBasedSshSessionFactoryTest {
+
+ private static final String HOST_KEY = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMzCa0AcNbahUFjFYJHIilhJOhKFHuDOOuY+/HqV9kALftitwNYo6dQ+tC9IK5JVZCZfqKfDWVMxspcPDf9eMoE=";
+ private static final String HOST_KEY_ALGORITHM = "ecdsa-sha2-nistp256";
+ private static final String PRIVATE_KEY = getResourceAsString("/ssh/key");
+ private PropertyBasedSshSessionFactory factory;
+ @Mock
+ private Host hc;
+ @Mock
+ private Session session;
+ @Mock
+ private JSch jSch;
+ @Mock
+ private HostKeyRepository hostKeyRepository;
+
+ @Test
+ public void strictHostKeyCheckingIsOptional() {
+ SshUriProperties sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("ssh://gitlab.example.local:3322/somerepo.git")
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.configure(hc, session);
+
+ verify(session).setConfig("StrictHostKeyChecking", "no");
+ verifyNoMoreInteractions(session);
+ }
+
+ @Test
+ public void strictHostKeyCheckingIsUsed() {
+ SshUriProperties sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("ssh://gitlab.example.local:3322/somerepo.git")
+ .hostKey(HOST_KEY)
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.configure(hc, session);
+
+ verify(session).setConfig("StrictHostKeyChecking", "yes");
+ verifyNoMoreInteractions(session);
+ }
+
+ @Test
+ public void hostKeyAlgorithmIsSpecified() {
+ SshUriProperties sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("ssh://gitlab.example.local:3322/somerepo.git")
+ .hostKeyAlgorithm(HOST_KEY_ALGORITHM)
+ .hostKey(HOST_KEY)
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.configure(hc, session);
+ verify(session).setConfig("server_host_key", HOST_KEY_ALGORITHM);
+ verify(session).setConfig("StrictHostKeyChecking", "yes");
+ verifyNoMoreInteractions(session);
+ }
+
+ @Test
+ public void privateKeyIsUsed() throws Exception {
+ SshUriProperties sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("git@gitlab.example.local:someorg/somerepo.git")
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.createSession(hc, null, sshKey.getHostname(), 22, null);
+ verify(jSch).addIdentity("gitlab.example.local", PRIVATE_KEY.getBytes(), null, null);
+ }
+
+ @Test
+ public void hostKeyIsUsed() throws Exception {
+ SshUriProperties sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("git@gitlab.example.local:someorg/somerepo.git")
+ .hostKey(HOST_KEY)
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.createSession(hc, null, sshKey.getHostname(), 22, null);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(HostKey.class);
+ verify(hostKeyRepository).add(captor.capture(), any(UserInfo.class));
+ HostKey hostKey = captor.getValue();
+ Assert.assertEquals("gitlab.example.local", hostKey.getHost());
+ Assert.assertEquals(HOST_KEY, hostKey.getKey());
+ }
+
+ private void setupSessionFactory(SshUriProperties sshKey) {
+ Map sshKeysByHostname = new HashMap<>();
+ sshKeysByHostname.put(sshKey.getHostname(), sshKey);
+ factory = new PropertyBasedSshSessionFactory(sshKeysByHostname, jSch) ;
+ when(hc.getHostName()).thenReturn(sshKey.getHostname());
+ when(jSch.getHostKeyRepository()).thenReturn(hostKeyRepository);
+ }
+
+ public static String getResourceAsString(String path) {
+ try {
+ Resource resource = new ClassPathResource(path);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
+ StringBuilder builder = new StringBuilder();
+ String line = "";
+ while ((line = br.readLine()) != null) {
+ builder.append(line).append('\n');
+ }
+ return builder.toString();
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java
new file mode 100644
index 00000000..3fe69c98
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.ssh;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Unit tests for property based SSH config validators
+ *
+ * @author Ollie Hughes
+ */
+public class SshPropertyValidatorTest {
+
+ private static final String SSH_URI = "git@gitserver.com:team/repo1.git";
+
+ private static final String VALID_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" +
+ "MIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\n" +
+ "yyxp38YcuZG9cUDBAxby+K2TKmwHaC1R61QTwbPuCRdIPrDwRz+FLoegm3iDLCmn\n" +
+ "uP6rjZDneYsqfU1KSdrOwIbCnONfDdvYL/vnZC/o8DDMlk5Orw2SfHkT3pq0o8km\n" +
+ "ayBwN4Sf3bpyWTY0oZcmNeSCCoIdE59k8Pa7/t9bwY9caLj05C3DEsjucc7Ei/Eq\n" +
+ "TOyGyobtXwaya5CqKLUHes74Poz1aEP/yVFdUud91uezd8ZK1P1t5/ZKA3R6aHir\n" +
+ "+diDJ2/GQ2tD511FW46yw+EtBUJTO6ADVv4UnQIDAQABAoIBAF+5qwEfX82QfKFk\n" +
+ "jfADqFFexUDtl1biFKeJrpC2MKhn01wByH9uejrhFKQqW8UaKroLthyZ34DWIyGt\n" +
+ "lDnHGv0gSVF2LuAdNLdobJGt49e4+c9yD61vxzm97Eh8mRs08SM2q/VlF35E2fmI\n" +
+ "xdWusUImYzd8L9e+6tRd8zZl9UhG5vR5XIstKqxC6S0g79aAt0hasE4Gw1FKOf2V\n" +
+ "4mlL15atjQSKCPdOicuyc4zpjAtU1A9AfF51iG8oOUuJebPW8tCftfOQxaeGFgMG\n" +
+ "7M9aai1KzXR6M5IBAKEv31yBvz/SHTneP7oZXNLeC1GIR420PKybmeZdNK8BbEAu\n" +
+ "3reKgm0CgYEA03Sx8JoF5UBsIvFPpP1fjSlTgKryM5EJR6KQtj5e4YfyxccJepN8\n" +
+ "q4MrqDfNKleG/a1acEtDMhBNovU7Usp2QIP7zpAeioHBOhmE5WSieZGc3icOGWWq\n" +
+ "mRkdulSONruqWKv76ZoluxftekE03bDhZDNlcCgmrslEKB/ufHd2oc8CgYEAxPFa\n" +
+ "lKOdSeiYFV5CtvO8Ro8em6rGpSsVz4qkPxbeBqUDCb9KXHhq6YrhRxOIfQJKfT7M\n" +
+ "ZFCn8ArJXKgOGu+KsvwIErFHF9g2jJMG4DOUTpkQgi2yveihFxcmz/AltyVXgrnv\n" +
+ "ZWQbAerH77pdKKhNivLGgEv72GYawdYjYNjemdMCgYA2kEMmMahZyrDcp2YEzfit\n" +
+ "BT/t0K6kzcUWPgWXcSqsiZcEn+J7RbmCzFskkhmX1nQX23adyV3yejB+X0dKisHO\n" +
+ "zf/ZAmlPFkJVCqa3RquCMSfIT02dEhXeYZPBM/Zqeyxuqxpa4hLgX0FBLbhFiFHw\n" +
+ "uC5xrXql2XuD2xF//peXEwKBgQC+pa28Cg7vRxxCQzduB9CQtWc55j3aEjVQ7bNF\n" +
+ "54sS/5ZLT0Ra8677WZfuyDfuW9NkHvCZg4Ku2qJG8eCFrrGjxlrCTZ62tHVJ6+JS\n" +
+ "E1xUIdRbUIWhVZrr0VufG6hG/P0T7Y6Tpi6G0pKtvMkF3LcD9TS3adboix8H2ZXx\n" +
+ "4L7MRQKBgQC0OO3qqNXOjIVYWOoqXLybOY/Wqu9lxCAgGyCYaMcstnBI7W0MZTBr\n" +
+ "/syluvGsaFc1sE7MMGOOzKi1tF4YvDmSnzA/R1nmaPguuD9fOA+w7Pwkv5vLvuJq\n" +
+ "2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\n" +
+ "-----END RSA PRIVATE KEY-----";
+
+ private static final String VALID_HOST_KEY = "AAAAB3NzaC1yc2EAAAADAQABAAABAQDg6/W/5cbk/npvzpae7ZEa54F4rkwh2V3NiuqVZ5hWr+8O4/6SmrS7yBvRHAFeAJNb0LOCjE/7tjd1fqUx+QU1ATCtwkOhuwG8Ubzkx23mMZlrwEvx7XEfBoLN7Lw9fXjWDtTTgFB1AxCQ2pGGiNG0QCwyA4HViDHVU+ibwkRlzuDJG0tnp5Qpo3DXkHwFNdqWNfVrIZ6q2xbyeoJjKjnR215T0ehmuWFmKqG+uMNe/LQ6IOiK0F5+gr7rgPxNLAYYqyhraAnBeHn5gapsSzYJmFpoAHWvN7OUwHcJ88D9qUkKi4VKxYiuK69u3z825Xj2cLTfj9JiHCfV8cTo9GL";
+
+ @Test
+ public void supportedParametersSuccesful() throws Exception {
+ SshUriProperties validSettings = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey(VALID_PRIVATE_KEY)
+ .hostKey(VALID_HOST_KEY)
+ .hostKeyAlgorithm("ssh-rsa")
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = spy(new SshPropertyValidator(validSettings));
+ sshPropertyValidator.validateSshConfigurationProperties();
+ verify(sshPropertyValidator, times(1)).validatePrivateKeyFormat();
+ verify(sshPropertyValidator, times(1)).validateAlgorithmSpecifiedWhenHostKeySet();
+ verify(sshPropertyValidator, times(1)).validatePrivateKeyPresent();
+ verify(sshPropertyValidator, times(1)).validateHostKeyAlgorithmSupported();
+ verify(sshPropertyValidator, times(1)).validateHostKeySpecifiedWhenAlgorithmSet();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invalidPrivateKeyFails() throws Exception {
+
+ SshUriProperties invalidKey = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = new SshPropertyValidator(invalidKey);
+ sshPropertyValidator.validateSshConfigurationProperties();
+
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void missingPrivateKeyFails() throws Exception {
+
+ SshUriProperties missingKey = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = new SshPropertyValidator(missingKey);
+ sshPropertyValidator.validateSshConfigurationProperties();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void hostKeyWithMissingAlgoFails() throws Exception {
+
+ SshUriProperties missingAlgo = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .hostKey("some_host")
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = new SshPropertyValidator(missingAlgo);
+ sshPropertyValidator.validateSshConfigurationProperties();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void algoWithMissingHostKeyFails() throws Exception {
+
+ SshUriProperties missingHostKey = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .hostKeyAlgorithm("some_host_algo")
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = new SshPropertyValidator(missingHostKey);
+ sshPropertyValidator.validateSshConfigurationProperties();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void unsupportedAlgoFails() throws Exception {
+
+ SshUriProperties unsupportedAlgo = SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .hostKey("some_host_key")
+ .hostKeyAlgorithm("unsupported")
+ .build();
+
+ SshPropertyValidator sshPropertyValidator = new SshPropertyValidator(unsupportedAlgo);
+ sshPropertyValidator.validateSshConfigurationProperties();
+ }
+
+ @Test
+ public void validatorNotRunIfIgnoreLocalSettingsFalse() throws Exception {
+
+ SshUriProperties useLocal = (SshUriProperties.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(false)
+ .privateKey("invalid_key")
+ .build());
+
+ SshPropertyValidator sshPropertyValidator = spy(new SshPropertyValidator(useLocal));
+ sshPropertyValidator.validateSshConfigurationProperties();
+ verify(sshPropertyValidator, times(0)).validatePrivateKeyFormat();
+ verify(sshPropertyValidator, times(0)).validateAlgorithmSpecifiedWhenHostKeySet();
+ verify(sshPropertyValidator, times(0)).validatePrivateKeyPresent();
+ verify(sshPropertyValidator, times(0)).validateHostKeyAlgorithmSupported();
+ verify(sshPropertyValidator, times(0)).validateHostKeySpecifiedWhenAlgorithmSet();
+ }
+
+ @Test
+ public void validatorNotRunIfHttpsUri() throws Exception {
+
+ SshUriProperties httpsUri = (SshUriProperties.builder()
+ .uri("https://somerepo.com/team/project.git")
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .build());
+
+ SshPropertyValidator sshPropertyValidator = spy(new SshPropertyValidator(httpsUri));
+ sshPropertyValidator.validateSshConfigurationProperties();
+ verify(sshPropertyValidator, times(0)).validatePrivateKeyFormat();
+ verify(sshPropertyValidator, times(0)).validateAlgorithmSpecifiedWhenHostKeySet();
+ verify(sshPropertyValidator, times(0)).validatePrivateKeyPresent();
+ verify(sshPropertyValidator, times(0)).validateHostKeyAlgorithmSupported();
+ verify(sshPropertyValidator, times(0)).validateHostKeySpecifiedWhenAlgorithmSet();
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessorTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessorTest.java
new file mode 100644
index 00000000..cc3457b0
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessorTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.server.ssh;
+
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for property based SSH config processor
+ * @author William Tran
+ * @author Ollie Hughes
+ */
+public class SshUriPropertyProcessorTest {
+
+ private static final String PRIVATE_KEY1 = "privateKey";
+ private static final String HOST_KEY1 = "hostKey";
+ private static final String ALGO1 = "ssh-rsa";
+ private static final String URI1 = "git@gitlab.test.local:wtran/my-repo";
+ private static final String HOST1 = "gitlab.test.local";
+ private static final String PRIVATE_KEY2 = "privateKey2";
+ private static final String URI2 = "git@gitlab2.test.local:wtran/my-repo";
+ private static final String HOST2 = "gitlab2.test.local";
+
+ @After
+ public void cleanup() {
+ SshSessionFactory.setInstance(null);
+ }
+
+ @Test
+ public void testSingleSshUriProperties() {
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(mainRepoPropertiesFixture());
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+
+ assertThat(sshKeysByHostname.values(), hasSize(1));
+
+ SshUriProperties sshKey = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey);
+ }
+
+ @Test
+ public void testMultipleSshUriPropertiess() {
+ SshUriProperties sshUriProperties = mainRepoPropertiesFixture();
+ addRepoProperties(sshUriProperties, SshUriProperties.builder()
+ .uri(URI2)
+ .privateKey(PRIVATE_KEY2)
+ .build());
+
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(sshUriProperties);
+
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+
+
+ SshUriProperties sshKey = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey);
+
+ sshKey = sshKeysByHostname.get(HOST2);
+
+ assertThat(sshKeysByHostname.values(), hasSize(2));
+
+ assertThat(sshKey.getHostname(), is(equalTo(HOST2)));
+
+ assertThat(sshKey.getHostKeyAlgorithm(), is(nullValue()));
+
+ assertThat(sshKey.getHostKey(), is(nullValue()));
+
+ assertThat(sshKey.getPrivateKey(), is(equalTo(PRIVATE_KEY2)));
+ }
+
+ @Test
+ public void testSameHostnameDifferentKeysFirstOneWins() {
+ SshUriProperties sshUriProperties = mainRepoPropertiesFixture();
+ addRepoProperties(sshUriProperties, SshUriProperties.builder().uri(URI1)
+ .privateKey(PRIVATE_KEY1)
+ .hostKey(HOST_KEY1)
+ .hostKeyAlgorithm(ALGO1)
+ .build());
+
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(sshUriProperties);
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+
+ assertThat(sshKeysByHostname.values(), hasSize(1));
+
+ SshUriProperties sshKey = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey);
+ }
+
+ @Test
+ public void testNoSshUriPropertiess() {
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(new SshUriProperties());
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+ assertThat(sshKeysByHostname.values(), hasSize(0));
+ }
+
+ private SshUriProperties mainRepoPropertiesFixture() {
+
+ return SshUriProperties.builder()
+ .uri(URI1)
+ .hostKeyAlgorithm(ALGO1)
+ .hostKey(HOST_KEY1)
+ .privateKey(PRIVATE_KEY1)
+ .build();
+ }
+
+ private void addRepoProperties(SshUriProperties mainRepoProperties, SshUriProperties repoProperties) {
+ Map repos = new HashMap<>();
+ repos.put("repo2", repoProperties);
+ mainRepoProperties.setRepos(repos);
+ }
+
+ private void assertMainRepo(SshUriProperties sshKey) {
+ assertThat(sshKey.getHostname(), is(equalTo(HOST1)));
+ assertThat(sshKey.getHostKeyAlgorithm(), is(equalTo(ALGO1)));
+ assertThat(sshKey.getHostKey(), is(equalTo(HOST_KEY1)));
+ assertThat(sshKey.getPrivateKey(), is(equalTo(PRIVATE_KEY1)));
+ }
+
+}
diff --git a/spring-cloud-config-server/src/test/resources/ssh/key b/spring-cloud-config-server/src/test/resources/ssh/key
new file mode 100644
index 00000000..f1586c20
--- /dev/null
+++ b/spring-cloud-config-server/src/test/resources/ssh/key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwQTXywKTzJEYXQ/LOR/hDciu4tJ1Av8zQQqi4ekL2hnOUzA0
+XecMmoYp31j3aWbT8RFNaUxFObfG7uGUWG21NlciqN7NtRkzTceygwBl25J9UEIo
+iSHkS2arMhSIQ/B5TIFmL8fucsMUzqZ0LFwd2NR5k+BL5Ip/g6Gp6NpY2lNlxG3W
+EaI/ee73vp8fJd9xge1o5Y/WFjCdP2TVfBuMTzswtjooakhQMg16c3YxcpK3Utbc
+J4gaPDAGSd9eu5YuxSnTyC4s+FMPsKF3zEu+kTb5iutiCvO4lFdvlhURN2WBecRt
+Am4nrMHEmLp3Yy440UTSOAzUUdxlfyltxvdOnQIDAQABAoIBABpQtbb4z/u44HB3
+ocZVAVLmxGkqNkfFUVCRyakxI6uFjyqqTMlsobxDhFvt/jVBjq20JTCw9FMtKhMR
+cC8qLeWNtaz+S19gYy08h6ryl/B6pVkW2/3uP/jDs51tTTKuC2uRzMPA51KAJEsU
+SIxvtqYEOdseXlp3Q3FHzilv82M4dKQCUXO/BbYV1jtgGHsJIJIcJhb8JIuXGScq
+krm+GrzXpejF8FjpEFJ+VRbh/wjbMqUD5/9KimtJ9tXK5MKaxZ2aerYVONrPIbRH
+Ij2tKS2o+cEBL9b0E2GHSGQyzs+g4+eTmsf56O8MWAoh5uib3pKXi84wxWi1ETQd
++NIGuUECgYEA9VZuTCbJVEriqptZKw8BNn7xIlXkmABEWAmIlo8U4NodjP/WIyIB
+FtwYS4jUxfJhLdgs8mzb7O+EY6F+lusQBcAViRfk6a0VL9qrzYAY21d1X0hRtt4t
+NKixjQ1hESao/CQDOonsYIqolXlDsvYxDX0tWXkVjiXPVk71oPtaF80CgYEAyWhT
+j1v6XtFQT4KAQ10pQEbXBet9ae4M6WALAghdROMY60zX6XdXNGugycy3T9s9q712
+gxglhhhQO+RpEtaVOXvOzZv5aczrDqPwYOX1jJbIaOT4Iv2szjI518akRTef+ANW
+t+mETUBf+qYKhW91lfSmi5PUPrWBs5z8j9qcohECgYEA1XgfAKO/ClkeYNRLAexM
+Yd6Scg3KmXeh//hLoLVetUcT71usN2WfDQT1HsQwXwxHAlzWvxl5QvAlgYtDWo2t
+o1M/acogpDK1/K2IT4bmGfQ/bGjAsR0guHEQDGtXylSZNMshBA5+XoJHrceV4GHW
+zKXej7uLxeOgRLjvgOMzwQECgYAWSjJ9OiuyVK3zrritO+E12OExKYe+snbBWTy+
+o9hWqF94ow9+KhP/nC22R6696sCfgh9ckM6OUaCHkFbEm/T14KrZfHqU5/XXr/MJ
+dkm/nmxdl4GStcprI4ndRIGnm2lb1FrDjddziT1WCww9CkHUL7hC2EB0FDhffW9C
+KGqPUQKBgQCd85DH751a6EKN8dyKUyf3sP8BraTsc26DFk3q+vfhzSyJF4wxIVWa
+kmjx6kfGTu0+ED0UXWi5LnMSvne4rGU1j3rQyHs71t6JLVcFl8j5i8g/n91FA87C
+EIni0JgBvgbDNlM04mMFE6K0u5+Rt3Nx/yywW/w9nQUrzNio7MP+8w==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/spring-cloud-config-server/src/test/resources/ssh/ssh-nested-settings.yml b/spring-cloud-config-server/src/test/resources/ssh/ssh-nested-settings.yml
new file mode 100644
index 00000000..28b09e86
--- /dev/null
+++ b/spring-cloud-config-server/src/test/resources/ssh/ssh-nested-settings.yml
@@ -0,0 +1,41 @@
+spring:
+ cloud:
+ config:
+ server:
+ git:
+ uri: git@gitserver.com:team/repo1.git
+ ignoreLocalSshSettings: true
+ privateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\nyyxp38YcuZG9cUDBAxby+K2TKmwHaC1R61QTwbPuCRdIPrDwRz+FLoegm3iDLCmn\nuP6rjZDneYsqfU1KSdrOwIbCnONfDdvYL/vnZC/o8DDMlk5Orw2SfHkT3pq0o8km\nayBwN4Sf3bpyWTY0oZcmNeSCCoIdE59k8Pa7/t9bwY9caLj05C3DEsjucc7Ei/Eq\nTOyGyobtXwaya5CqKLUHes74Poz1aEP/yVFdUud91uezd8ZK1P1t5/ZKA3R6aHir\n+diDJ2/GQ2tD511FW46yw+EtBUJTO6ADVv4UnQIDAQABAoIBAF+5qwEfX82QfKFk\njfADqFFexUDtl1biFKeJrpC2MKhn01wByH9uejrhFKQqW8UaKroLthyZ34DWIyGt\nlDnHGv0gSVF2LuAdNLdobJGt49e4+c9yD61vxzm97Eh8mRs08SM2q/VlF35E2fmI\nxdWusUImYzd8L9e+6tRd8zZl9UhG5vR5XIstKqxC6S0g79aAt0hasE4Gw1FKOf2V\n4mlL15atjQSKCPdOicuyc4zpjAtU1A9AfF51iG8oOUuJebPW8tCftfOQxaeGFgMG\n7M9aai1KzXR6M5IBAKEv31yBvz/SHTneP7oZXNLeC1GIR420PKybmeZdNK8BbEAu\n3reKgm0CgYEA03Sx8JoF5UBsIvFPpP1fjSlTgKryM5EJR6KQtj5e4YfyxccJepN8\nq4MrqDfNKleG/a1acEtDMhBNovU7Usp2QIP7zpAeioHBOhmE5WSieZGc3icOGWWq\nmRkdulSONruqWKv76ZoluxftekE03bDhZDNlcCgmrslEKB/ufHd2oc8CgYEAxPFa\nlKOdSeiYFV5CtvO8Ro8em6rGpSsVz4qkPxbeBqUDCb9KXHhq6YrhRxOIfQJKfT7M\nZFCn8ArJXKgOGu+KsvwIErFHF9g2jJMG4DOUTpkQgi2yveihFxcmz/AltyVXgrnv\nZWQbAerH77pdKKhNivLGgEv72GYawdYjYNjemdMCgYA2kEMmMahZyrDcp2YEzfit\nBT/t0K6kzcUWPgWXcSqsiZcEn+J7RbmCzFskkhmX1nQX23adyV3yejB+X0dKisHO\nzf/ZAmlPFkJVCqa3RquCMSfIT02dEhXeYZPBM/Zqeyxuqxpa4hLgX0FBLbhFiFHw\nuC5xrXql2XuD2xF//peXEwKBgQC+pa28Cg7vRxxCQzduB9CQtWc55j3aEjVQ7bNF\n54sS/5ZLT0Ra8677WZfuyDfuW9NkHvCZg4Ku2qJG8eCFrrGjxlrCTZ62tHVJ6+JS\nE1xUIdRbUIWhVZrr0VufG6hG/P0T7Y6Tpi6G0pKtvMkF3LcD9TS3adboix8H2ZXx\n4L7MRQKBgQC0OO3qqNXOjIVYWOoqXLybOY/Wqu9lxCAgGyCYaMcstnBI7W0MZTBr\n/syluvGsaFc1sE7MMGOOzKi1tF4YvDmSnzA/R1nmaPguuD9fOA+w7Pwkv5vLvuJq\n2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\n-----END RSA PRIVATE KEY-----"
+ repos:
+ repo1:
+ uri: git@gitserver.com:team/repo2.git
+ hostKey: someHostKey
+ hostKeyAlgorithm: ssh-rsa
+ privateKey: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX
+ IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF
+ ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud
+ 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i
+ oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W
+ DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd
+ fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b
+ BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG
+ EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj
+ 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8
+ +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe
+ pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG
+ ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ
+ xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW
+ dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi
+ PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX
+ VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z
+ FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R
+ gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4
+ VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV
+ cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee
+ KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N
+ CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs
+ q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J
+ 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT
+ -----END RSA PRIVATE KEY-----
diff --git a/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-block.yml b/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-block.yml
new file mode 100644
index 00000000..3db045f3
--- /dev/null
+++ b/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-block.yml
@@ -0,0 +1,35 @@
+spring:
+ cloud:
+ config:
+ server:
+ git:
+ uri: git@gitserver.com:team/repo.git
+ ignoreLocalSshSettings: true
+ privateKey: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI
+ yyxp38YcuZG9cUDBAxby+K2TKmwHaC1Wf1QTwbPuCRdIPrDwRz+FLoegm3iDLCmn
+ uP6rjZDneYsqfU1sSdrOwIbCnONfDdvYL/vnZC/o8DDMlk5Orw2SfHkT3pq0o8km
+ ayBwN4Sf3bpyWTY0oZcmNeSCCoIdE59k8Pa7/t9bwY9caLj05C3DEsjucc7Ei/Eq
+ TOyGyobtXwaya5CqKLUHes74Poz1aEP/yVFdUud91uezd8ZK1P1t5/ZKA3R6aHir
+ +diDJ2/GQ2tD511FW46yw+EtBUJTO6ADVv4UnQIDAQABAoIBAF+5qwEfX82QfKFk
+ jfADqFFexUDtl1biFKeJrpC2MKhn01wByH9uejrhFKQqW8UaKroLthyZ34DWIyGt
+ lDnHGv0gSVF2LuAdNLdobJGt49e4+c9yD61vxzm97Eh8mRs08SM2q/VlF35E2fmI
+ xdWusUImYzd8L9e+6tRd8zZl9UhG5vR5XIstKqxC6S0g79aAt0hasE4Gw1FKOf2V
+ 4mlL15atjQSKCPdOicuyc4zpjAtU1A9AfF51iG8oOUuJebPW8tCftfOQxaeGFgMG
+ 7M9aai1KzXR6M5IBAKEv31yBvz/SHTneP7oZXNLeC1GIR420PKybmeZdNK8BbEAu
+ 3reKgm0CgYEA03Sx8JgF5UBsIvFPpP1fjSlTgKryM5EJR6KQtj5e4YfyxccJepN8
+ q4MrqDfNKleG/a1acEtDMhBNovU7Usp2QIP7zpAeioHBOhmE5WSieZGc3icOGWWq
+ mRkdulSONruqWKv76ZoluxftekE03bDhZDNlcCgmrslEKB/ufHd2oc8CgYEAxPFa
+ lKOdSeiYFV5CtvO8Ro8em6rGpSsVz4qkPxbeBqUDCb9KXHhq6YrhRxOIfQJKfT7M
+ ZFCn8ArJXKgOGu+KsvwIErFHF9g2jJMG4DOUTpkQgi2yveihFxcmz/AltyVXgrnv
+ ZWQbAerH77pdKKhNivLGgEv72GYawdYjYNjemdMCgYA2kEMmMahZyrDcp2YEzfit
+ BT/t0K6kzcUWPgWXcSqsiZcEn+J7RbmCzFskkhmX1nQX23adyV3yejB+X0dKisHO
+ zf/ZAmlPFkJVCqa3RquCMSfIT02dEhXeYZPBM/Zqeyxuqxpa4hLgX0FBLbhFiFHw
+ uC5xrXql2XuD2xF//peXEwKBgQC+pa28Cg7vRxxCQzduB9CQtWc55j3aEjVQ7bNF
+ 54sS/5ZLT0Ra8677WZfuyDfuW9NkHvCZg4Ku2qJG8eCFrrGjxlrCTZ62tHVJ6+JS
+ E1xUIdRbUIWhVZrr0VufG6hG/P0T7Y6Tpi6G0pKtvMkF3LcD9TS3adboix8H2ZXx
+ 4L7MRQKBgQC0OO3qqNXOjIVYWOoqXLybOY/Wqu9lxCAgGyCYaMcstnBI7W0MZTBr
+ /syluvGsaFc1sE7MMGOOzKi1tF4YvDmSnzA/R1nmaPguuD9fOA+w7Pwkv5vLvuJq
+ 2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==
+ -----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-newline.yml b/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-newline.yml
new file mode 100644
index 00000000..399ba87a
--- /dev/null
+++ b/spring-cloud-config-server/src/test/resources/ssh/ssh-private-key-newline.yml
@@ -0,0 +1,10 @@
+spring:
+ cloud:
+ config:
+ server:
+ git:
+ uri: git@gitserver.com:team/repo.git
+ ignoreLocalSshSettings: true
+ privateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\nyyxp38YcuZG9cUDBAxby+K2TKmwHaC1R61QTwbPuCRdIPrDwRz+FLoegm3iDLCmn\nuP6rjZDneYsqfU1KSdrOwIbCnONfDdvYL/vnZC/o8DDMlk5Orw2SfHkT3pq0o8km\nayBwN4Sf3bpyWTY0oZcmNeSCCoIdE59k8Pa7/t9bwY9caLj05C3DEsjucc7Ei/Eq\nTOyGyobtXwaya5CqKLUHes74Poz1aEP/yVFdUud91uezd8ZK1P1t5/ZKA3R6aHir\n+diDJ2/GQ2tD511FW46yw+EtBUJTO6ADVv4UnQIDAQABAoIBAF+5qwEfX82QfKFk\njfADqFFexUDtl1biFKeJrpC2MKhn01wByH9uejrhFKQqW8UaKroLthyZ34DWIyGt\nlDnHGv0gSVF2LuAdNLdobJGt49e4+c9yD61vxzm97Eh8mRs08SM2q/VlF35E2fmI\nxdWusUImYzd8L9e+6tRd8zZl9UhG5vR5XIstKqxC6S0g79aAt0hasE4Gw1FKOf2V\n4mlL15atjQSKCPdOicuyc4zpjAtU1A9AfF51iG8oOUuJebPW8tCftfOQxaeGFgMG\n7M9aai1KzXR6M5IBAKEv31yBvz/SHTneP7oZXNLeC1GIR420PKybmeZdNK8BbEAu\n3reKgm0CgYEA03Sx8JoF5UBsIvFPpP1fjSlTgKryM5EJR6KQtj5e4YfyxccJepN8\nq4MrqDfNKleG/a1acEtDMhBNovU7Usp2QIP7zpAeioHBOhmE5WSieZGc3icOGWWq\nmRkdulSONruqWKv76ZoluxftekE03bDhZDNlcCgmrslEKB/ufHd2oc8CgYEAxPFa\nlKOdSeiYFV5CtvO8Ro8em6rGpSsVz4qkPxbeBqUDCb9KXHhq6YrhRxOIfQJKfT7M\nZFCn8ArJXKgOGu+KsvwIErFHF9g2jJMG4DOUTpkQgi2yveihFxcmz/AltyVXgrnv\nZWQbAerH77pdKKhNivLGgEv72GYawdYjYNjemdMCgYA2kEMmMahZyrDcp2YEzfit\nBT/t0K6kzcUWPgWXcSqsiZcEn+J7RbmCzFskkhmX1nQX23adyV3yejB+X0dKisHO\nzf/ZAmlPFkJVCqa3RquCMSfIT02dEhXeYZPBM/Zqeyxuqxpa4hLgX0FBLbhFiFHw\nuC5xrXql2XuD2xF//peXEwKBgQC+pa28Cg7vRxxCQzduB9CQtWc55j3aEjVQ7bNF\n54sS/5ZLT0Ra8677WZfuyDfuW9NkHvCZg4Ku2qJG8eCFrrGjxlrCTZ62tHVJ6+JS\nE1xUIdRbUIWhVZrr0VufG6hG/P0T7Y6Tpi6G0pKtvMkF3LcD9TS3adboix8H2ZXx\n4L7MRQKBgQC0OO3qqNXOjIVYWOoqXLybOY/Wqu9lxCAgGyCYaMcstnBI7W0MZTBr\n/syluvGsaFc1sE7MMGOOzKi1tF4YvDmSnzA/R1nmaPguuD9fOA+w7Pwkv5vLvuJq\n2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\n-----END RSA PRIVATE KEY-----"
+ hostKey: somekey
+ hostKeyAlgorithm: ssh-rsa
\ No newline at end of file