diff --git a/docs/pom.xml b/docs/pom.xml
index 38eabee8..abc036ed 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -7,7 +7,9 @@
org.springframework.cloud
spring-cloud-config
1.3.2.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..f4ab6f8a 100644
--- a/docs/src/main/asciidoc/spring-cloud-config.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-config.adoc
@@ -151,8 +151,8 @@ This repository implementation maps the `{label}` parameter of the
HTTP resource to a git label (commit id, branch name or tag). If the
git branch or tag name contains a slash ("/") then the label in the
HTTP URL should be specified with the special string "(\_)" instead (to
-avoid ambiguity with other URL paths). For example, if the label is
-`foo/bar`, replacing the slash would result in a label that looks like
+avoid ambiguity with other URL paths). For example, if the label is
+`foo/bar`, replacing the slash would result in a label that looks like
`foo(_)bar`. Be careful with the brackets in
the URL if you are using a command line client like curl (e.g. escape
them from the shell with quotes '').
@@ -351,7 +351,7 @@ https://git-codecommit.${AWS_REGION}.amazonaws.com/${repopath}.
If you provide a username and password with an AWS CodeCommit URI, then these must be
the http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html[AWS accessKeyId and secretAccessKey]
to be used to access the repository. If you do not specify a username and password,
-then the accessKeyId and secretAccessKey will be retrieved using the
+then the accessKeyId and secretAccessKey will be retrieved using the
http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html[AWS Default Credential Provider Chain].
If your Git URI matches the CodeCommit URI pattern (above) then you must provide
@@ -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, the JGit library used by 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. Must be set at as `spring.cloud.config.server.git.ignoreLocalSshSettings`, *not* inside a repository definition.
+
+|*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..a8d3721c
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/config/TransportConfiguration.java
@@ -0,0 +1,103 @@
+/*
+ * 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) {
+ if (transport instanceof SshTransport) {
+ 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 f895ebca..f71b0ad4 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
@@ -16,6 +16,8 @@
package org.springframework.cloud.config.server.environment;
+import static org.springframework.util.StringUtils.hasText;
+
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
@@ -58,8 +60,6 @@ import org.springframework.util.StringUtils;
import com.jcraft.jsch.Session;
-import static org.springframework.util.StringUtils.hasText;
-
/**
* An {@link EnvironmentRepository} backed by a single git repository.
*
@@ -81,8 +81,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.
@@ -108,6 +106,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
* changes and take from remote repository.
*/
private boolean forcePull;
+ private boolean initialized;
public JGitEnvironmentRepository(ConfigurableEnvironment environment) {
super(environment);
@@ -187,7 +186,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
* Get the working directory ready.
*/
public String refresh(String label) {
- initialize();
Git git = null;
try {
git = createGitClient();
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/BaseSshUriProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/BaseSshUriProperties.java
new file mode 100644
index 00000000..4a81e8da
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/BaseSshUriProperties.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 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.validation.annotation.Validated;
+
+/**
+ * @author Dave Syer
+ *
+ */
+@Validated
+@PrivateKeyIsValid
+@HostKeyAndAlgoBothExist
+@HostKeyAlgoSupported
+public class BaseSshUriProperties {
+
+ private String privateKey;
+ private String uri;
+ private String hostKeyAlgorithm;
+ private String hostKey;
+ private boolean ignoreLocalSshSettings;
+ private boolean strictHostKeyChecking = true;
+
+ public BaseSshUriProperties(String uri, String hostKeyAlgorithm, String hostKey, String privateKey, boolean ignoreLocalSshSettings, boolean strictHostKeyChecking) {
+ this.uri = uri;
+ this.hostKeyAlgorithm = hostKeyAlgorithm;
+ this.hostKey = hostKey;
+ this.privateKey = privateKey;
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ }
+
+ public BaseSshUriProperties() {
+ }
+
+ 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 boolean isIgnoreLocalSshSettings() {
+ return this.ignoreLocalSshSettings;
+ }
+
+ public boolean isStrictHostKeyChecking() {
+ return this.strictHostKeyChecking;
+ }
+
+ 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 setIgnoreLocalSshSettings(boolean ignoreLocalSshSettings) {
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ }
+
+ public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ }
+
+ public String toString() {
+ return "org.springframework.cloud.config.server.ssh.SshUriProperties(uri=" + this.getUri() + " hostKeyAlgorithm=" + this.getHostKeyAlgorithm() + ", hostKey=" + this.getHostKey() + ", privateKey=" + this.getPrivateKey() + ", ignoreLocalSshSettings=" + this.isIgnoreLocalSshSettings() + ", strictHostKeyChecking=" + this.isStrictHostKeyChecking() + ")";
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupported.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupported.java
new file mode 100644
index 00000000..68fae7d0
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupported.java
@@ -0,0 +1,39 @@
+/*
+ * 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.validation.annotation.Validated;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Beans annotated with {@link HostKeyAlgoSupported} and {@link Validated} will have the constraints applied.
+ *
+ * @author Ollie Hughes
+ **/
+@Constraint(validatedBy = HostKeyAlgoSupportedValidator.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HostKeyAlgoSupported {
+ String message() default "{HostKeyAlgoSupported.message}";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupportedValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupportedValidator.java
new file mode 100644
index 00000000..d75d2e48
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAlgoSupportedValidator.java
@@ -0,0 +1,77 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.springframework.validation.annotation.Validated;
+
+import static java.lang.String.format;
+import static org.springframework.cloud.config.server.ssh.SshPropertyValidator.isSshUri;
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * JSR-303 Cross Field validator that ensures that a {@link SshUriProperties} bean for the constraints:
+ * - If host key algo is supported
+ *
+ * Beans annotated with {@link HostKeyAlgoSupported} and {@link Validated} will have the constraints applied.
+ *
+ * @author Ollie Hughes
+ */
+public class HostKeyAlgoSupportedValidator implements ConstraintValidator {
+ private static final String GIT_PROPERTY_PREFIX = "spring.cloud.config.server.git.";
+ private final SshPropertyValidator sshPropertyValidator = new SshPropertyValidator();
+ 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"));
+
+ @Override
+ public void initialize(HostKeyAlgoSupported constrainAnnotation) {
+ //No special initialization of validator required
+ }
+
+ @Override
+ public boolean isValid(SshUriProperties sshUriProperties, ConstraintValidatorContext context) {
+ context.disableDefaultConstraintViolation();
+ Set validationResults = new HashSet<>();
+ List extractedProperties = sshPropertyValidator.extractRepoProperties(sshUriProperties);
+
+ for (SshUri extractedProperty : extractedProperties) {
+ if (sshUriProperties.isIgnoreLocalSshSettings() && isSshUri(extractedProperty.getUri())) {
+ validationResults.add(isHostKeySpecifiedWhenAlgorithmSet(extractedProperty, context));
+ }
+ }
+ return !validationResults.contains(false);
+ }
+
+ private boolean isHostKeySpecifiedWhenAlgorithmSet(SshUri sshUriProperties, ConstraintValidatorContext context) {
+ if (hasText(sshUriProperties.getHostKeyAlgorithm())
+ && !VALID_HOST_KEY_ALGORITHMS.contains(sshUriProperties.getHostKeyAlgorithm())) {
+
+ context.buildConstraintViolationWithTemplate(
+ format("Property '%shostKeyAlgorithm' must be one of %s", GIT_PROPERTY_PREFIX, VALID_HOST_KEY_ALGORITHMS))
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExist.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExist.java
new file mode 100644
index 00000000..b0464526
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExist.java
@@ -0,0 +1,38 @@
+/*
+ * 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.validation.annotation.Validated;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Beans annotated with {@link HostKeyAndAlgoBothExist} and {@link Validated} will have the constraints applied.
+ * @author Ollie Hughes
+ */
+@Constraint(validatedBy = HostKeyAndAlgoBothExistValidator.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HostKeyAndAlgoBothExist {
+ String message() default "{HostKeyAndAlgoBothExist.message}";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExistValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExistValidator.java
new file mode 100644
index 00000000..c8c3d417
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/HostKeyAndAlgoBothExistValidator.java
@@ -0,0 +1,84 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.springframework.validation.annotation.Validated;
+
+import static java.lang.String.format;
+import static org.springframework.cloud.config.server.ssh.SshPropertyValidator.isSshUri;
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * JSR-303 Cross Field validator that ensures that a {@link SshUriProperties} bean for the constraints:
+ * - If host key is set then host key algo must also be set
+ * - If host key algo is set then host key must also be set
+ *
+ * Beans annotated with {@link HostKeyAndAlgoBothExist} and {@link Validated} will have the constraints applied.
+ *
+ * @author Ollie Hughes
+ */
+public class HostKeyAndAlgoBothExistValidator implements ConstraintValidator {
+ private static final String GIT_PROPERTY_PREFIX = "spring.cloud.config.server.git.";
+ private final SshPropertyValidator sshPropertyValidator = new SshPropertyValidator();
+
+ @Override
+ public void initialize(HostKeyAndAlgoBothExist constrainAnnotation) {
+ //No special initialization of validator required
+ }
+
+ @Override
+ public boolean isValid(SshUriProperties sshUriProperties, ConstraintValidatorContext context) {
+ Set validationResults = new HashSet<>();
+ List extractedProperties = sshPropertyValidator.extractRepoProperties(sshUriProperties);
+
+ for (SshUri extractedProperty : extractedProperties) {
+ if (sshUriProperties.isIgnoreLocalSshSettings() && isSshUri(extractedProperty.getUri())) {
+ validationResults.add(
+ isAlgorithmSpecifiedWhenHostKeySet(extractedProperty, context)
+ && isHostKeySpecifiedWhenAlgorithmSet(extractedProperty, context));
+ }
+ }
+ return !validationResults.contains(false);
+ }
+
+ private boolean isHostKeySpecifiedWhenAlgorithmSet(SshUri sshUriProperties, ConstraintValidatorContext context) {
+ if (hasText(sshUriProperties.getHostKeyAlgorithm()) && !hasText(sshUriProperties.getHostKey())) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(
+ format("Property '%shostKey' must be set when '%shostKeyAlgorithm' is specified", GIT_PROPERTY_PREFIX, GIT_PROPERTY_PREFIX))
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isAlgorithmSpecifiedWhenHostKeySet(SshUri sshUriProperties, ConstraintValidatorContext context) {
+ if (hasText(sshUriProperties.getHostKey()) && !hasText(sshUriProperties.getHostKeyAlgorithm())) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate(
+ format("Property '%shostKeyAlgorithm' must be set when '%shostKey' is specified", GIT_PROPERTY_PREFIX, GIT_PROPERTY_PREFIX))
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyIsValid.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyIsValid.java
new file mode 100644
index 00000000..6a0a831b
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyIsValid.java
@@ -0,0 +1,24 @@
+package org.springframework.cloud.config.server.ssh;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import org.springframework.validation.annotation.Validated;
+
+/**
+* Beans annotated with {@link PrivateKeyValidator} and {@link Validated} will have the constraints applied.
+*
+* @author Ollie Hughes
+*/
+@Constraint(validatedBy = PrivateKeyValidator.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PrivateKeyIsValid {
+ String message() default "{PrivateKeyIsValid.message}";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java
new file mode 100644
index 00000000..87055f7f
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java
@@ -0,0 +1,89 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+
+import org.springframework.validation.annotation.Validated;
+
+import static java.lang.String.format;
+import static org.springframework.cloud.config.server.ssh.SshPropertyValidator.isSshUri;
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * JSR-303 Cross Field validator that ensures that a {@link SshUriProperties} bean for the constraints:
+ * - Private key is present and can be correctly parsed using {@link com.jcraft.jsch.KeyPair}
+ *
+ * Beans annotated with {@link PrivateKeyValidator} and {@link Validated} will have the constraints applied.
+ *
+ * @author Ollie Hughes
+ */
+public class PrivateKeyValidator implements ConstraintValidator {
+ private static final String GIT_PROPERTY_PREFIX = "spring.cloud.config.server.git.";
+ private final SshPropertyValidator sshPropertyValidator = new SshPropertyValidator();
+
+ @Override
+ public void initialize(PrivateKeyIsValid constrainAnnotation) {
+ //No special initialization of validator required
+ }
+
+ @Override
+ public boolean isValid(SshUriProperties sshUriProperties, ConstraintValidatorContext context) {
+ context.disableDefaultConstraintViolation();
+ Set validationResults = new HashSet<>();
+ List extractedProperties = sshPropertyValidator.extractRepoProperties(sshUriProperties);
+
+ for (SshUri extractedProperty : extractedProperties) {
+ if (sshUriProperties.isIgnoreLocalSshSettings() && isSshUri(extractedProperty.getUri())) {
+ validationResults.add(
+ isPrivateKeyPresent(extractedProperty, context)
+ && isPrivateKeyFormatCorrect(extractedProperty, context));
+ }
+ }
+ return !validationResults.contains(false);
+
+ }
+
+ private boolean isPrivateKeyPresent(SshUri sshUriProperties, ConstraintValidatorContext context) {
+ if (!hasText(sshUriProperties.getPrivateKey())) {
+ context.buildConstraintViolationWithTemplate(
+ format("Property '%shostKey' must be set when '%shostKeyAlgorithm' is specified", GIT_PROPERTY_PREFIX, GIT_PROPERTY_PREFIX))
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isPrivateKeyFormatCorrect(SshUri sshUriProperties, ConstraintValidatorContext context) {
+ try {
+ KeyPair.load(new JSch(), sshUriProperties.getPrivateKey().getBytes(), null);
+ return true;
+ } catch (JSchException e) {
+ context.buildConstraintViolationWithTemplate(
+ format("Property '%sprivateKey' contains is not a valid private key", GIT_PROPERTY_PREFIX))
+ .addConstraintViolation();
+ return false;
+ }
+ }
+}
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..0934ccec
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactory.java
@@ -0,0 +1,78 @@
+/*
+ * 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 java.util.Map;
+
+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;
+
+/**
+ * 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 static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
+ private static final String YES_OPTION = "yes";
+ private static final String NO_OPTION = "no";
+ private static final String SERVER_HOST_KEY = "server_host_key";
+ 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) {
+ SshUri 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(STRICT_HOST_KEY_CHECKING, NO_OPTION);
+ } else {
+ session.setConfig(STRICT_HOST_KEY_CHECKING, YES_OPTION);
+ }
+ }
+
+ @Override
+ protected Session createSession(Host hc, String user, String host, int port, FS fs) throws JSchException {
+ if (sshKeysByHostname.containsKey(host)) {
+ SshUri 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..6b1b1475
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshPropertyValidator.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.transport.URIish;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * Validate SSH related properties
+ *
+ * @author Ollie Hughes
+ */
+@Component
+@EnableConfigurationProperties(SshUriProperties.class)
+public class SshPropertyValidator {
+
+ protected static boolean isSshUri(Object uri) {
+ if(uri != null) {
+ try {
+ URIish urIish = new URIish(uri.toString());
+ String scheme = urIish.getScheme();
+ if(scheme == null && hasText(urIish.getHost()) && hasText(urIish.getUser())) {
+ //JGit returns null if using SCP URI but user and host will be populated
+ return true;
+ }
+ return scheme != null && !scheme.matches("^(http|https)$");
+
+ } catch (URISyntaxException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ protected List extractRepoProperties(SshUriProperties sshUriProperties) {
+ List allRepoProperties = new ArrayList<>();
+ allRepoProperties.add(sshUriProperties);
+ Map repos = sshUriProperties.getRepos();
+ if (repos != null) {
+ allRepoProperties.addAll(repos.values());
+ }
+ return allRepoProperties;
+ }
+}
diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUri.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUri.java
new file mode 100644
index 00000000..109d6a31
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUri.java
@@ -0,0 +1,150 @@
+package org.springframework.cloud.config.server.ssh;
+
+import org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriNestedRepoProperties;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Base class that contains configuration properties for Git SSH properties
+ *
+ * @author Ollie Hughes
+ */
+public abstract class SshUri {
+ private String privateKey;
+ private String uri;
+ private String hostKeyAlgorithm;
+ private String hostKey;
+ private boolean ignoreLocalSshSettings;
+ private boolean strictHostKeyChecking = true;
+
+ public static SshUriPropertiesBuilder builder() {
+ return new SshUriPropertiesBuilder();
+ }
+
+ 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 boolean isIgnoreLocalSshSettings() {
+ return this.ignoreLocalSshSettings;
+ }
+
+ public boolean isStrictHostKeyChecking() {
+ return this.strictHostKeyChecking;
+ }
+
+ 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 setIgnoreLocalSshSettings(boolean ignoreLocalSshSettings) {
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ }
+
+ public void setStrictHostKeyChecking(boolean strictHostKeyChecking) {
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ }
+
+ public String toString() {
+ return "org.springframework.cloud.config.server.ssh.SshUriProperties(uri=" + this.getUri() + " hostKeyAlgorithm=" + this.getHostKeyAlgorithm() + ", hostKey=" + this.getHostKey() + ", privateKey=" + this.getPrivateKey() + ", ignoreLocalSshSettings=" + this.isIgnoreLocalSshSettings() + ", strictHostKeyChecking=" + this.isStrictHostKeyChecking() + ",)";
+ }
+
+ public static class SshUriPropertiesBuilder {
+ private String uri;
+ private String hostKeyAlgorithm;
+ private String hostKey;
+ private String privateKey;
+ private boolean ignoreLocalSshSettings;
+ private boolean strictHostKeyChecking = true;
+ private Map repos = new LinkedHashMap<>();
+
+ SshUriPropertiesBuilder() {
+ }
+
+ public SshUri.SshUriPropertiesBuilder uri(String uri) {
+ this.uri = uri;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder hostKeyAlgorithm(String hostKeyAlgorithm) {
+ this.hostKeyAlgorithm = hostKeyAlgorithm;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder hostKey(String hostKey) {
+ this.hostKey = hostKey;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder privateKey(String privateKey) {
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder ignoreLocalSshSettings(boolean ignoreLocalSshSettings) {
+ this.ignoreLocalSshSettings = ignoreLocalSshSettings;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder strictHostKeyChecking(boolean strictHostKeyChecking) {
+ this.strictHostKeyChecking = strictHostKeyChecking;
+ return this;
+ }
+
+ public SshUri.SshUriPropertiesBuilder repos(Map repos) {
+ this.repos = repos;
+ return this;
+ }
+
+ public SshUriProperties build() {
+ SshUriProperties sshUriProperties = new SshUriProperties();
+ sshUriProperties.setRepos(repos);
+ build(sshUriProperties);
+ return sshUriProperties;
+ }
+
+ public SshUriNestedRepoProperties buildAsNestedRepo() {
+ SshUriNestedRepoProperties sshUriNestedRepoProperties = new SshUriNestedRepoProperties();
+ build(sshUriNestedRepoProperties);
+ return sshUriNestedRepoProperties;
+ }
+
+ private void build(SshUri sshUriNestedRepoProperties) {
+ sshUriNestedRepoProperties.setUri(uri);
+ sshUriNestedRepoProperties.setHostKeyAlgorithm(hostKeyAlgorithm);
+ sshUriNestedRepoProperties.setHostKey(hostKey);
+ sshUriNestedRepoProperties.setPrivateKey(privateKey);
+ sshUriNestedRepoProperties.setIgnoreLocalSshSettings(ignoreLocalSshSettings);
+ sshUriNestedRepoProperties.setStrictHostKeyChecking(strictHostKeyChecking);
+ }
+
+ public String toString() {
+ return "org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriPropertiesBuilder(uri=" + this.uri + "hostKeyAlgorithm=" + this.hostKeyAlgorithm + ", hostKey=" + this.hostKey + ", privateKey=" + this.privateKey + ", 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/SshUriProperties.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java
new file mode 100644
index 00000000..c5a25ddb
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriProperties.java
@@ -0,0 +1,63 @@
+/*
+ * 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 java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * Data container for property based SSH config
+ *
+ * @author Ollie Hughes
+ */
+@ConfigurationProperties("spring.cloud.config.server.git")
+@Validated
+@PrivateKeyIsValid
+@HostKeyAndAlgoBothExist
+@HostKeyAlgoSupported
+public class SshUriProperties extends SshUri {
+
+ private Map repos = new LinkedHashMap<>();
+
+ public Map getRepos() {
+ return this.repos;
+ }
+
+ public void setRepos(Map repos) {
+ this.repos = repos;
+ }
+
+ public void addRepo(String repoName, SshUriProperties.SshUriNestedRepoProperties properties) {
+ this.repos.put(repoName, properties);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "{repos=" + repos + "}";
+ }
+
+ /**
+ * Differentiate between sets of properties that are defined in nested Git repos.
+ * This is to prevent boot from guarding against a potential infinite deserialization of nested properties.
+ * This sub class differentiates from {@link SshUriProperties} as it does not contain the self mao
+ */
+ public static class SshUriNestedRepoProperties extends SshUri {
+
+ }
+}
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..36cbf85a
--- /dev/null
+++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessor.java
@@ -0,0 +1,71 @@
+/*
+ * 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 java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.transport.URIish;
+import org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriNestedRepoProperties;
+
+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 (SshUriNestedRepoProperties repoProperties : repos.values()) {
+ String repoUri = repoProperties.getUri();
+ if (isSshUri(repoUri) && getHostname(repoUri) != null) {
+ sshUriPropertyMap.put(getHostname(repoUri), repoProperties);
+ }
+ }
+ }
+ return sshUriPropertyMap;
+ }
+
+ protected static String getHostname(String uri) {
+ try {
+ URIish urIish = new URIish(uri);
+ return urIish.getHost();
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+}
diff --git a/spring-cloud-config-server/src/main/resources/configserver.yml b/spring-cloud-config-server/src/main/resources/configserver.yml
index 176cc464..ee3218a8 100644
--- a/spring-cloud-config-server/src/main/resources/configserver.yml
+++ b/spring-cloud-config-server/src/main/resources/configserver.yml
@@ -18,4 +18,3 @@ server:
port: 8888
management:
context_path: /admin
-
\ No newline at end of file
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/config/TransportConfigurationTest.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/TransportConfigurationTest.java
new file mode 100644
index 00000000..40296eef
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/config/TransportConfigurationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 org.eclipse.jgit.api.TransportConfigCallback;
+import org.junit.Test;
+import org.springframework.cloud.config.server.ssh.SshUri;
+import org.springframework.cloud.config.server.ssh.SshUriProperties;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.instanceOf;
+/**
+* @author Ollie Hughes
+*/
+public class TransportConfigurationTest {
+ @Test
+ public void propertiesBasedSshTransportCallbackCreated() throws Exception {
+ SshUriProperties ignoreLocalSettings = SshUri.builder()
+ .uri("user@gitrepo.com:proj/repo")
+ .ignoreLocalSshSettings(true)
+ .build();
+ TransportConfiguration transportConfiguration = new TransportConfiguration();
+ TransportConfigCallback transportConfigCallback = transportConfiguration.propertiesBasedSshTransportCallback(ignoreLocalSettings);
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.PropertiesBasedSshTransportConfigCallback.class)));
+ }
+
+ @Test
+ public void fileBasedSshTransportCallbackCreated() throws Exception {
+ SshUriProperties dontIgnoreLocalSettings = SshUri.builder()
+ .uri("user@gitrepo.com:proj/repo")
+ .ignoreLocalSshSettings(false)
+ .build();
+
+ TransportConfiguration transportConfiguration = new TransportConfiguration();
+ TransportConfigCallback transportConfigCallback = transportConfiguration.propertiesBasedSshTransportCallback(dontIgnoreLocalSettings);
+ assertThat(transportConfigCallback, is(instanceOf(TransportConfiguration.FileBasedSshTransportConfigCallback.class)));
+
+ }
+
+}
\ No newline at end of file
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 40742fca..e3bc26cb 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..cf41ce6d
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/PropertyBasedSshSessionFactoryTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.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() {
+ SshUri 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() {
+ SshUri 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() {
+ SshUri 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 {
+ SshUri sshKey = new SshUriProperties.SshUriPropertiesBuilder()
+ .uri("git@gitlab.example.local:someorg/somerepo.git")
+ .privateKey(PRIVATE_KEY)
+ .build();
+ setupSessionFactory(sshKey);
+
+ factory.createSession(hc, null, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
+ verify(jSch).addIdentity("gitlab.example.local", PRIVATE_KEY.getBytes(), null, null);
+ }
+
+ @Test
+ public void hostKeyIsUsed() throws Exception {
+ SshUri 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, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 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(SshUri sshKey) {
+ Map sshKeysByHostname = new HashMap<>();
+ sshKeysByHostname.put(SshUriPropertyProcessor.getHostname(sshKey.getUri()), sshKey);
+ factory = new PropertyBasedSshSessionFactory(sshKeysByHostname, jSch) ;
+ when(hc.getHostName()).thenReturn(SshUriPropertyProcessor.getHostname(sshKey.getUri()));
+ 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..154c139a
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshPropertyValidatorTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.BeforeClass;
+import org.junit.Test;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+
+/**
+ * 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";
+ private static Validator validator;
+
+ @BeforeClass
+ public static void setUpValidator() {
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ @Test
+ public void supportedParametersSuccesful() throws Exception {
+ SshUriProperties validSettings = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey(VALID_PRIVATE_KEY)
+ .hostKey(VALID_HOST_KEY)
+ .hostKeyAlgorithm("ssh-rsa")
+ .build();
+
+ Set> constraintViolations = validator.validate(validSettings);
+ assertThat(constraintViolations, hasSize(0));
+
+ }
+
+ @Test
+ public void invalidPrivateKeyFails() throws Exception {
+
+ SshUriProperties invalidKey = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .build();
+
+ Set> constraintViolations = validator.validate(invalidKey);
+ assertThat(constraintViolations, hasSize(1));
+
+ }
+
+ @Test
+ public void missingPrivateKeyFails() throws Exception {
+
+ SshUriProperties missingKey = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .build();
+
+ Set> constraintViolations = validator.validate(missingKey);
+ assertThat(constraintViolations, hasSize(1));
+ }
+
+ @Test
+ public void hostKeyWithMissingAlgoFails() throws Exception {
+
+ SshUriProperties missingAlgo = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey(VALID_PRIVATE_KEY)
+ .hostKey("some_host")
+ .build();
+
+ Set> constraintViolations = validator.validate(missingAlgo);
+ assertThat(constraintViolations, hasSize(1));
+ }
+
+ @Test
+ public void algoWithMissingHostKeyFails() throws Exception {
+
+ SshUriProperties missingHostKey = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey(VALID_PRIVATE_KEY)
+ .hostKeyAlgorithm("ssh-rsa")
+ .build();
+
+ Set> constraintViolations = validator.validate(missingHostKey);
+ assertThat(constraintViolations, hasSize(1));
+ }
+
+ @Test
+ public void unsupportedAlgoFails() throws Exception {
+
+ SshUriProperties unsupportedAlgo = SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(true)
+ .privateKey(VALID_PRIVATE_KEY)
+ .hostKey("some_host_key")
+ .hostKeyAlgorithm("unsupported")
+ .build();
+
+ Set> constraintViolations = validator.validate(unsupportedAlgo);
+ assertThat(constraintViolations, hasSize(1));
+ }
+
+ @Test
+ public void validatorNotRunIfIgnoreLocalSettingsFalse() throws Exception {
+
+ SshUriProperties useLocal = (SshUri.builder()
+ .uri(SSH_URI)
+ .ignoreLocalSshSettings(false)
+ .privateKey("invalid_key")
+ .build());
+
+ Set> constraintViolations = validator.validate(useLocal);
+ assertThat(constraintViolations, hasSize(0));
+
+ }
+
+ @Test
+ public void validatorNotRunIfHttpsUri() throws Exception {
+
+ SshUriProperties httpsUri = (SshUri.builder()
+ .uri("https://somerepo.com/team/project.git")
+ .ignoreLocalSshSettings(true)
+ .privateKey("invalid_key")
+ .build());
+
+ Set> constraintViolations = validator.validate(httpsUri);
+ assertThat(constraintViolations, hasSize(0));
+
+ }
+}
\ 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..8f7fd49a
--- /dev/null
+++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/ssh/SshUriPropertyProcessorTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 java.util.Map;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.cloud.config.server.ssh.SshUriProperties.SshUriNestedRepoProperties;
+
+
+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 = "ollie@gitlab1.test.local:project/my-repo";
+ private static final String HOST1 = "gitlab1.test.local";
+ private static final String PRIVATE_KEY2 = "privateKey2";
+ private static final String URI2 = "ssh://git@gitlab2.test.local/wtran/my-repo";
+ private static final String HOST2 = "gitlab2.test.local";
+ private static final String PRIVATE_KEY3 = "privateKey3";
+ private static final String URI3 = "git+ssh://git@gitlab3.test.local/wtran/my-repo";
+ private static final String HOST3 = "gitlab3.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));
+
+ SshUri sshKey = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey);
+ }
+
+ @Test
+ public void testMultipleSshUriPropertiess() {
+ SshUriProperties sshUriProperties = mainRepoPropertiesFixture();
+ addRepoProperties(sshUriProperties, SshUri.builder()
+ .uri(URI2)
+ .privateKey(PRIVATE_KEY2)
+ .buildAsNestedRepo(), "repo2");
+ addRepoProperties(sshUriProperties, SshUri.builder()
+ .uri(URI3)
+ .privateKey(PRIVATE_KEY3)
+ .buildAsNestedRepo(), "repo3");
+
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(sshUriProperties);
+
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+
+ assertThat(sshKeysByHostname.values(), hasSize(3));
+
+ SshUri sshKey1 = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey1);
+
+ SshUri sshKey2 = sshKeysByHostname.get(HOST2);
+
+ assertThat(SshUriPropertyProcessor.getHostname(sshKey2.getUri()), is(equalTo(HOST2)));
+ assertThat(sshKey2.getHostKeyAlgorithm(), is(nullValue()));
+ assertThat(sshKey2.getHostKey(), is(nullValue()));
+ assertThat(sshKey2.getPrivateKey(), is(equalTo(PRIVATE_KEY2)));
+
+ SshUri sshKey3 = sshKeysByHostname.get(HOST3);
+
+ assertThat(SshUriPropertyProcessor.getHostname(sshKey3.getUri()), is(equalTo(HOST3)));
+ assertThat(sshKey3.getHostKeyAlgorithm(), is(nullValue()));
+ assertThat(sshKey3.getHostKey(), is(nullValue()));
+ assertThat(sshKey3.getPrivateKey(), is(equalTo(PRIVATE_KEY3)));
+ }
+
+ @Test
+ public void testSameHostnameDifferentKeysFirstOneWins() {
+ SshUriProperties sshUriProperties = mainRepoPropertiesFixture();
+ addRepoProperties(sshUriProperties, SshUri.builder().uri(URI1)
+ .privateKey(PRIVATE_KEY1)
+ .hostKey(HOST_KEY1)
+ .hostKeyAlgorithm(ALGO1)
+ .buildAsNestedRepo(), "repo2");
+
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(sshUriProperties);
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+
+ assertThat(sshKeysByHostname.values(), hasSize(1));
+
+ SshUri sshKey = sshKeysByHostname.get(HOST1);
+ assertMainRepo(sshKey);
+ }
+
+ @Test
+ public void testNoSshUriProperties() {
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(new SshUriProperties());
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+ assertThat(sshKeysByHostname.values(), hasSize(0));
+ }
+
+ @Test
+ public void testInvalidUriDoesNotAddEntry() {
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(SshUri.builder().uri("invalid_uri").build());
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+ assertThat(sshKeysByHostname.values(), hasSize(0));
+ }
+
+ @Test
+ public void testHttpsUriDoesNotAddEntry() {
+ SshUriPropertyProcessor sshUriPropertyProcessor = new SshUriPropertyProcessor(SshUri.builder().uri("https://user@github.com/proj/repo.git").build());
+ Map sshKeysByHostname = sshUriPropertyProcessor.getSshKeysByHostname();
+ assertThat(sshKeysByHostname.values(), hasSize(0));
+ }
+
+ private SshUriProperties mainRepoPropertiesFixture() {
+
+ return SshUri.builder()
+ .uri(URI1)
+ .hostKeyAlgorithm(ALGO1)
+ .hostKey(HOST_KEY1)
+ .privateKey(PRIVATE_KEY1)
+ .build();
+ }
+
+ private void addRepoProperties(SshUriProperties mainRepoProperties, SshUriNestedRepoProperties repoProperties, String repoName) {
+ mainRepoProperties.addRepo(repoName, repoProperties);
+ }
+
+ private void assertMainRepo(SshUri sshKey) {
+ assertThat(sshKey, is(notNullValue()));
+ assertThat(SshUriPropertyProcessor.getHostname(sshKey.getUri()), 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