Skip to content
Permalink
Browse files

[FIXED JENKINS-42959] Specify preferred host keys during connect

  • Loading branch information
mc1arke committed May 16, 2017
1 parent 860a3a9 commit 9a0fdc706d2862d79b8bc1c4c63df87049f4a71c
11 pom.xml
@@ -16,8 +16,8 @@
<description>Allows to launch agents over SSH, using a Java implementation of the SSH protocol</description>

<properties>
<jenkins.version>1.609.1</jenkins.version>
<java.level>6</java.level>
<jenkins.version>1.625</jenkins.version>
<java.level>7</java.level>
<jenkins-test-harness.version>2.18</jenkins-test-harness.version>
</properties>

@@ -62,6 +62,13 @@

<dependencies>
<!-- regular dependencies -->
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>trilead-ssh2</artifactId>
<version>build217-jenkins-9</version>
<scope>provided</scope>
<!-- we only need the newer version for testing, we use the bundled version during execution -->
</dependency>
<!-- plugin dependencies -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
@@ -786,6 +786,7 @@ public synchronized void launch(final SlaveComputer computer, final TaskListener
public Boolean call() throws InterruptedException {
Boolean rval = Boolean.FALSE;
try {
connection.setServerHostKeyAlgorithms(sshHostKeyVerificationStrategy.getPreferredKeyAlgorithms(computer));

openConnection(listener, computer);

@@ -0,0 +1,39 @@
package hudson.plugins.sshslaves.verifiers;

import com.trilead.ssh2.signature.KeyAlgorithm;
import com.trilead.ssh2.signature.KeyAlgorithmManager;
import hudson.plugins.sshslaves.Messages;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* @author Michael Clarke
*/
class JenkinsTrilead9VersionSupport extends TrileadVersionSupportManager.TrileadVersionSupport {

@Override
public String[] getSupportedAlgorithms() {
List<String> algorithms = new ArrayList<>();
for (KeyAlgorithm<?, ?> algorithm : KeyAlgorithmManager.getSupportedAlgorithms()) {
algorithms.add(algorithm.getKeyFormat());
}
return algorithms.toArray(new String[algorithms.size()]);
}

@Override
public HostKey parseKey(String algorithm, byte[] keyValue) {
for (KeyAlgorithm<?, ?> keyAlgorithm : KeyAlgorithmManager.getSupportedAlgorithms()) {
try {
if (keyAlgorithm.getKeyFormat().equals(algorithm)) {
keyAlgorithm.decodePublicKey(keyValue);
return new HostKey(algorithm, keyValue);
}
} catch (IOException ex) {
throw new IllegalArgumentException(Messages.ManualKeyProvidedHostKeyVerifier_KeyValueDoesNotParse(algorithm), ex);
}
}
throw new IllegalArgumentException("Unexpected key algorithm " + algorithm);
}
}
@@ -24,6 +24,7 @@
package hudson.plugins.sshslaves.verifiers;

import java.io.File;
import java.io.IOException;

import org.kohsuke.stapler.DataBoundConstructor;

@@ -73,6 +74,16 @@ public boolean verify(SlaveComputer computer, HostKey hostKey, TaskListener list
}

}

@Override
public String[] getPreferredKeyAlgorithms(SlaveComputer computer) throws IOException {
if (!KNOWN_HOSTS_FILE.exists()) {
return super.getPreferredKeyAlgorithms(computer);
}

KnownHosts knownHosts = new KnownHosts(KNOWN_HOSTS_FILE);
return knownHosts.getPreferredServerHostkeyAlgorithmOrder(((SSHLauncher) computer.getLauncher()).getHost());
}

@Extension
public static class KnownHostsFileKeyVerificationStrategyDescriptor extends SshHostKeyVerificationStrategyDescriptor {
@@ -24,13 +24,14 @@
package hudson.plugins.sshslaves.verifiers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;

import hudson.Extension;
import hudson.model.TaskListener;
@@ -75,6 +76,16 @@ public boolean verify(SlaveComputer computer, HostKey hostKey, TaskListener list
return false;
}
}

@Override
public String[] getPreferredKeyAlgorithms(SlaveComputer computer) throws IOException {
List<String> sortedAlgorithms = new ArrayList<>(Arrays.asList(super.getPreferredKeyAlgorithms(computer)));

sortedAlgorithms.remove(key.getAlgorithm());
sortedAlgorithms.add(0, key.getAlgorithm());

return sortedAlgorithms.toArray(new String[sortedAlgorithms.size()]);
}

private static HostKey parseKey(String key) {
if (!key.contains(" ")) {
@@ -87,22 +98,7 @@ private static HostKey parseKey(String key) {
throw new IllegalArgumentException(Messages.ManualKeyProvidedHostKeyVerifier_Base64EncodedKeyValueRequired());
}

try {
if ("ssh-rsa".equals(algorithm)) {
RSASHA1Verify.decodeSSHRSAPublicKey(keyValue);
} else if ("ssh-dss".equals(algorithm)) {
DSASHA1Verify.decodeSSHDSAPublicKey(keyValue);
} else {
throw new IllegalArgumentException("Key algorithm should be one of ssh-rsa or ssh-dss");
}
} catch (IOException ex) {
throw new IllegalArgumentException(Messages.ManualKeyProvidedHostKeyVerifier_KeyValueDoesNotParse(algorithm), ex);
} catch (StringIndexOutOfBoundsException ex) {
// can happen in DSASHA1Verifier with certain values (from quick testing)
throw new IllegalArgumentException(Messages.ManualKeyProvidedHostKeyVerifier_KeyValueDoesNotParse(algorithm), ex);
}

return new HostKey(algorithm, keyValue);
return TrileadVersionSupportManager.getTrileadSupport().parseKey(algorithm, keyValue);
}

@Extension
@@ -33,6 +33,8 @@
import hudson.slaves.SlaveComputer;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -98,6 +100,24 @@ else if (!existingHostKey.equals(hostKey)) {
}
}

@Override
public String[] getPreferredKeyAlgorithms(SlaveComputer computer) throws IOException {
String[] algorithms = super.getPreferredKeyAlgorithms(computer);

HostKey hostKey = HostKeyHelper.getInstance().getHostKey(computer);

if (null != hostKey) {
List<String> sortedAlgorithms = new ArrayList<>(Arrays.asList(algorithms));

sortedAlgorithms.remove(hostKey.getAlgorithm());
sortedAlgorithms.add(0, hostKey.getAlgorithm());

algorithms = sortedAlgorithms.toArray(new String[sortedAlgorithms.size()]);
}

return algorithms;
}

/** TODO replace with {@link Computer#addAction} after core baseline picks up JENKINS-42969 fix */
private static void addAction(@Nonnull Computer c, @Nonnull Action a) {
try {
@@ -29,6 +29,8 @@
import hudson.slaves.SlaveComputer;
import jenkins.model.Jenkins;

import java.io.IOException;

/**
* A method for verifying the host key provided by the remote host during the
* initiation of each connection.
@@ -53,6 +55,10 @@ public SshHostKeyVerificationStrategyDescriptor getDescriptor() {
* @since 1.12
*/
public abstract boolean verify(SlaveComputer computer, HostKey hostKey, TaskListener listener) throws Exception;

public String[] getPreferredKeyAlgorithms(SlaveComputer computer) throws IOException {
return TrileadVersionSupportManager.getTrileadSupport().getSupportedAlgorithms();
}

public static abstract class SshHostKeyVerificationStrategyDescriptor extends Descriptor<SshHostKeyVerificationStrategy> {

@@ -0,0 +1,70 @@
package hudson.plugins.sshslaves.verifiers;

import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.RSASHA1Verify;
import hudson.plugins.sshslaves.Messages;

import java.io.IOException;

/**
* @author Michael Clarke
* @since 1.18
*/
final class TrileadVersionSupportManager {

static TrileadVersionSupport getTrileadSupport() {
try {
Thread.currentThread().getContextClassLoader().loadClass("com.trilead.ssh2.signature.KeyAlgorithmManager");
return createaVersion9Instance();
} catch (ReflectiveOperationException e) {
// KeyAlgorithmManager doesn't exist, fall back to legacy trilead handler
return new LegacyTrileadVersionSupport();
}
}

private static TrileadVersionSupport createaVersion9Instance() {
try {
return (TrileadVersionSupport) Class.forName("hudson.plugins.sshslaves.verifiers.JenkinsTrilead9VersionSupport").newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("Could not create Trilead support class", e);
}

}

public abstract static class TrileadVersionSupport {

/*package*/ TrileadVersionSupport() {
super();
}

public abstract String[] getSupportedAlgorithms();

public abstract HostKey parseKey(String algorithm, byte[] keyValue);
}

private static class LegacyTrileadVersionSupport extends TrileadVersionSupport {

@Override
public String[] getSupportedAlgorithms() {
return new String[]{"ssh-rsa", "ssh-dss"};
}

@Override
public HostKey parseKey(String algorithm, byte[] keyValue) {
try {
if ("ssh-rsa".equals(algorithm)) {
RSASHA1Verify.decodeSSHRSAPublicKey(keyValue);
} else if ("ssh-dss".equals(algorithm)) {
DSASHA1Verify.decodeSSHDSAPublicKey(keyValue);
} else {
throw new IllegalArgumentException("Key algorithm should be one of ssh-rsa or ssh-dss");
}
} catch (IOException | StringIndexOutOfBoundsException ex) {
throw new IllegalArgumentException(Messages.ManualKeyProvidedHostKeyVerifier_KeyValueDoesNotParse(algorithm), ex);
}

return new HostKey(algorithm, keyValue);
}
}

}
@@ -0,0 +1,40 @@
package hudson.plugins.sshslaves.verifiers;

import org.apache.commons.io.IOUtils;
import org.junit.Test;

import java.io.IOException;

import static org.junit.Assert.assertArrayEquals;

/**
* @author Michael Clarke
*/
public class ManuallyProvidedKeyVerificationStrategyTest {

@Test
public void testRsa() throws IOException {
ManuallyProvidedKeyVerificationStrategy testCase = new ManuallyProvidedKeyVerificationStrategy("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAtqwn/v4+sYBD0e5UT59zGjQ+iBOJvKbqVX22vt4hFIVrbwmB+HKJGwOINe1gnc/syPGj/5c6yoOnjTdpI/xerip6RjVPRTQVh2nNjsbXIS5epi/39nnPFZ/0hE3ozOtQ1j9OS5bXVBD770ha1UFnCql4DfcWj+y1QVYvm53p2fID+an0HNunnZjq+r2UJgt138lkZN2K7S42U/apqOHStFGVPxF+gmK1fI021QI+QjxfKOoyGNCpbAaMM6jzikqCJOE8M7jpSZgHMO2x+wvjMK8p2uXAaZlYJeUlEqUVGa9jjkdEiTPabFJyrKORrTWX7Ahs6C4vCAgWmNZzOmOvnw== rsa-key-20170516");
assertArrayEquals(new String[]{"ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", "ssh-dss"}, testCase.getPreferredKeyAlgorithms(null));
}

@Test
public void testEd25519() throws IOException {
ManuallyProvidedKeyVerificationStrategy testCase = new ManuallyProvidedKeyVerificationStrategy("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQPcXch45Uak9iiHt1puffR6LHZxZsHU0iyeyUnf5qW ed25519-key-20170516");
assertArrayEquals(new String[]{"ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", "ssh-rsa", "ssh-dss"}, testCase.getPreferredKeyAlgorithms(null));
}


@Test
public void testEcdsa() throws IOException {
ManuallyProvidedKeyVerificationStrategy testCase = new ManuallyProvidedKeyVerificationStrategy("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMQMVHTpplIuqEcOR8j7wzydDUzXF0Fl82WluEJphpo2JKbJ4DNaL3Zu6bfeDQGuH3hWtG1H0r4ntoDtN940GGA= ecdsa-key-20170516");
assertArrayEquals(new String[]{"ecdsa-sha2-nistp256", "ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ssh-rsa", "ssh-dss"}, testCase.getPreferredKeyAlgorithms(null));
}

@Test
public void testDsa() throws IOException {
ManuallyProvidedKeyVerificationStrategy testCase = new ManuallyProvidedKeyVerificationStrategy("ssh-dss AAAAB3NzaC1kc3MAAAAhAOD3H2nbagBMaZ7XDnGUBO3vuqi3McIC9A+smJH9lsnzAAAAFQD3lLxlCXN8K4CeNCJdHeXEpeE7vwAAACBtZ3osIr0OtX6uKFumP6ybXGrfiy7otYqmSPwS+A2MywAAACEA34SUyAprA9HHPmRqZnJ6Acgq6KKRrh4SKTPUdJa8aBc= dsa-key-20170516");
assertArrayEquals(new String[]{"ssh-dss", "ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", "ssh-rsa"}, testCase.getPreferredKeyAlgorithms(null));
}

}
@@ -0,0 +1,44 @@
package hudson.plugins.sshslaves.verifiers;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* @author Michael Clarke
*/
public class TrileadVersionSupportManagerTest {

@Test
public void testLegacyInstance() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(new BlockingClassloader(Thread.currentThread().getContextClassLoader()));
String name = TrileadVersionSupportManager.getTrileadSupport().getClass().getName();
assertEquals("hudson.plugins.sshslaves.verifiers.TrileadVersionSupportManager$LegacyTrileadVersionSupport", name);
} finally {
Thread.currentThread().setContextClassLoader(loader);
}
}

@Test
public void testCurrentInstance() {
assertEquals(JenkinsTrilead9VersionSupport.class, TrileadVersionSupportManager.getTrileadSupport().getClass());
}


private static class BlockingClassloader extends ClassLoader {

public BlockingClassloader(ClassLoader parent) {
super(parent);
}

public Class<?> loadClass(String className) throws ClassNotFoundException {
if ("com.trilead.ssh2.signature.KeyAlgorithmManager".equals(className)) {
throw new ClassNotFoundException(className);
}
return super.loadClass(className);
}

}
}

0 comments on commit 9a0fdc7

Please sign in to comment.
You can’t perform that action at this time.