Replaces Jsch with Apache MINA in config-server (#2122)
This commit is contained in:
@@ -428,7 +428,7 @@ The following table describes the SSH configuration properties.
|
||||
|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, or ecdsa-sha2-nistp521`. Must be set if `hostKey` is also set.
|
||||
|One of `ssh-dss, ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, or ecdsa-sha2-nistp521`. Must be set if `hostKey` is also set.
|
||||
|
||||
|*strictHostKeyChecking*
|
||||
|`true` or `false`. If false, ignore errors with host key.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<name>spring-cloud-config-dependencies</name>
|
||||
<description>Spring Cloud Config Dependencies</description>
|
||||
<properties>
|
||||
<jgit.version>5.12.0.202106070339-r</jgit.version>
|
||||
<jgit.version>5.13.1.202206130422-r</jgit.version>
|
||||
<spring-vault.version>2.3.2</spring-vault.version>
|
||||
<spring-credhub.version>2.1.1.RELEASE</spring-credhub.version>
|
||||
</properties>
|
||||
@@ -68,14 +68,9 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
|
||||
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
|
||||
<version>${jgit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.tmatesoft.svnkit</groupId>
|
||||
<artifactId>svnkit</artifactId>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
|
||||
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
|
||||
@@ -97,7 +97,7 @@ public class JGitEnvironmentProperties extends AbstractScmAccessorProperties
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* One of ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, or
|
||||
* One of ssh-dss, ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, or
|
||||
* ecdsa-sha2-nistp521. Must be set if hostKey is also set.
|
||||
*/
|
||||
private String hostKeyAlgorithm;
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.jcraft.jsch.Session;
|
||||
import org.eclipse.jgit.api.CheckoutCommand;
|
||||
import org.eclipse.jgit.api.CloneCommand;
|
||||
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
|
||||
@@ -50,10 +49,7 @@ import org.eclipse.jgit.lib.BranchTrackingStatus;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||
import org.eclipse.jgit.transport.OpenSshConfig.Host;
|
||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||
import org.eclipse.jgit.transport.SshSessionFactory;
|
||||
import org.eclipse.jgit.transport.TagOpt;
|
||||
import org.eclipse.jgit.transport.TrackingRefUpdate;
|
||||
import org.eclipse.jgit.util.FileUtils;
|
||||
@@ -135,8 +131,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
*/
|
||||
private boolean forcePull;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Flag to indicate that the branch should be deleted locally if it's origin tracked
|
||||
* branch was removed.
|
||||
@@ -279,7 +273,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
@Override
|
||||
public synchronized void afterPropertiesSet() throws Exception {
|
||||
Assert.state(getUri() != null, MESSAGE);
|
||||
initialize();
|
||||
if (this.cloneOnStart) {
|
||||
initClonedRepository();
|
||||
}
|
||||
@@ -677,18 +670,6 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (!this.initialized) {
|
||||
SshSessionFactory.setInstance(new JschConfigSessionFactory() {
|
||||
@Override
|
||||
protected void configure(Host hc, Session session) {
|
||||
session.setConfig("StrictHostKeyChecking", isStrictHostKeyChecking() ? "yes" : "no");
|
||||
}
|
||||
});
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void configureCommand(TransportCommand<?, ?> command) {
|
||||
command.setTimeout(this.timeout);
|
||||
if (this.transportConfigCallback != null) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2018-2019 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
|
||||
*
|
||||
* https://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.io.File;
|
||||
|
||||
import org.eclipse.jgit.annotations.NonNull;
|
||||
import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
|
||||
import org.eclipse.jgit.transport.SshConfigStore;
|
||||
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
|
||||
|
||||
import org.springframework.cloud.config.server.environment.JGitEnvironmentProperties;
|
||||
|
||||
public class FileBasedSshSessionFactory extends SshdSessionFactory {
|
||||
|
||||
private static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
|
||||
|
||||
private static final String YES_OPTION = "yes";
|
||||
|
||||
private static final String NO_OPTION = "no";
|
||||
|
||||
private final JGitEnvironmentProperties sshUriProperties;
|
||||
|
||||
public FileBasedSshSessionFactory(JGitEnvironmentProperties sshUriProperties) {
|
||||
this.sshUriProperties = sshUriProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SshConfigStore createSshConfigStore(File homeDir, File configFile, String localUserName) {
|
||||
return configFile == null ? null : new OpenSshConfigFile(homeDir, configFile, localUserName) {
|
||||
|
||||
@Override
|
||||
public HostEntry lookup(@NonNull String hostName, int port, String userName) {
|
||||
HostEntry hostEntry = super.lookup(hostName, port, userName);
|
||||
|
||||
hostEntry.setValue(STRICT_HOST_KEY_CHECKING,
|
||||
sshUriProperties.isStrictHostKeyChecking() ? YES_OPTION : NO_OPTION);
|
||||
|
||||
return hostEntry;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.config.server.ssh;
|
||||
|
||||
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.transport.SshTransport;
|
||||
import org.eclipse.jgit.transport.Transport;
|
||||
|
||||
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentProperties;
|
||||
@@ -33,7 +30,7 @@ import org.springframework.cloud.config.server.environment.MultipleJGitEnvironme
|
||||
*/
|
||||
public class FileBasedSshTransportConfigCallback implements TransportConfigCallback {
|
||||
|
||||
private MultipleJGitEnvironmentProperties sshUriProperties;
|
||||
private final MultipleJGitEnvironmentProperties sshUriProperties;
|
||||
|
||||
public FileBasedSshTransportConfigCallback(MultipleJGitEnvironmentProperties sshUriProperties) {
|
||||
this.sshUriProperties = sshUriProperties;
|
||||
@@ -45,14 +42,9 @@ public class FileBasedSshTransportConfigCallback implements TransportConfigCallb
|
||||
|
||||
@Override
|
||||
public void configure(Transport transport) {
|
||||
SshSessionFactory.setInstance(new JschConfigSessionFactory() {
|
||||
@Override
|
||||
protected void configure(OpenSshConfig.Host hc, Session session) {
|
||||
session.setConfig("StrictHostKeyChecking",
|
||||
FileBasedSshTransportConfigCallback.this.sshUriProperties.isStrictHostKeyChecking() ? "yes"
|
||||
: "no");
|
||||
}
|
||||
});
|
||||
if (transport instanceof SshTransport) {
|
||||
((SshTransport) transport).setSshSessionFactory(new FileBasedSshSessionFactory(sshUriProperties));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ public class HostKeyAlgoSupportedValidator
|
||||
|
||||
private static final String GIT_PROPERTY_PREFIX = "spring.cloud.config.server.git.";
|
||||
|
||||
private static final Set<String> VALID_HOST_KEY_ALGORITHMS = new LinkedHashSet<>(
|
||||
Arrays.asList("ssh-dss", "ssh-rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"));
|
||||
private static final Set<String> VALID_HOST_KEY_ALGORITHMS = new LinkedHashSet<>(Arrays.asList("ssh-dss", "ssh-rsa",
|
||||
"ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"));
|
||||
|
||||
private final SshPropertyValidator sshPropertyValidator = new SshPropertyValidator();
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2015-2022 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
|
||||
*
|
||||
* https://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.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
|
||||
import org.apache.sshd.common.session.SessionContext;
|
||||
import org.apache.sshd.common.util.io.resource.AbstractIoResource;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
|
||||
final class KeyPairUtils {
|
||||
|
||||
private static final KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser();
|
||||
|
||||
private KeyPairUtils() {
|
||||
|
||||
}
|
||||
|
||||
static Collection<KeyPair> load(SessionContext session, String privateKey)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return loader.loadKeyPairs(session, new StringResource(privateKey), null);
|
||||
}
|
||||
|
||||
static boolean isValid(String privateKey) {
|
||||
try {
|
||||
return !KeyPairUtils.load(null, privateKey).isEmpty();
|
||||
}
|
||||
catch (IOException | GeneralSecurityException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringResource extends AbstractIoResource<String> {
|
||||
|
||||
protected StringResource(String resourceValue) {
|
||||
super(String.class, resourceValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() {
|
||||
return new ByteArrayInputStream(this.getResourceValue().getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,10 +23,6 @@ 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.cloud.config.server.environment.JGitEnvironmentProperties;
|
||||
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -38,7 +34,7 @@ import static org.springframework.util.StringUtils.hasText;
|
||||
/**
|
||||
* JSR-303 Cross Field validator that ensures that an
|
||||
* {@link MultipleJGitEnvironmentProperties} bean for the constraints: - Private key is
|
||||
* present and can be correctly parsed using {@link com.jcraft.jsch.KeyPair}
|
||||
* present and can be correctly parsed using {@link java.security.KeyPair}
|
||||
*
|
||||
* Beans annotated with {@link PrivateKeyValidator} and {@link Validated} will have the
|
||||
* constraints applied.
|
||||
@@ -87,16 +83,14 @@ public class PrivateKeyValidator implements ConstraintValidator<PrivateKeyIsVali
|
||||
|
||||
private boolean isPrivateKeyFormatCorrect(JGitEnvironmentProperties sshUriProperties,
|
||||
ConstraintValidatorContext context) {
|
||||
try {
|
||||
KeyPair.load(new JSch(), sshUriProperties.getPrivateKey().getBytes(), null);
|
||||
if (KeyPairUtils.isValid(sshUriProperties.getPrivateKey())) {
|
||||
return true;
|
||||
}
|
||||
catch (JSchException e) {
|
||||
context.buildConstraintViolationWithTemplate(
|
||||
format("Property '%sprivateKey' is not a valid private key", GIT_PROPERTY_PREFIX))
|
||||
.addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
|
||||
context.buildConstraintViolationWithTemplate(
|
||||
format("Property '%sprivateKey' is not a valid private key", GIT_PROPERTY_PREFIX))
|
||||
.addConstraintViolation();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.config.server.ssh;
|
||||
|
||||
import com.jcraft.jsch.JSch;
|
||||
import org.eclipse.jgit.api.TransportConfigCallback;
|
||||
import org.eclipse.jgit.transport.SshTransport;
|
||||
import org.eclipse.jgit.transport.Transport;
|
||||
@@ -44,9 +43,8 @@ public class PropertiesBasedSshTransportConfigCallback implements TransportConfi
|
||||
@Override
|
||||
public void configure(Transport transport) {
|
||||
if (transport instanceof SshTransport) {
|
||||
SshTransport sshTransport = (SshTransport) transport;
|
||||
sshTransport.setSshSessionFactory(new PropertyBasedSshSessionFactory(
|
||||
new SshUriPropertyProcessor(this.sshUriProperties).getSshKeysByHostname(), new JSch()));
|
||||
((SshTransport) transport).setSshSessionFactory(new PropertyBasedSshSessionFactory(
|
||||
new SshUriPropertyProcessor(this.sshUriProperties).getSshKeysByHostname()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,20 +16,39 @@
|
||||
|
||||
package org.springframework.cloud.config.server.ssh;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
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.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.KeyUtils;
|
||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
|
||||
import org.apache.sshd.common.session.SessionContext;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.eclipse.jgit.annotations.NonNull;
|
||||
import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
|
||||
import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.SshConfigStore;
|
||||
import org.eclipse.jgit.transport.sshd.JGitKeyCache;
|
||||
import org.eclipse.jgit.transport.sshd.ProxyData;
|
||||
import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
|
||||
import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
|
||||
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
|
||||
|
||||
import org.springframework.cloud.config.server.environment.JGitEnvironmentProperties;
|
||||
import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* In a cloud environment local SSH config files such as `.known_hosts` may not be
|
||||
@@ -39,7 +58,7 @@ import org.springframework.cloud.config.server.proxy.ProxyHostProperties;
|
||||
* @author William Tran
|
||||
* @author Ollie Hughes
|
||||
*/
|
||||
public class PropertyBasedSshSessionFactory extends JschConfigSessionFactory {
|
||||
public class PropertyBasedSshSessionFactory extends SshdSessionFactory {
|
||||
|
||||
private static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
|
||||
|
||||
@@ -49,62 +68,179 @@ public class PropertyBasedSshSessionFactory extends JschConfigSessionFactory {
|
||||
|
||||
private static final String NO_OPTION = "no";
|
||||
|
||||
private static final String SERVER_HOST_KEY = "server_host_key";
|
||||
|
||||
private final Map<String, JGitEnvironmentProperties> sshKeysByHostname;
|
||||
|
||||
private final JSch jSch;
|
||||
public PropertyBasedSshSessionFactory(Map<String, JGitEnvironmentProperties> sshKeysByHostname) {
|
||||
super(new JGitKeyCache(), new HttpProxyDataFactory(sshKeysByHostname));
|
||||
|
||||
public PropertyBasedSshSessionFactory(Map<String, JGitEnvironmentProperties> sshKeysByHostname, JSch jSch) {
|
||||
this.sshKeysByHostname = sshKeysByHostname;
|
||||
this.jSch = jSch;
|
||||
assert this.sshKeysByHostname.entrySet().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Host hc, Session session) {
|
||||
JGitEnvironmentProperties sshProperties = this.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);
|
||||
}
|
||||
String preferredAuthentications = sshProperties.getPreferredAuthentications();
|
||||
if (preferredAuthentications != null) {
|
||||
session.setConfig(PREFERRED_AUTHENTICATIONS, preferredAuthentications);
|
||||
}
|
||||
protected SshConfigStore createSshConfigStore(File homeDir, File configFile, String localUserName) {
|
||||
return new SshConfigStore() {
|
||||
|
||||
ProxyHostProperties proxyHostProperties = sshProperties.getProxy().get(ProxyHostProperties.ProxyForScheme.HTTP);
|
||||
if (proxyHostProperties != null && proxyHostProperties.connectionInformationProvided()) {
|
||||
ProxyHTTP proxy = createProxy(proxyHostProperties);
|
||||
proxy.setUserPasswd(proxyHostProperties.getUsername(), proxyHostProperties.getPassword());
|
||||
session.setProxy(proxy);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public HostConfig lookup(@NonNull String hostName, int port, String userName) {
|
||||
OpenSshConfigFile.HostEntry hostEntry = new OpenSshConfigFile.HostEntry();
|
||||
|
||||
protected ProxyHTTP createProxy(ProxyHostProperties proxyHostProperties) {
|
||||
return new ProxyHTTP(proxyHostProperties.getHost(), proxyHostProperties.getPort());
|
||||
return updateIfNeeded(hostEntry, hostName);
|
||||
}
|
||||
|
||||
private OpenSshConfigFile.HostEntry updateIfNeeded(OpenSshConfigFile.HostEntry hostEntry, String hostName) {
|
||||
JGitEnvironmentProperties sshProperties = sshKeysByHostname.get(hostName);
|
||||
if (sshProperties == null) {
|
||||
return hostEntry;
|
||||
}
|
||||
|
||||
if (sshProperties.getHostKey() == null || !sshProperties.isStrictHostKeyChecking()) {
|
||||
hostEntry.setValue(STRICT_HOST_KEY_CHECKING, NO_OPTION);
|
||||
}
|
||||
else {
|
||||
hostEntry.setValue(STRICT_HOST_KEY_CHECKING, YES_OPTION);
|
||||
}
|
||||
|
||||
String preferredAuthentications = sshProperties.getPreferredAuthentications();
|
||||
if (preferredAuthentications != null) {
|
||||
hostEntry.setValue(PREFERRED_AUTHENTICATIONS, preferredAuthentications);
|
||||
}
|
||||
return hostEntry;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Session createSession(Host hc, String user, String host, int port, FS fs) throws JSchException {
|
||||
if (this.sshKeysByHostname.containsKey(host)) {
|
||||
JGitEnvironmentProperties sshUriProperties = this.sshKeysByHostname.get(host);
|
||||
this.jSch.addIdentity(host, sshUriProperties.getPrivateKey().getBytes(), null, null);
|
||||
if (sshUriProperties.getKnownHostsFile() != null) {
|
||||
this.jSch.setKnownHosts(sshUriProperties.getKnownHostsFile());
|
||||
protected File getSshConfig(File dir) {
|
||||
// Do not use a config file.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerKeyDatabase getServerKeyDatabase(File homeDir, File dir) {
|
||||
return new ServerKeyDatabase() {
|
||||
@Override
|
||||
public List<PublicKey> lookup(String connectAddress, InetSocketAddress remoteAddress,
|
||||
Configuration config) {
|
||||
|
||||
JGitEnvironmentProperties sshProperties = sshKeysByHostname.get(remoteAddress.getHostName());
|
||||
if (sshProperties == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Path> knownHostFiles = getKnownHostFiles(sshProperties);
|
||||
List<PublicKey> publicKeys = new OpenSshServerKeyDatabase(false, knownHostFiles).lookup(connectAddress,
|
||||
remoteAddress, config);
|
||||
|
||||
PublicKey publicKey = getHostKey(sshProperties);
|
||||
if (publicKey != null) {
|
||||
publicKeys.add(publicKey);
|
||||
}
|
||||
|
||||
return publicKeys;
|
||||
}
|
||||
if (sshUriProperties.getHostKey() != null) {
|
||||
HostKey hostkey = new HostKey(host, Base64.decode(sshUriProperties.getHostKey()));
|
||||
this.jSch.getHostKeyRepository().add(hostkey, null);
|
||||
|
||||
@Override
|
||||
public boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey,
|
||||
Configuration config, CredentialsProvider provider) {
|
||||
if (isNotStrictHostKeyChecking(remoteAddress.getHostName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<PublicKey> knownServerKeys = lookup(connectAddress, remoteAddress, config);
|
||||
|
||||
return KeyUtils.findMatchingKey(serverKey, knownServerKeys) != null;
|
||||
}
|
||||
return this.jSch.getSession(user, host, port);
|
||||
|
||||
private boolean isNotStrictHostKeyChecking(String hostName) {
|
||||
JGitEnvironmentProperties sshProperties = sshKeysByHostname.get(hostName);
|
||||
if (sshProperties == null) {
|
||||
return false;
|
||||
}
|
||||
return !sshProperties.isStrictHostKeyChecking();
|
||||
}
|
||||
|
||||
private PublicKey getHostKey(JGitEnvironmentProperties sshProperties) {
|
||||
String hostKey = sshProperties.getHostKey();
|
||||
String hostKeyAlgorithm = sshProperties.getHostKeyAlgorithm();
|
||||
if (!StringUtils.hasText(hostKey) || !StringUtils.hasText(hostKeyAlgorithm)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return AuthorizedKeyEntry.parseAuthorizedKeyEntry(hostKeyAlgorithm + " " + hostKey)
|
||||
.resolvePublicKey(null, null);
|
||||
}
|
||||
catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Path> getKnownHostFiles(JGitEnvironmentProperties sshProperties) {
|
||||
if (sshProperties.getKnownHostsFile() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else {
|
||||
return Collections.singletonList(Paths.get(sshProperties.getKnownHostsFile()));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<KeyPair> getDefaultKeys(File dir) {
|
||||
return new SingleKeyIdentityProvider(sshKeysByHostname);
|
||||
}
|
||||
|
||||
private final static class SingleKeyIdentityProvider implements KeyIdentityProvider, Iterable<KeyPair> {
|
||||
|
||||
private final Map<String, JGitEnvironmentProperties> sshKeysByHostname;
|
||||
|
||||
private SingleKeyIdentityProvider(Map<String, JGitEnvironmentProperties> sshKeysByHostname) {
|
||||
this.sshKeysByHostname = sshKeysByHostname;
|
||||
}
|
||||
throw new JSchException("no keys configured for hostname " + host);
|
||||
|
||||
@Override
|
||||
public Iterator<KeyPair> iterator() {
|
||||
throw new UnsupportedOperationException("Should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException {
|
||||
SshdSocketAddress remoteAddress = SshdSocketAddress.toSshdSocketAddress(session.getRemoteAddress());
|
||||
JGitEnvironmentProperties sshProperties = sshKeysByHostname.get(remoteAddress.getHostName());
|
||||
|
||||
return sshProperties == null ? Collections.emptyList()
|
||||
: KeyPairUtils.load(session, sshProperties.getPrivateKey());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final static class HttpProxyDataFactory implements ProxyDataFactory {
|
||||
|
||||
private final Map<String, JGitEnvironmentProperties> sshKeysByHostname;
|
||||
|
||||
private HttpProxyDataFactory(Map<String, JGitEnvironmentProperties> sshKeysByHostname) {
|
||||
this.sshKeysByHostname = sshKeysByHostname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyData get(InetSocketAddress remoteAddress) {
|
||||
JGitEnvironmentProperties sshProperties = sshKeysByHostname.get(remoteAddress.getHostName());
|
||||
ProxyHostProperties proxyHostProperties = sshProperties.getProxy()
|
||||
.get(ProxyHostProperties.ProxyForScheme.HTTP);
|
||||
|
||||
if (proxyHostProperties == null || !proxyHostProperties.connectionInformationProvided()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP,
|
||||
new InetSocketAddress(proxyHostProperties.getHost(), proxyHostProperties.getPort()));
|
||||
return new ProxyData(proxy, proxyHostProperties.getUsername(),
|
||||
proxyHostProperties.getPassword().toCharArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,31 +18,32 @@ package org.springframework.cloud.config.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
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.eclipse.jgit.transport.FetchConnection;
|
||||
import org.eclipse.jgit.transport.PushConnection;
|
||||
import org.eclipse.jgit.transport.SshConfigStore;
|
||||
import org.eclipse.jgit.transport.SshTransport;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
|
||||
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.environment.MultipleJGitEnvironmentProperties;
|
||||
import org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository;
|
||||
import org.springframework.cloud.config.server.ssh.FileBasedSshSessionFactory;
|
||||
import org.springframework.cloud.config.server.ssh.FileBasedSshTransportConfigCallback;
|
||||
import org.springframework.cloud.config.server.ssh.PropertiesBasedSshTransportConfigCallback;
|
||||
import org.springframework.cloud.config.server.ssh.PropertyBasedSshSessionFactory;
|
||||
import org.springframework.cloud.config.server.ssh.SshPropertyValidator;
|
||||
import org.springframework.cloud.config.server.test.TestConfigServerApplication;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Integration tests for property based SSH config support.
|
||||
@@ -65,12 +66,22 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void propertyBasedTransportCallbackIsConfigured() throws Exception {
|
||||
public void propertyBasedTransportCallbackIsConfigured() {
|
||||
TransportConfigCallback transportConfigCallback = this.jGitEnvironmentRepository
|
||||
.getTransportConfigCallback();
|
||||
assertThat(transportConfigCallback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyBasedSessionFactoryIsUsedForSshTransports() throws Exception {
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
assertThat(sshTransport.getSshSessionFactory()).isInstanceOf(PropertyBasedSshSessionFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@@ -85,12 +96,22 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void propertyBasedTransportCallbackIsConfigured() throws Exception {
|
||||
public void propertyBasedTransportCallbackIsConfigured() {
|
||||
TransportConfigCallback transportConfigCallback = this.jGitEnvironmentRepository
|
||||
.getTransportConfigCallback();
|
||||
assertThat(transportConfigCallback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyBasedSessionFactoryIsUsedForSshTransports() throws Exception {
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
assertThat(sshTransport.getSshSessionFactory()).isInstanceOf(PropertyBasedSshSessionFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -109,7 +130,7 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void privateKeyPropertyWithLineBreaks() throws Exception {
|
||||
public void privateKeyPropertyWithLineBreaks() {
|
||||
TransportConfigCallback callback = this.jGitEnvironmentRepository.getTransportConfigCallback();
|
||||
assertThat(callback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
|
||||
@@ -132,7 +153,7 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void privateKeyPropertyWithLineBreaks() throws Exception {
|
||||
public void privateKeyPropertyWithLineBreaks() {
|
||||
TransportConfigCallback callback = this.jGitEnvironmentRepository.getTransportConfigCallback();
|
||||
assertThat(callback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
|
||||
@@ -159,7 +180,7 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void sshPropertiesWithinNestedRepo() throws Exception {
|
||||
public void sshPropertiesWithinNestedRepo() {
|
||||
TransportConfigCallback callback = this.jGitEnvironmentRepository.getTransportConfigCallback();
|
||||
assertThat(callback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
|
||||
@@ -187,7 +208,7 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void sshPropertiesWithinNestedRepo() throws Exception {
|
||||
public void sshPropertiesWithinNestedRepo() {
|
||||
TransportConfigCallback callback = this.jGitEnvironmentRepository.getTransportConfigCallback();
|
||||
assertThat(callback).isInstanceOf(PropertiesBasedSshTransportConfigCallback.class);
|
||||
|
||||
@@ -219,42 +240,44 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void fileBasedTransportCallbackIsConfigured() throws Exception {
|
||||
public void fileBasedTransportCallbackIsConfigured() {
|
||||
TransportConfigCallback transportConfigCallback = this.jGitEnvironmentRepository
|
||||
.getTransportConfigCallback();
|
||||
assertThat(transportConfigCallback).isInstanceOf(FileBasedSshTransportConfigCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileBasedSessionFactoryIsUsedForSshTransports() throws Exception {
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
assertThat(sshTransport.getSshSessionFactory()).isInstanceOf(FileBasedSshSessionFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckShouldCheck() throws Exception {
|
||||
String uri = "git+ssh://git@somegitserver/somegitrepo";
|
||||
SshSessionFactory.setInstance(null);
|
||||
this.jGitEnvironmentRepository.setUri(uri);
|
||||
this.jGitEnvironmentRepository.setBasedir(new File("./mybasedir"));
|
||||
assertThat(this.jGitEnvironmentRepository.isStrictHostKeyChecking()).isTrue();
|
||||
this.jGitEnvironmentRepository.setCloneOnStart(true);
|
||||
try {
|
||||
// this will throw but we don't care about connecting.
|
||||
this.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<String> keyCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
|
||||
configure.invoke(factory, hc, session);
|
||||
verify(session).setConfig(keyCaptor.capture(), valueCaptor.capture());
|
||||
configure.setAccessible(false);
|
||||
assertThat("yes".equals(valueCaptor.getValue())).isTrue();
|
||||
}
|
||||
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
SshdSessionFactory factory = (SshdSessionFactory) sshTransport.getSshSessionFactory();
|
||||
// There's no public method that can be used to inspect the ssh
|
||||
// configuration, so we'll reflect
|
||||
// the createSshConfigStore method to allow us to check that the config
|
||||
// property is set as expected.
|
||||
Method createSshConfigStore = factory.getClass().getDeclaredMethod("createSshConfigStore", File.class,
|
||||
File.class, String.class);
|
||||
createSshConfigStore.setAccessible(true);
|
||||
SshConfigStore configStore = (SshConfigStore) createSshConfigStore.invoke(factory, new File("."),
|
||||
new File("."), "local-username");
|
||||
createSshConfigStore.setAccessible(false);
|
||||
assertThat("yes"
|
||||
.equals(configStore.lookup("gitserver.com", 22, "username").getValue("StrictHostKeyChecking")))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -272,44 +295,73 @@ public class TransportConfigurationIntegrationTests {
|
||||
private MultipleJGitEnvironmentRepository jGitEnvironmentRepository;
|
||||
|
||||
@Test
|
||||
public void fileBasedTransportCallbackIsConfigured() throws Exception {
|
||||
public void fileBasedTransportCallbackIsConfigured() {
|
||||
TransportConfigCallback transportConfigCallback = this.jGitEnvironmentRepository
|
||||
.getTransportConfigCallback();
|
||||
assertThat(transportConfigCallback).isInstanceOf(FileBasedSshTransportConfigCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckShouldCheck() throws Exception {
|
||||
String uri = "git+ssh://git@somegitserver/somegitrepo";
|
||||
SshSessionFactory.setInstance(null);
|
||||
this.jGitEnvironmentRepository.setUri(uri);
|
||||
this.jGitEnvironmentRepository.setBasedir(new File("./mybasedir"));
|
||||
assertThat(this.jGitEnvironmentRepository.isStrictHostKeyChecking()).isTrue();
|
||||
this.jGitEnvironmentRepository.setCloneOnStart(true);
|
||||
try {
|
||||
// this will throw but we don't care about connecting.
|
||||
this.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<String> keyCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
|
||||
configure.invoke(factory, hc, session);
|
||||
verify(session).setConfig(keyCaptor.capture(), valueCaptor.capture());
|
||||
configure.setAccessible(false);
|
||||
assertThat("yes".equals(valueCaptor.getValue())).isTrue();
|
||||
}
|
||||
public void fileBasedSessionFactoryIsUsedForSshTransports() throws Exception {
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
assertThat(sshTransport.getSshSessionFactory()).isInstanceOf(FileBasedSshSessionFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckShouldCheck() throws Exception {
|
||||
assertThat(this.jGitEnvironmentRepository.isStrictHostKeyChecking()).isTrue();
|
||||
|
||||
this.jGitEnvironmentRepository.afterPropertiesSet();
|
||||
SshTransport sshTransport = DummySshTransport.newInstance();
|
||||
this.jGitEnvironmentRepository.getTransportConfigCallback().configure(sshTransport);
|
||||
|
||||
SshdSessionFactory factory = (SshdSessionFactory) sshTransport.getSshSessionFactory();
|
||||
// There's no public method that can be used to inspect the ssh
|
||||
// configuration, so we'll reflect
|
||||
// the createSshConfigStore method to allow us to check that the config
|
||||
// property is set as expected.
|
||||
Method createSshConfigStore = factory.getClass().getDeclaredMethod("createSshConfigStore", File.class,
|
||||
File.class, String.class);
|
||||
createSshConfigStore.setAccessible(true);
|
||||
SshConfigStore configStore = (SshConfigStore) createSshConfigStore.invoke(factory, new File("."),
|
||||
new File("."), "local-username");
|
||||
createSshConfigStore.setAccessible(false);
|
||||
assertThat("yes"
|
||||
.equals(configStore.lookup("gitserver.com", 22, "username").getValue("StrictHostKeyChecking")))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DummySshTransport extends SshTransport {
|
||||
|
||||
DummySshTransport(String uri) throws URISyntaxException {
|
||||
super(new URIish(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchConnection openFetch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PushConnection openPush() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SshTransport newInstance() {
|
||||
try {
|
||||
return new DummySshTransport("git+ssh://git@uri");
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Not expected", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.eclipse.jgit.junit.http.SimpleHttpServer;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
@@ -40,6 +41,8 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
// FIXME: 4.0.0 https://bugs.eclipse.org/bugs/show_bug.cgi?id=570447
|
||||
@Ignore("SimpleHttpServer does not use jakarta.servlet")
|
||||
public class JGitEnvironmentRepositorySslTests {
|
||||
|
||||
private static SimpleHttpServer server;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2015-2022 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
|
||||
*
|
||||
* https://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.io.File;
|
||||
|
||||
import org.eclipse.jgit.transport.SshConfigStore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.cloud.config.server.environment.JGitEnvironmentProperties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for file based SSH config processor.
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class FileBasedSshSessionFactoryTest {
|
||||
|
||||
private FileBasedSshSessionFactory factory;
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckingIsOptional() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
|
||||
sshKey.setStrictHostKeyChecking(false);
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("gitlab.example.local");
|
||||
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isEqualTo("no");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictHostKeyCheckingIsUsed() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("gitlab.example.local");
|
||||
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isEqualTo("yes");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesNullConfigFile() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
SshConfigStore configStore = factory.createSshConfigStore(new File("dummy"), null, "localUserName");
|
||||
|
||||
assertThat(configStore).isNull();
|
||||
}
|
||||
|
||||
private SshConfigStore.HostConfig getSshHostConfig(String hostName) {
|
||||
return factory.createSshConfigStore(new File("dummy"), new File("dummy"), "localUserName").lookup(hostName, 22,
|
||||
"userName");
|
||||
}
|
||||
|
||||
private void setupSessionFactory(JGitEnvironmentProperties sshKey) {
|
||||
this.factory = new FileBasedSshSessionFactory(sshKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,21 +17,35 @@
|
||||
package org.springframework.cloud.config.server.ssh;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Spliterator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
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 org.eclipse.jgit.transport.OpenSshConfig.Host;
|
||||
import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
|
||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
|
||||
import org.apache.sshd.common.session.SessionContext;
|
||||
import org.eclipse.jgit.transport.SshConfigStore;
|
||||
import org.eclipse.jgit.transport.sshd.ProxyData;
|
||||
import org.eclipse.jgit.transport.sshd.ProxyDataFactory;
|
||||
import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
|
||||
import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.cloud.config.server.environment.JGitEnvironmentProperties;
|
||||
@@ -40,9 +54,7 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -63,21 +75,6 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
|
||||
private PropertyBasedSshSessionFactory factory;
|
||||
|
||||
@Mock
|
||||
private Host hc;
|
||||
|
||||
@Mock
|
||||
private Session session;
|
||||
|
||||
@Mock
|
||||
private JSch jSch;
|
||||
|
||||
@Mock
|
||||
private HostKeyRepository hostKeyRepository;
|
||||
|
||||
@Mock
|
||||
private ProxyHTTP proxyMock;
|
||||
|
||||
public static String getResourceAsString(String path) {
|
||||
try {
|
||||
Resource resource = new ClassPathResource(path);
|
||||
@@ -102,10 +99,9 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
this.factory.configure(this.hc, this.session);
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("gitlab.example.local");
|
||||
|
||||
verify(this.session).setConfig("StrictHostKeyChecking", "no");
|
||||
verifyNoMoreInteractions(this.session);
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isEqualTo("no");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,10 +112,25 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
this.factory.configure(this.hc, this.session);
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("gitlab.example.local");
|
||||
|
||||
verify(this.session).setConfig("StrictHostKeyChecking", "yes");
|
||||
verifyNoMoreInteractions(this.session);
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isEqualTo("yes");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sshConfigIsUsedForRelevantHostOnly() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("ssh://gitlab.example.local:3322/somerepo.git");
|
||||
sshKey.setHostKeyAlgorithm(HOST_KEY_ALGORITHM);
|
||||
sshKey.setHostKey(HOST_KEY);
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
PublicKey configuredKey = toPublicKey(HOST_KEY, HOST_KEY_ALGORITHM);
|
||||
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("another.host");
|
||||
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isNull();
|
||||
assertThat(isKnownKeyForHost(configuredKey, "another.host")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,37 +142,40 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
this.factory.configure(this.hc, this.session);
|
||||
verify(this.session).setConfig("server_host_key", HOST_KEY_ALGORITHM);
|
||||
verify(this.session).setConfig("StrictHostKeyChecking", "yes");
|
||||
verifyNoMoreInteractions(this.session);
|
||||
PublicKey hostKey = getSshHostKey("gitlab.example.local");
|
||||
|
||||
assertThat(hostKey).isNotNull();
|
||||
assertThat(hostKey.getAlgorithm()).isEqualTo(toPublicKey(HOST_KEY, HOST_KEY_ALGORITHM).getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateKeyIsUsed() throws Exception {
|
||||
public void privateKeyIsUsed() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("git@gitlab.example.local:someorg/somerepo.git");
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
this.factory.createSession(this.hc, null, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
|
||||
verify(this.jSch).addIdentity("gitlab.example.local", PRIVATE_KEY.getBytes(), null, null);
|
||||
PrivateKey privateKey = getSshPrivateKey("gitlab.example.local");
|
||||
|
||||
assertThat(privateKey).isNotNull();
|
||||
assertThat(privateKey).isEqualTo(toPrivateKey(PRIVATE_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hostKeyIsUsed() throws Exception {
|
||||
public void hostKeyIsUsed() {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("git@gitlab.example.local:someorg/somerepo.git");
|
||||
sshKey.setHostKeyAlgorithm(HOST_KEY_ALGORITHM);
|
||||
sshKey.setHostKey(HOST_KEY);
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
setupSessionFactory(sshKey);
|
||||
PublicKey configuredKey = toPublicKey(HOST_KEY, HOST_KEY_ALGORITHM);
|
||||
|
||||
this.factory.createSession(this.hc, null, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
|
||||
ArgumentCaptor<HostKey> captor = ArgumentCaptor.forClass(HostKey.class);
|
||||
verify(this.hostKeyRepository).add(captor.capture(), isNull());
|
||||
HostKey hostKey = captor.getValue();
|
||||
assertThat(hostKey.getHost()).isEqualTo("gitlab.example.local");
|
||||
assertThat(hostKey.getKey()).isEqualTo(HOST_KEY);
|
||||
PublicKey knownHostKey = getSshHostKey("gitlab.example.local");
|
||||
|
||||
assertThat(knownHostKey).isNotNull();
|
||||
assertThat(knownHostKey).isEqualTo(configuredKey);
|
||||
assertThat(isKnownKeyForHost(configuredKey, "gitlab.example.local")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -172,30 +186,32 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
sshKey.setPreferredAuthentications("password,keyboard-interactive");
|
||||
setupSessionFactory(sshKey);
|
||||
|
||||
this.factory.configure(this.hc, this.session);
|
||||
verify(this.session).setConfig("PreferredAuthentications", "password,keyboard-interactive");
|
||||
verify(this.session).setConfig("StrictHostKeyChecking", "no");
|
||||
verifyNoMoreInteractions(this.session);
|
||||
SshConfigStore.HostConfig sshConfig = getSshHostConfig("gitlab.example.local");
|
||||
|
||||
assertThat(sshConfig.getValue("PreferredAuthentications")).isEqualTo("password,keyboard-interactive");
|
||||
assertThat(sshConfig.getValue("StrictHostKeyChecking")).isEqualTo("no");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customKnownHostsFileIsUsed() throws Exception {
|
||||
public void customKnownHostsFileIsUsed() throws IOException {
|
||||
JGitEnvironmentProperties sshKey = new JGitEnvironmentProperties();
|
||||
sshKey.setUri("git@gitlab.example.local:someorg/somerepo.git");
|
||||
sshKey.setPrivateKey(PRIVATE_KEY);
|
||||
sshKey.setKnownHostsFile("/ssh/known_hosts");
|
||||
sshKey.setKnownHostsFile(new ClassPathResource("/ssh/known_hosts").getFile().getPath());
|
||||
setupSessionFactory(sshKey);
|
||||
PublicKey configuredKey = toPublicKey(HOST_KEY, HOST_KEY_ALGORITHM);
|
||||
|
||||
this.factory.createSession(this.hc, null, SshUriPropertyProcessor.getHostname(sshKey.getUri()), 22, null);
|
||||
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
|
||||
PublicKey knownHostKey = getSshHostKey("gitlab.example.local");
|
||||
|
||||
verify(this.jSch).setKnownHosts(captor.capture());
|
||||
assertThat(captor.getValue()).isEqualTo("/ssh/known_hosts");
|
||||
assertThat(knownHostKey).isNotNull();
|
||||
assertThat(knownHostKey).isEqualTo(configuredKey);
|
||||
assertThat(isKnownKeyForHost(configuredKey, "gitlab.example.local")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void proxySettingsIsUsed() {
|
||||
public void proxySettingsIsUsed() throws Exception {
|
||||
JGitEnvironmentProperties sshProperties = new JGitEnvironmentProperties();
|
||||
sshProperties.setUri("ssh://gitlab.example.local:3322/somerepo.git");
|
||||
sshProperties.setPrivateKey(PRIVATE_KEY);
|
||||
Map<ProxyHostProperties.ProxyForScheme, ProxyHostProperties> map = new HashMap<>();
|
||||
ProxyHostProperties proxyHostProperties = new ProxyHostProperties();
|
||||
@@ -204,30 +220,99 @@ public class PropertyBasedSshSessionFactoryTest {
|
||||
proxyHostProperties.setUsername("user");
|
||||
proxyHostProperties.setPassword("password");
|
||||
map.put(ProxyHostProperties.ProxyForScheme.HTTP, proxyHostProperties);
|
||||
|
||||
sshProperties.setProxy(map);
|
||||
setupSessionFactory(sshProperties);
|
||||
|
||||
this.factory.configure(this.hc, this.session);
|
||||
ArgumentCaptor<ProxyHTTP> captor = ArgumentCaptor.forClass(ProxyHTTP.class);
|
||||
ProxyData proxyData = getSshProxyData("gitlab.example.local");
|
||||
|
||||
verify(this.session).setProxy(captor.capture());
|
||||
assertThat(captor.getValue()).isNotNull();
|
||||
verify(this.proxyMock).setUserPasswd("user", "password");
|
||||
assertThat(proxyData.getUser()).isEqualTo("user");
|
||||
assertThat(new String(proxyData.getPassword())).isEqualTo("password");
|
||||
assertThat(proxyData.getProxy().type().toString()).isEqualTo("HTTP");
|
||||
assertThat(proxyData.getProxy().address().toString()).containsPattern("host\\.domain.*:8080");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sshConfigFileIsNotUsed() {
|
||||
setupSessionFactory(new JGitEnvironmentProperties());
|
||||
|
||||
assertThat(factory.getSshConfig(new File("."))).isNull();
|
||||
}
|
||||
|
||||
private ProxyData getSshProxyData(String hostname) {
|
||||
try {
|
||||
Field proxies = SshdSessionFactory.class.getDeclaredField("proxies");
|
||||
proxies.setAccessible(true);
|
||||
ProxyDataFactory proxyDataFactory = (ProxyDataFactory) proxies.get(factory);
|
||||
proxies.setAccessible(false);
|
||||
return proxyDataFactory.get(new InetSocketAddress(hostname, 22));
|
||||
}
|
||||
catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey toPublicKey(String key, String algorithm) {
|
||||
try {
|
||||
return new ECDSAPublicKeyEntryDecoder().decodePublicKey(null, algorithm, Base64.getDecoder().decode(key),
|
||||
Collections.emptyMap());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateKey toPrivateKey(String key) {
|
||||
try {
|
||||
Collection<KeyPair> keyPairs = KeyPairUtils.load(null, key);
|
||||
|
||||
return keyPairs.isEmpty() ? null : keyPairs.iterator().next().getPrivate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateKey getSshPrivateKey(String hostName) {
|
||||
SessionContext session = mock(SessionContext.class);
|
||||
when(session.getRemoteAddress()).thenReturn(new InetSocketAddress(hostName, 22));
|
||||
|
||||
List<KeyPair> kayPairs;
|
||||
try {
|
||||
Spliterator<KeyPair> spliterator = ((KeyIdentityProvider) factory.getDefaultKeys(new File(".")))
|
||||
.loadKeys(session).spliterator();
|
||||
|
||||
kayPairs = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
|
||||
}
|
||||
catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return kayPairs.isEmpty() ? null : kayPairs.get(0).getPrivate();
|
||||
}
|
||||
|
||||
private PublicKey getSshHostKey(String hostName) {
|
||||
InetSocketAddress address = new InetSocketAddress(hostName, 22);
|
||||
List<PublicKey> publicKeys = factory.getServerKeyDatabase(null, null).lookup("address", address,
|
||||
mock(ServerKeyDatabase.Configuration.class));
|
||||
|
||||
return publicKeys.isEmpty() ? null : publicKeys.get(0);
|
||||
}
|
||||
|
||||
private boolean isKnownKeyForHost(PublicKey publicKey, String hostName) {
|
||||
InetSocketAddress address = new InetSocketAddress(hostName, 22);
|
||||
return factory.getServerKeyDatabase(null, null).accept("address", address, publicKey,
|
||||
mock(ServerKeyDatabase.Configuration.class), null);
|
||||
}
|
||||
|
||||
private SshConfigStore.HostConfig getSshHostConfig(String hostName) {
|
||||
return factory.createSshConfigStore(new File("dummy"), new File("dummy"), "localUserName").lookup(hostName, 22,
|
||||
"userName");
|
||||
}
|
||||
|
||||
private void setupSessionFactory(JGitEnvironmentProperties sshKey) {
|
||||
Map<String, JGitEnvironmentProperties> sshKeysByHostname = new HashMap<>();
|
||||
sshKeysByHostname.put(SshUriPropertyProcessor.getHostname(sshKey.getUri()), sshKey);
|
||||
this.factory = new PropertyBasedSshSessionFactory(sshKeysByHostname, this.jSch) {
|
||||
|
||||
@Override
|
||||
protected ProxyHTTP createProxy(ProxyHostProperties proxyHostProperties) {
|
||||
return proxyMock;
|
||||
}
|
||||
};
|
||||
when(this.hc.getHostName()).thenReturn(SshUriPropertyProcessor.getHostname(sshKey.getUri()));
|
||||
when(this.jSch.getHostKeyRepository()).thenReturn(this.hostKeyRepository);
|
||||
this.factory = new PropertyBasedSshSessionFactory(sshKeysByHostname);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportedParametersSuccesful() throws Exception {
|
||||
public void supportedParametersSuccessful() {
|
||||
MultipleJGitEnvironmentProperties validSettings = new MultipleJGitEnvironmentProperties();
|
||||
validSettings.setUri(SSH_URI);
|
||||
validSettings.setIgnoreLocalSshSettings(true);
|
||||
@@ -96,7 +96,19 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPrivateKeyFails() throws Exception {
|
||||
public void invalidPrivateKeyFails() {
|
||||
MultipleJGitEnvironmentProperties invalidKey = new MultipleJGitEnvironmentProperties();
|
||||
invalidKey.setUri(SSH_URI);
|
||||
invalidKey.setIgnoreLocalSshSettings(true);
|
||||
invalidKey.setPrivateKey("-----BEGIN OPENSSH PRIVATE KEY-----\nFOOBAR");
|
||||
|
||||
Set<ConstraintViolation<MultipleJGitEnvironmentProperties>> constraintViolations = validator
|
||||
.validate(invalidKey);
|
||||
assertThat(constraintViolations).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dummyPrivateKeyFails() {
|
||||
MultipleJGitEnvironmentProperties invalidKey = new MultipleJGitEnvironmentProperties();
|
||||
invalidKey.setUri(SSH_URI);
|
||||
invalidKey.setIgnoreLocalSshSettings(true);
|
||||
@@ -108,7 +120,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPrivateKeyFails() throws Exception {
|
||||
public void missingPrivateKeyFails() {
|
||||
MultipleJGitEnvironmentProperties missingKey = new MultipleJGitEnvironmentProperties();
|
||||
missingKey.setUri(SSH_URI);
|
||||
missingKey.setIgnoreLocalSshSettings(true);
|
||||
@@ -119,7 +131,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hostKeyWithMissingAlgoFails() throws Exception {
|
||||
public void hostKeyWithMissingAlgoFails() {
|
||||
MultipleJGitEnvironmentProperties missingAlgo = new MultipleJGitEnvironmentProperties();
|
||||
missingAlgo.setUri(SSH_URI);
|
||||
missingAlgo.setIgnoreLocalSshSettings(true);
|
||||
@@ -132,7 +144,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void algoWithMissingHostKeyFails() throws Exception {
|
||||
public void algoWithMissingHostKeyFails() {
|
||||
MultipleJGitEnvironmentProperties missingHostKey = new MultipleJGitEnvironmentProperties();
|
||||
missingHostKey.setUri(SSH_URI);
|
||||
missingHostKey.setIgnoreLocalSshSettings(true);
|
||||
@@ -145,7 +157,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedAlgoFails() throws Exception {
|
||||
public void unsupportedAlgoFails() {
|
||||
MultipleJGitEnvironmentProperties unsupportedAlgo = new MultipleJGitEnvironmentProperties();
|
||||
unsupportedAlgo.setUri(SSH_URI);
|
||||
unsupportedAlgo.setIgnoreLocalSshSettings(true);
|
||||
@@ -159,7 +171,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatorNotRunIfIgnoreLocalSettingsFalse() throws Exception {
|
||||
public void validatorNotRunIfIgnoreLocalSettingsFalse() {
|
||||
MultipleJGitEnvironmentProperties useLocal = new MultipleJGitEnvironmentProperties();
|
||||
useLocal.setUri(SSH_URI);
|
||||
useLocal.setIgnoreLocalSshSettings(false);
|
||||
@@ -171,7 +183,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatorNotRunIfHttpsUri() throws Exception {
|
||||
public void validatorNotRunIfHttpsUri() {
|
||||
MultipleJGitEnvironmentProperties httpsUri = new MultipleJGitEnvironmentProperties();
|
||||
httpsUri.setUri("https://somerepo.com/team/project.git");
|
||||
httpsUri.setIgnoreLocalSshSettings(true);
|
||||
@@ -183,7 +195,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preferredAuthenticationsIsValidated() throws Exception {
|
||||
public void preferredAuthenticationsIsValidated() {
|
||||
MultipleJGitEnvironmentProperties sshUriProperties = new MultipleJGitEnvironmentProperties();
|
||||
assertThat(validator.validate(sshUriProperties)).hasSize(0);
|
||||
|
||||
@@ -195,7 +207,7 @@ public class SshPropertyValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void knowHostsFileIsValidated() throws Exception {
|
||||
public void knowHostsFileIsValidated() {
|
||||
MultipleJGitEnvironmentProperties sshUriProperties = new MultipleJGitEnvironmentProperties();
|
||||
assertThat(validator.validate(sshUriProperties)).hasSize(0);
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
gitlab.example.local ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMzCa0AcNbahUFjFYJHIilhJOhKFHuDOOuY+/HqV9kALftitwNYo6dQ+tC9IK5JVZCZfqKfDWVMxspcPDf9eMoE=
|
||||
@@ -7,30 +7,41 @@ spring:
|
||||
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-----
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAYEAw9e+uwqtqnXCuxqeCoterQzT95Mx1XwAlIEsee+0xyzbQ54hlXTw
|
||||
p/8kFRue0eq08VYOIFt/CkpDNvGiNZDYa1HPzYRbxgU1Mi73c58VbRdghg8hGEF8hs29tQ
|
||||
bAyi1E7KoPfXn4Jhqp57PoUB2StuBwj9VU8DR7t4QvTx1XZmIh6ZND7pRKjyvzDCFSG7cO
|
||||
bMpiWgVkDmmZWbRGY/el/GHfqah5cTEyBN8/HYXOL5opymQfmhd66t1/Yh0sacEBnbvHjD
|
||||
Q1yIhf/tFXbuunacGLzP30XimexQOJ8CnSgFBBgu360saS5xfGJVoIgvnm44Irbw/Mtuxj
|
||||
i6VFaWEfJNvGvhFIAVMf98cHZ+mdLpQIxFm+AUBcAPVGoI6kiXr6LucSWigz+TmeQ3T/Y1
|
||||
hSlofgN4KudZWpxUPJKDU3kQ2WAdVIo22/tO7egJsnd9os+fzxaas+yaaZ2QtLDCaHBxO0
|
||||
78o2TPlAqxzgt+yvm0NYZ+G0XBazJMn49qCL91T5AAAFmHD1ZR9w9WUfAAAAB3NzaC1yc2
|
||||
EAAAGBAMPXvrsKrap1wrsangqLXq0M0/eTMdV8AJSBLHnvtMcs20OeIZV08Kf/JBUbntHq
|
||||
tPFWDiBbfwpKQzbxojWQ2GtRz82EW8YFNTIu93OfFW0XYIYPIRhBfIbNvbUGwMotROyqD3
|
||||
15+CYaqeez6FAdkrbgcI/VVPA0e7eEL08dV2ZiIemTQ+6USo8r8wwhUhu3DmzKYloFZA5p
|
||||
mVm0RmP3pfxh36moeXExMgTfPx2Fzi+aKcpkH5oXeurdf2IdLGnBAZ27x4w0NciIX/7RV2
|
||||
7rp2nBi8z99F4pnsUDifAp0oBQQYLt+tLGkucXxiVaCIL55uOCK28PzLbsY4ulRWlhHyTb
|
||||
xr4RSAFTH/fHB2fpnS6UCMRZvgFAXAD1RqCOpIl6+i7nElooM/k5nkN0/2NYUpaH4DeCrn
|
||||
WVqcVDySg1N5ENlgHVSKNtv7Tu3oCbJ3faLPn88WmrPsmmmdkLSwwmhwcTtO/KNkz5QKsc
|
||||
4Lfsr5tDWGfhtFwWsyTJ+Pagi/dU+QAAAAMBAAEAAAGAAc1LYPcxL99Tgls1Vw1/OoJitO
|
||||
Vy0O8KJlOl8B1HgYmlHtMmpfRkfnc3gsY1SOMq9QmAqcWNvq9+PNQuVOXXR+2BxvdPzNuh
|
||||
aKvL9RFiphVP+wvKlymLFsZv12mPfoy6FJ9f8xybLuaR56LdIVeUUQBxqLEize79sGuT79
|
||||
tqQXPnsfl754cPxI939gWcdsrRZCjcjM195TANjCi/eQ3/Rfo0j6AQNf1o42iWcRQZGJEH
|
||||
j2gqbkWHCQAuut1f2j0/XXApnY2YVnD6brfTDo0P6AX1DWV9DvCkWwiI6/xYWUPmyjmE2x
|
||||
JiDDeGWkI6WihnMlMObc8N8y/C5r2Oaw/WVtNSxiP2JaLBHq7lF3cGOzTfKrwS0lXtw1z6
|
||||
nJS/OwKiplvTDcNKUIFAKlryb6psHSFuHKneFPB3hNz0Pznth3EmXoxoAgGD3vMJQD40sF
|
||||
BIsSS6AuQVHstbLg6AZ7g6eTCMt+JH9Rbpt3hhznR5SZz4rgi49txFV0x/fN6JOm+NAAAA
|
||||
wQCuAd8WCe4sQkTkpaSt0qEvqifGxLFnRLoLpVBQvBiJAFDn6f9JhpWHoND6YBYg9T8OXp
|
||||
vJBED+v6TIfmynxt53S8MhadzwnSaqhf+arW+Q+H56acOtmEnlxv+epbSdFraGT6XExsdG
|
||||
9zeVV2FTpaOG8s9Dye5zg7FYUAazjlxEnj8IavfkSV5htF7k6nuNADauw35v9jfEPbLvGN
|
||||
L0jY277Bj55a2ytRuHqbZHm1lrOe9iK4XOddehBlXEsvIGWuUAAADBAOQcUn7EtxSYmUL+
|
||||
mqn8+biEC8+7dgsv6mJvOETJhcRE3mbSgYO03UxrtOhcylUf0ks0mYvZugvTHN2dsdG9eT
|
||||
1ds0nZvOoKXwkSSJCH83+J7+jnI9PxK+nhqppZ3RtjKaaRDHmaS5zQZi6ZahSKGPx0KszB
|
||||
+SmrzUJLpUq9cD7kDC3TydN2prGIF7RSgl6KhiHXm5UbYmFctNhMhyb1GPPDY9RWeo5zFX
|
||||
VcXjY8MCXWiBYLCtbEPfcIAzHQcpiv+wAAAMEA28l23xIkQnMDkoiJqiK0BjT6G5joRzcj
|
||||
VDD+qZCOR5z7d617mvYQM7B+3Mu8h4jXk4aQPyHMF3riRj8f2pvWBVqVl5EAAvKsywm/9l
|
||||
LuyVFDDd3GlJRYuO/1XYKM8i7iXa4nKrQVcxiyF8gkiyafrqYSxuM0UTMc5vX4fCxQSqgh
|
||||
8WUG6n1OTwJ/x9grdXLi8rbjXaZbZfax4eOBJeEb9gKqE8+Ak0aRyXOMw8ygiUTIQELr7T
|
||||
xLwZE6Y5ULy9ibAAAAHm1zaGFtc2lAbXNoYW1zaS1hMDIudm13YXJlLmNvbQECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
@@ -6,30 +6,41 @@ spring:
|
||||
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-----
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAYEAw9e+uwqtqnXCuxqeCoterQzT95Mx1XwAlIEsee+0xyzbQ54hlXTw
|
||||
p/8kFRue0eq08VYOIFt/CkpDNvGiNZDYa1HPzYRbxgU1Mi73c58VbRdghg8hGEF8hs29tQ
|
||||
bAyi1E7KoPfXn4Jhqp57PoUB2StuBwj9VU8DR7t4QvTx1XZmIh6ZND7pRKjyvzDCFSG7cO
|
||||
bMpiWgVkDmmZWbRGY/el/GHfqah5cTEyBN8/HYXOL5opymQfmhd66t1/Yh0sacEBnbvHjD
|
||||
Q1yIhf/tFXbuunacGLzP30XimexQOJ8CnSgFBBgu360saS5xfGJVoIgvnm44Irbw/Mtuxj
|
||||
i6VFaWEfJNvGvhFIAVMf98cHZ+mdLpQIxFm+AUBcAPVGoI6kiXr6LucSWigz+TmeQ3T/Y1
|
||||
hSlofgN4KudZWpxUPJKDU3kQ2WAdVIo22/tO7egJsnd9os+fzxaas+yaaZ2QtLDCaHBxO0
|
||||
78o2TPlAqxzgt+yvm0NYZ+G0XBazJMn49qCL91T5AAAFmHD1ZR9w9WUfAAAAB3NzaC1yc2
|
||||
EAAAGBAMPXvrsKrap1wrsangqLXq0M0/eTMdV8AJSBLHnvtMcs20OeIZV08Kf/JBUbntHq
|
||||
tPFWDiBbfwpKQzbxojWQ2GtRz82EW8YFNTIu93OfFW0XYIYPIRhBfIbNvbUGwMotROyqD3
|
||||
15+CYaqeez6FAdkrbgcI/VVPA0e7eEL08dV2ZiIemTQ+6USo8r8wwhUhu3DmzKYloFZA5p
|
||||
mVm0RmP3pfxh36moeXExMgTfPx2Fzi+aKcpkH5oXeurdf2IdLGnBAZ27x4w0NciIX/7RV2
|
||||
7rp2nBi8z99F4pnsUDifAp0oBQQYLt+tLGkucXxiVaCIL55uOCK28PzLbsY4ulRWlhHyTb
|
||||
xr4RSAFTH/fHB2fpnS6UCMRZvgFAXAD1RqCOpIl6+i7nElooM/k5nkN0/2NYUpaH4DeCrn
|
||||
WVqcVDySg1N5ENlgHVSKNtv7Tu3oCbJ3faLPn88WmrPsmmmdkLSwwmhwcTtO/KNkz5QKsc
|
||||
4Lfsr5tDWGfhtFwWsyTJ+Pagi/dU+QAAAAMBAAEAAAGAAc1LYPcxL99Tgls1Vw1/OoJitO
|
||||
Vy0O8KJlOl8B1HgYmlHtMmpfRkfnc3gsY1SOMq9QmAqcWNvq9+PNQuVOXXR+2BxvdPzNuh
|
||||
aKvL9RFiphVP+wvKlymLFsZv12mPfoy6FJ9f8xybLuaR56LdIVeUUQBxqLEize79sGuT79
|
||||
tqQXPnsfl754cPxI939gWcdsrRZCjcjM195TANjCi/eQ3/Rfo0j6AQNf1o42iWcRQZGJEH
|
||||
j2gqbkWHCQAuut1f2j0/XXApnY2YVnD6brfTDo0P6AX1DWV9DvCkWwiI6/xYWUPmyjmE2x
|
||||
JiDDeGWkI6WihnMlMObc8N8y/C5r2Oaw/WVtNSxiP2JaLBHq7lF3cGOzTfKrwS0lXtw1z6
|
||||
nJS/OwKiplvTDcNKUIFAKlryb6psHSFuHKneFPB3hNz0Pznth3EmXoxoAgGD3vMJQD40sF
|
||||
BIsSS6AuQVHstbLg6AZ7g6eTCMt+JH9Rbpt3hhznR5SZz4rgi49txFV0x/fN6JOm+NAAAA
|
||||
wQCuAd8WCe4sQkTkpaSt0qEvqifGxLFnRLoLpVBQvBiJAFDn6f9JhpWHoND6YBYg9T8OXp
|
||||
vJBED+v6TIfmynxt53S8MhadzwnSaqhf+arW+Q+H56acOtmEnlxv+epbSdFraGT6XExsdG
|
||||
9zeVV2FTpaOG8s9Dye5zg7FYUAazjlxEnj8IavfkSV5htF7k6nuNADauw35v9jfEPbLvGN
|
||||
L0jY277Bj55a2ytRuHqbZHm1lrOe9iK4XOddehBlXEsvIGWuUAAADBAOQcUn7EtxSYmUL+
|
||||
mqn8+biEC8+7dgsv6mJvOETJhcRE3mbSgYO03UxrtOhcylUf0ks0mYvZugvTHN2dsdG9eT
|
||||
1ds0nZvOoKXwkSSJCH83+J7+jnI9PxK+nhqppZ3RtjKaaRDHmaS5zQZi6ZahSKGPx0KszB
|
||||
+SmrzUJLpUq9cD7kDC3TydN2prGIF7RSgl6KhiHXm5UbYmFctNhMhyb1GPPDY9RWeo5zFX
|
||||
VcXjY8MCXWiBYLCtbEPfcIAzHQcpiv+wAAAMEA28l23xIkQnMDkoiJqiK0BjT6G5joRzcj
|
||||
VDD+qZCOR5z7d617mvYQM7B+3Mu8h4jXk4aQPyHMF3riRj8f2pvWBVqVl5EAAvKsywm/9l
|
||||
LuyVFDDd3GlJRYuO/1XYKM8i7iXa4nKrQVcxiyF8gkiyafrqYSxuM0UTMc5vX4fCxQSqgh
|
||||
8WUG6n1OTwJ/x9grdXLi8rbjXaZbZfax4eOBJeEb9gKqE8+Ak0aRyXOMw8ygiUTIQELr7T
|
||||
xLwZE6Y5ULy9ibAAAAHm1zaGFtc2lAbXNoYW1zaS1hMDIudm13YXJlLmNvbQECAwQ=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
Reference in New Issue
Block a user