diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 486f8cb7..2258ae94 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -445,6 +445,12 @@ Example: |*hostKeyAlgorithm* |One of `ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384 ,ecdsa-sha2-nistp521`. Must be set if `hostKey` is also set +|*proxyHost* +|Hostname for the ssh proxy connection. Is optional and used only when `ignoreLocalSshSettings` is true + +|*proxyPort* +|Port for the ssh proxy connection. Must be set if `proxyHost` is also set + |*strictHostKeyChecking* |`true` or `false`. If false, ignore errors with host key 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 index 0b3d2eb4..0f72bbb7 100644 --- 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 @@ -15,16 +15,18 @@ */ 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.ProxyHTTP; import com.jcraft.jsch.Session; import org.eclipse.jgit.transport.JschConfigSessionFactory; import org.eclipse.jgit.transport.OpenSshConfig.Host; import org.eclipse.jgit.util.Base64; import org.eclipse.jgit.util.FS; +import org.springframework.util.Assert; + +import java.util.Map; /** * In a cloud environment local SSH config files such as `.known_hosts` may not be suitable for providing @@ -64,6 +66,12 @@ public class PropertyBasedSshSessionFactory extends JschConfigSessionFactory { if (preferredAuthentications != null) { session.setConfig(PREFERRED_AUTHENTICATIONS, preferredAuthentications); } + String proxyHost = sshProperties.getProxyHost(); + if (proxyHost != null) { + Integer proxyPort = sshProperties.getProxyPort(); + Assert.notNull(proxyPort, "Property proxyPort is required, since proxyHost is set."); + session.setProxy(new ProxyHTTP(proxyHost, proxyPort)); + } } @Override 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 index a7211160..4089b78f 100644 --- 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 @@ -37,6 +37,8 @@ public abstract class SshUri { private String preferredAuthentications; private boolean ignoreLocalSshSettings; private boolean strictHostKeyChecking = true; + private String proxyHost; + private Integer proxyPort; public static SshUriPropertiesBuilder builder() { return new SshUriPropertiesBuilder(); @@ -106,6 +108,22 @@ public abstract class SshUri { this.strictHostKeyChecking = strictHostKeyChecking; } + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public Integer getProxyPort() { + return proxyPort; + } + + public void setProxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + } + public String toString() { return "org.springframework.cloud.config.server.ssh.SshUriProperties(uri=" + this.getUri() + " hostKeyAlgorithm=" + this.getHostKeyAlgorithm() @@ -114,7 +132,9 @@ public abstract class SshUri { + ", ignoreLocalSshSettings=" + this.isIgnoreLocalSshSettings() + ", knownHostsFile=" + this.getKnownHostsFile() + ", preferredAuthentications=" + this.getPreferredAuthentications() - + ", strictHostKeyChecking=" + this.isStrictHostKeyChecking() + ",)"; + + ", strictHostKeyChecking=" + this.isStrictHostKeyChecking() + + ", proxyHost=" + this.getProxyHost() + + ", proxyPort=" + this.getProxyPort() + ",)"; } public static class SshUriPropertiesBuilder { @@ -126,6 +146,8 @@ public abstract class SshUri { private String preferredAuthentications; private boolean ignoreLocalSshSettings; private boolean strictHostKeyChecking = true; + private String proxyHost; + private Integer proxyPort; private Map repos = new LinkedHashMap<>(); SshUriPropertiesBuilder() { @@ -171,6 +193,16 @@ public abstract class SshUri { return this; } + public SshUri.SshUriPropertiesBuilder proxyHost(String proxyHost) { + this.proxyHost = proxyHost; + return this; + } + + public SshUri.SshUriPropertiesBuilder proxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + return this; + } + public SshUri.SshUriPropertiesBuilder repos(Map repos) { this.repos = repos; return this; @@ -198,6 +230,8 @@ public abstract class SshUri { sshUriNestedRepoProperties.setPreferredAuthentications(preferredAuthentications); sshUriNestedRepoProperties.setIgnoreLocalSshSettings(ignoreLocalSshSettings); sshUriNestedRepoProperties.setStrictHostKeyChecking(strictHostKeyChecking); + sshUriNestedRepoProperties.setProxyHost(proxyHost); + sshUriNestedRepoProperties.setProxyPort(proxyPort); } public String toString() { @@ -209,6 +243,8 @@ public abstract class SshUri { + ", preferredAuthentications=" + this.preferredAuthentications + ", ignoreLocalSshSettings=" + this.ignoreLocalSshSettings + ", strictHostKeyChecking=" + this.strictHostKeyChecking + + ", proxyHost=" + this.proxyHost + + ", proxyPort=" + this.proxyPort + ", repos=" + this.repos + ")"; } } 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 index 3528b52a..3fdc9a60 100644 --- 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 @@ -16,6 +16,7 @@ package org.springframework.cloud.config.server.ssh; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -35,6 +36,7 @@ import org.springframework.core.io.Resource; import com.jcraft.jsch.HostKey; import com.jcraft.jsch.HostKeyRepository; import com.jcraft.jsch.JSch; +import com.jcraft.jsch.ProxyHTTP; import com.jcraft.jsch.Session; import com.jcraft.jsch.UserInfo; @@ -169,6 +171,39 @@ public class PropertyBasedSshSessionFactoryTest { Assert.assertEquals("/ssh/known_hosts", captor.getValue()); } + @Test + public void proxyHostIsSet() { + SshUri sshKey = new SshUriProperties.SshUriPropertiesBuilder() + .uri("git@gitlab.example.local:someorg/somerepo.git") + .privateKey(PRIVATE_KEY) + .hostKey(HOST_KEY) + .hostKeyAlgorithm(HOST_KEY_ALGORITHM) + .proxyHost("localhost") + .proxyPort(8080) + .build(); + setupSessionFactory(sshKey); + + factory.configure(hc, session); + ArgumentCaptor captor = ArgumentCaptor.forClass(ProxyHTTP.class); + + verify(session).setProxy(captor.capture()); + Assert.assertNotNull(captor.getValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void proxyPortIsMissing() { + SshUri sshKey = new SshUriProperties.SshUriPropertiesBuilder() + .uri("git@gitlab.example.local:someorg/somerepo.git") + .privateKey(PRIVATE_KEY) + .hostKey(HOST_KEY) + .hostKeyAlgorithm(HOST_KEY_ALGORITHM) + .proxyHost("localhost") + .build(); + setupSessionFactory(sshKey); + + factory.configure(hc, session); + } + private void setupSessionFactory(SshUri sshKey) { Map sshKeysByHostname = new HashMap<>(); sshKeysByHostname.put(SshUriPropertyProcessor.getHostname(sshKey.getUri()), sshKey);