diff --git a/README.md b/README.md index 05deffe3a..20472b824 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ * [Strategies](#strategies) * [Check New Hard](#check-new-hard) * [Check New Soft](#check-new-soft) + * [Check Static](#check-static) * [Accept New](#accept-new) * [Off](#off) * [New AMIs](#new-amis) @@ -515,6 +516,18 @@ when upgrading from a previous version of the plugin. _Check New Hard_ is the sa consider migrating to it. We recommend, whenever possible, configuring each AMI with _Stop/Disconnect on Idle Timeout_ to take advantage of the ssh host key cache allowing next connections to be done faster. +#### Check Static + +This strategy checks the SSH host key provided by the `Static Host Keys` field in the slave template. +If the key is not found, the plugin **doesn't allow** the connection to the instance to +guarantee the instance is the right one. If the key is found and it is the same as the one presented by the instance, +then it's saved to be used on future connections, so the console is only checked once. + +The expected format of the `Static Host Keys` field is `algorithm base64-public-key`. For example: +``` +ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow +``` + #### Accept New This strategy doesn't check any key on the console. It accepts the key provided by the instance on the first connection. Then, the key is saved to be used on future connections to detect a Man-in-the-Middle attack (the host diff --git a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java index 69b8af71a..45bcc11fd 100644 --- a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java +++ b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java @@ -27,12 +27,14 @@ import hudson.plugins.ec2.ssh.verifiers.AcceptNewStrategy; import hudson.plugins.ec2.ssh.verifiers.CheckNewHardStrategy; import hudson.plugins.ec2.ssh.verifiers.CheckNewSoftStrategy; +import hudson.plugins.ec2.ssh.verifiers.CheckStaticStrategy; import hudson.plugins.ec2.ssh.verifiers.NonVerifyingKeyVerificationStrategy; import hudson.plugins.ec2.ssh.verifiers.SshHostKeyVerificationStrategy; public enum HostKeyVerificationStrategyEnum { CHECK_NEW_HARD("check-new-hard", "yes", new CheckNewHardStrategy()), CHECK_NEW_SOFT("check-new-soft", "accept-new", new CheckNewSoftStrategy()), + CHECK_STATIC("check-static", "yes", new CheckStaticStrategy()), ACCEPT_NEW("accept-new", "accept-new", new AcceptNewStrategy()), OFF("off", "no", new NonVerifyingKeyVerificationStrategy()); diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index e402d3f85..15dcd498f 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -23,20 +23,16 @@ import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.AmazonEC2Exception; -import com.amazonaws.services.ec2.model.AvailabilityZone; import com.amazonaws.services.ec2.model.BlockDeviceMapping; import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest; import com.amazonaws.services.ec2.model.CreateTagsRequest; import com.amazonaws.services.ec2.model.CreditSpecificationRequest; -import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest; import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryRequest; -import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryResult; import com.amazonaws.services.ec2.model.DescribeSubnetsRequest; import com.amazonaws.services.ec2.model.DescribeSubnetsResult; import com.amazonaws.services.ec2.model.Filter; @@ -61,7 +57,6 @@ import com.amazonaws.services.ec2.model.SpotInstanceRequest; import com.amazonaws.services.ec2.model.SpotMarketOptions; import com.amazonaws.services.ec2.model.SpotPlacement; -import com.amazonaws.services.ec2.model.SpotPrice; import com.amazonaws.services.ec2.model.StartInstancesRequest; import com.amazonaws.services.ec2.model.StartInstancesResult; import com.amazonaws.services.ec2.model.Subnet; @@ -112,11 +107,9 @@ import java.util.Base64; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -127,42 +120,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.servlet.ServletException; - import edu.umd.cs.findbugs.annotations.NonNull; -import hudson.plugins.ec2.util.*; -import hudson.XmlFile; -import hudson.model.listeners.SaveableListener; import hudson.security.Permission; -import hudson.util.Secret; -import jenkins.model.Jenkins; -import jenkins.model.JenkinsLocationConfiguration; -import jenkins.slaves.iterators.api.NodeIterator; - -import org.apache.commons.lang.StringUtils; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.QueryParameter; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.*; -import hudson.Extension; -import hudson.Util; -import hudson.model.*; -import hudson.model.Descriptor.FormException; -import hudson.model.labels.LabelAtom; -import hudson.slaves.NodeProperty; -import hudson.slaves.NodePropertyDescriptor; -import hudson.util.DescribableList; -import hudson.util.FormValidation; -import hudson.util.ListBoxModel; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -238,6 +199,8 @@ public class SlaveTemplate implements Describable { public HostKeyVerificationStrategyEnum hostKeyVerificationStrategy; + public String staticHostKeys; + public final boolean associatePublicIp; protected transient EC2Cloud parent; @@ -292,7 +255,7 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) { + List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, String staticHostKeys) { if(StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)){ LOGGER.log(Level.FINE, "As remoteAdmin, jvmopts or tmpDir is not blank, we must ensure the user has ADMINISTER rights."); // Can be null during tests @@ -355,21 +318,22 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri this.customDeviceMapping = customDeviceMapping; this.t2Unlimited = t2Unlimited; - this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; + this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; + this.staticHostKeys = staticHostKeys; readResolve(); // initialize } @Deprecated public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, + boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, + boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, + List> nodeProperties, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, @@ -378,39 +342,39 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, null); + nodeProperties, null, staticHostKeys); } @Deprecated public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,List> nodeProperties ) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, + String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, + boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, + boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, List> nodeProperties, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, - connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, nodeProperties); + connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, nodeProperties, staticHostKeys); } @Deprecated public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, + String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, + boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, + boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, - connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, Collections.emptyList()); + connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, Collections.emptyList(), staticHostKeys); } @Deprecated @@ -421,67 +385,67 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) { + boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - monitoring, t2Unlimited, connectionStrategy, maxTotalUses); + monitoring, t2Unlimited, connectionStrategy, maxTotalUses, staticHostKeys); } @Deprecated public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp, boolean monitoring, - boolean t2Unlimited) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, + boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, + boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp, boolean monitoring, + boolean t2Unlimited, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - monitoring, t2Unlimited, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp), -1); + monitoring, t2Unlimited, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp), -1, staticHostKeys); } public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, + boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, + boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - connectUsingPublicIp, false, false); + connectUsingPublicIp, false, false, staticHostKeys); } - + // 1 public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, - boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, - boolean connectBySSHProcess) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, + boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, + boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, + boolean connectBySSHProcess, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, false, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, false); + useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, false, staticHostKeys); } - + // 2 public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, - boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping) { + InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, + String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, + boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, + boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, + boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, String staticHostKeys) { this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, false); + useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, false, staticHostKeys); } /** @@ -496,7 +460,7 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, tmpDir, userData, numExecutors, remoteAdmin, new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort), jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, - useEphemeralDevices, false, launchTimeoutStr, false, null); + useEphemeralDevices, false, launchTimeoutStr, false, null, ""); } public boolean isConnectBySSHProcess() { @@ -738,7 +702,11 @@ public void setHostKeyVerificationStrategy(HostKeyVerificationStrategyEnum hostK public HostKeyVerificationStrategyEnum getHostKeyVerificationStrategy() { return hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; } - + + public String getStaticHostKeys() { + return staticHostKeys; + } + @Override public String toString() { return "SlaveTemplate{" + diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index 433820f75..65e6393e7 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -401,7 +401,6 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla conn.setProxyData(proxyData); logInfo(computer, listener, "Using HTTP Proxy Configuration"); } - conn.connect(new ServerHostKeyVerifierImpl(computer, listener), slaveConnectTimeout, slaveConnectTimeout); logInfo(computer, listener, "Connected via SSH."); return conn; // successfully connected diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckStaticStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckStaticStrategy.java new file mode 100644 index 000000000..8c8bca3eb --- /dev/null +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckStaticStrategy.java @@ -0,0 +1,111 @@ +/* + * The MIT License + * + * Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.plugins.ec2.ssh.verifiers; + +import hudson.model.TaskListener; +import hudson.plugins.ec2.EC2Cloud; +import hudson.plugins.ec2.EC2Computer; +import hudson.plugins.ec2.SlaveTemplate; +import hudson.slaves.OfflineCause; + +import java.util.ArrayList; +import java.util.Base64; + +import java.io.IOException; +import java.util.Objects; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This strategy checks the key presented by the host with the key in the Static Host Keys` field in the slave template. + * If the key is not found, the connection is not trusted. If it's found, the key is stored in the ssh-host-key.xml file + * of the node directory and checked on every further connection. + * @author Christian Groschupp + * @since TODO + */ +public class CheckStaticStrategy extends SshHostKeyVerificationStrategy { + private static final Logger LOGGER = Logger.getLogger(CheckStaticStrategy.class.getName()); + + private ArrayList getStaticHostKeys(EC2Computer computer) { + ArrayList hostKeys; + hostKeys = new ArrayList<>(); + + SlaveTemplate computerSlaveTemplate = computer.getSlaveTemplate(); + if (computerSlaveTemplate == null) { + EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), "No compute slave template found, return empty hostKeys list"); + return hostKeys; + } + + Scanner scanner = new Scanner(computerSlaveTemplate.getStaticHostKeys()); + + while (scanner.hasNextLine()) { + String hostKeyString = scanner.nextLine(); + String[] hostKeyParts = hostKeyString.split(" "); + if (hostKeyParts.length < 2 || hostKeyParts.length > 3) { + EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), "The provided static SSH key is invalid"); + continue; + } + HostKey hostKey = new HostKey(hostKeyParts[0], Base64.getDecoder().decode(hostKeyParts[1])); + hostKeys.add(hostKey); + } + scanner.close(); + return hostKeys; + } + + @Override + public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException { + HostKey existingHostKey = HostKeyHelper.getInstance().getHostKey(computer); + ArrayList staticHostKeys = getStaticHostKeys(computer); + + if (staticHostKeys.size() < 1) { + EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), "No configured static SSH key or none of the statically configured SSH keys are valid"); + // To avoid reconnecting continuously + computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); + return false; + } + + if (null == existingHostKey) { + for (HostKey staticHostKey : staticHostKeys) { + if (hostKey.equals(staticHostKey)) { + HostKeyHelper.getInstance().saveHostKey(computer, hostKey); + EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key %s %s has been successfully checked against the instance console for connections to %s", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + return true; + } + } + // To avoid reconnecting continuously + computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); + return false; + + } else if (existingHostKey.equals(hostKey)) { + EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), "Connection allowed after the host key has been verified"); + return true; + } else { + EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); + // To avoid reconnecting continuously + computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); + return false; + } + } +} diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly index bdcb6f64f..d9f86ac42 100644 --- a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly @@ -199,6 +199,10 @@ THE SOFTWARE. + + + + diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-hostKeyVerificationStrategy.html b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-hostKeyVerificationStrategy.html index b2a08ccae..800b7669c 100644 --- a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-hostKeyVerificationStrategy.html +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-hostKeyVerificationStrategy.html @@ -26,6 +26,7 @@
  • check-new-hard: Check the key presented by the instance against the instance console and stores it to check subsequent connections. If the key is not printed on the console, the connection is not trusted. This is the default behavior for new AMIs.
  • check-new-soft: Check the key against the instance console and stores it to check subsequent connections. If the key is not printed on the console, the connection is trusted anyway. This is the default behavior for existing AMIs (upgrading from a previous plugin version). This avoids future attacks but cannot guarantee the instance is the right one if a man-in-the-middle attack has already been committed.
  • +
  • check-static: Check the key against the static configured SSH keys in the cloud template.
  • accept-new: Accept the key on first connection and stores it to check subsequent connections. This doesn't try to check the key against the console as the check-new-soft strategy does.
  • off: Don't check the host key on any connection.
@@ -34,6 +35,7 @@
  • check-new-hard = yes
  • check-new-soft = accept-new
  • +
  • check-static = yes
  • accept-new = accept-new
  • off = no
diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-staticHostKeys.html b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-staticHostKeys.html new file mode 100644 index 000000000..a55d70adc --- /dev/null +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-staticHostKeys.html @@ -0,0 +1,3 @@ +
+ List of static ssh host keys, seperated by new line. Only used when Host Key Verification Strategy is check-static. +
diff --git a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java index 1ae4adaff..d9b36d25b 100644 --- a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java @@ -41,7 +41,7 @@ public String getEc2Type() { @Test public void testMaxUsesBackwardCompat() throws Exception { final String description = "description"; - SlaveTemplate orig = new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1); + SlaveTemplate orig = new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, ""); List templates = new ArrayList<>(); templates.add(orig); AmazonEC2Cloud ac = new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", "3", templates, null, null); diff --git a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java index 18aefd8a8..b809dade2 100644 --- a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java @@ -107,7 +107,7 @@ public InstanceState getState() { @Override public SlaveTemplate getSlaveTemplate() { - return new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, ""); + return new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, "", ""); } }; assertTrue(computer.isIdle()); @@ -209,7 +209,7 @@ public void testRetentionDespiteIdleWithMinimumInstances() throws Exception { SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, 0, "10", null, true, true, false, "", false, "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList()); + true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList(), ""); AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", Collections .singletonList(template), "roleArn", "roleSessionName"); @@ -268,7 +268,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRange() throws SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, 0, "10", null, true, true, false, "", false, "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList()); + true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList(), ""); MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); @@ -324,7 +324,7 @@ public void testRetentionIdleWithMinimumInstanceInactiveTimeRange() throws Excep SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, 0, "10", null, true, true, false, "", false, "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList()); + true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList(), ""); MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); @@ -365,7 +365,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRangeAfterMidni SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, 0, "10", null, true, true, false, "", false, "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList()); + true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList(), ""); MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("15:00"); @@ -420,7 +420,7 @@ public void testRetentionStopsAfterActiveRangeEnds() throws Exception { SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, 0, "10", null, true, true, false, "", false, "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList()); + true, ConnectionStrategy.PRIVATE_IP, 0, Collections.emptyList(), ""); MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); diff --git a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java index a8d48c8c0..0e7c82481 100644 --- a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java @@ -1,49 +1,49 @@ -package hudson.plugins.ec2; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; - -import com.amazonaws.services.ec2.model.InstanceType; - -import hudson.model.Node; -import hudson.plugins.ec2.util.PrivateKeyHelper; -import jenkins.model.Jenkins; - -public class EC2SlaveMonitorTest { - - @Rule - public JenkinsRule r = new JenkinsRule(); - - @Test - public void testMinimumNumberOfInstances() throws Exception { - SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, null, null, true, true, false, "", false, "", false, false, true, ConnectionStrategy.PRIVATE_IP, 0); - AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", Collections.singletonList(template), "roleArn", "roleSessionName"); - r.jenkins.clouds.add(cloud); - r.configRoundtrip(); - - Assert.assertEquals(2, Arrays.stream(Jenkins.get().getComputers()).filter(computer -> computer instanceof EC2Computer).count()); - } - - @Test - public void testMinimumNumberOfSpareInstances() throws Exception { - // Arguments split onto newlines matching the construtor definition to make figuring which is which easier. - SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "defaultsecgroup", "remotefs", - InstanceType.M1Large, false, "label", Node.Mode.NORMAL, "description", "init script", - "tmpdir", "userdata", "10", "remoteadmin", null, "-Xmx1g", - false, "subnet 456", null, "0", 0, - 2, null, null, true, - true, false, "", false, - "", false, false, - true, ConnectionStrategy.PRIVATE_IP, 0, - null); - AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", Collections.singletonList(template), "roleArn", "roleSessionName"); - r.jenkins.clouds.add(cloud); - r.configRoundtrip(); - Assert.assertEquals(2, Arrays.stream(Jenkins.get().getComputers()).filter(computer -> computer instanceof EC2Computer).count()); - } -} +package hudson.plugins.ec2; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import com.amazonaws.services.ec2.model.InstanceType; + +import hudson.model.Node; +import hudson.plugins.ec2.util.PrivateKeyHelper; +import jenkins.model.Jenkins; + +public class EC2SlaveMonitorTest { + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Test + public void testMinimumNumberOfInstances() throws Exception { + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, null, null, true, true, false, "", false, "", false, false, true, ConnectionStrategy.PRIVATE_IP, 0, ""); + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", Collections.singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + + Assert.assertEquals(2, Arrays.stream(Jenkins.get().getComputers()).filter(computer -> computer instanceof EC2Computer).count()); + } + + @Test + public void testMinimumNumberOfSpareInstances() throws Exception { + // Arguments split onto newlines matching the construtor definition to make figuring which is which easier. + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "defaultsecgroup", "remotefs", + InstanceType.M1Large, false, "label", Node.Mode.NORMAL, "description", "init script", + "tmpdir", "userdata", "10", "remoteadmin", null, "-Xmx1g", + false, "subnet 456", null, "0", 0, + 2, null, null, true, + true, false, "", false, + "", false, false, + true, ConnectionStrategy.PRIVATE_IP, 0, + null, ""); + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", Collections.singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + Assert.assertEquals(2, Arrays.stream(Jenkins.get().getComputers()).filter(computer -> computer instanceof EC2Computer).count()); + } +} diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java index c16af93cf..ec821b3bb 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java @@ -94,7 +94,7 @@ public void testConfigRoundtrip() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -118,7 +118,7 @@ public void testConfigRoundtripWithCustomConnectionStrategy() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, ""); List templates = new ArrayList(); templates.add(orig); @@ -138,7 +138,7 @@ public void testDefaultSSHHostKeyVerificationStrategy() throws Exception { List tags = new ArrayList(); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, 0, 0, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, null); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, 0, 0, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, null, ""); List templates = new ArrayList(); templates.add(orig); @@ -167,7 +167,7 @@ public void testConfigRoundtripWithCustomSSHHostKeyVerificationStrategy() throws // We check this one is set final HostKeyVerificationStrategyEnum STRATEGY_TO_CHECK = HostKeyVerificationStrategyEnum.OFF; - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, 0, 0, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, null, STRATEGY_TO_CHECK); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, 0, 0, null, "", true, false, false, "", false, "", false, false, false, ConnectionStrategy.PUBLIC_IP, -1, null, STRATEGY_TO_CHECK, ""); List templates = new ArrayList(); templates.add(orig); @@ -204,7 +204,7 @@ public void testConfigWithSpotBidPrice() throws Exception { spotConfig.setFallbackToOndemand(false); spotConfig.setSpotBlockReservationDuration(0); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -235,7 +235,7 @@ public void testSpotConfigWithoutBidPrice() throws Exception { SpotConfiguration spotConfig = new SpotConfiguration(false); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -269,7 +269,7 @@ public void testSpotConfigWithFallback() throws Exception { spotConfig.setFallbackToOndemand(true); spotConfig.setSpotBlockReservationDuration(0); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, true, null, "", false, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -297,7 +297,7 @@ public void testConfigRoundtripIamRole() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "iamInstanceProfile", false, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "iamInstanceProfile", false, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -312,13 +312,13 @@ public void testConfigRoundtripIamRole() throws Exception { @Test public void testNullTimeoutShouldReturnMaxInt() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, false, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, false, "", ""); assertEquals(Integer.MAX_VALUE, st.getLaunchTimeout()); } @Test public void testUpdateAmi() { - SlaveTemplate st = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "0", false, ""); + SlaveTemplate st = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "0", false, "", ""); assertEquals("ami1", st.getAmi()); st.setAmi("ami2"); assertEquals("ami2", st.getAmi()); @@ -328,37 +328,37 @@ public void testUpdateAmi() { @Test public void test0TimeoutShouldReturnMaxInt() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "0", false, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "0", false, "", ""); assertEquals(Integer.MAX_VALUE, st.getLaunchTimeout()); } @Test public void testNegativeTimeoutShouldReturnMaxInt() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "-1", false, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "-1", false, "", ""); assertEquals(Integer.MAX_VALUE, st.getLaunchTimeout()); } @Test public void testNonNumericTimeoutShouldReturnMaxInt() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "NotANumber", false, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, "NotANumber", false, "", ""); assertEquals(Integer.MAX_VALUE, st.getLaunchTimeout()); } @Test public void testAssociatePublicIpSetting() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, true, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, true, "", ""); assertEquals(true, st.getAssociatePublicIp()); } @Test public void testConnectUsingPublicIpSetting() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, false, null, true, "", false, true); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, false, null, true, "", false, true, ""); assertEquals(st.connectionStrategy, ConnectionStrategy.PUBLIC_IP); } @Test public void testConnectUsingPublicIpSettingWithDefaultSetting() { - SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, true, ""); + SlaveTemplate st = new SlaveTemplate("", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, false, null, "iamInstanceProfile", false, false, null, true, "", ""); assertEquals(st.connectionStrategy, ConnectionStrategy.PUBLIC_IP); } @@ -381,7 +381,7 @@ public void testWindowsConfigRoundTrip() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "rrr", new WindowsData("password", false, ""), "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "rrr", new WindowsData("password", false, ""), "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -407,7 +407,7 @@ public void testUnixConfigRoundTrip() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "rrr", new UnixData("sudo", null, null, "22"), "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "rrr", new UnixData("sudo", null, null, "22"), "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -422,7 +422,7 @@ public void testUnixConfigRoundTrip() throws Exception { @Test public void testChooseSubnetId() throws Exception { - SlaveTemplate slaveTemplate = new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, ""); + SlaveTemplate slaveTemplate = new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, "", ""); String subnet1 = slaveTemplate.chooseSubnetId(); String subnet2 = slaveTemplate.chooseSubnetId(); @@ -436,7 +436,7 @@ public void testChooseSubnetId() throws Exception { @Issue("JENKINS-59460") @Test public void testConnectionStrategyDeprecatedFieldsAreExported() { - SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", Collections.singletonList(new EC2Tag("name1", "value1")), null, false, null, "", true, false, "", false, ""); + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", Collections.singletonList(new EC2Tag("name1", "value1")), null, false, null, "", true, false, "", false, "", ""); String exported = Jenkins.XSTREAM.toXML(template); assertThat(exported, containsString("usePrivateDnsName")); @@ -461,7 +461,7 @@ public void testMinimumNumberOfInstancesActiveRangeConfig() throws Exception { spotConfig.setSpotMaxBidPrice("22"); spotConfig.setFallbackToOndemand(true); spotConfig.setSpotBlockReservationDuration(1); - SlaveTemplate slaveTemplate = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, null, null, true, true, false, "", false, "", false, false, true, ConnectionStrategy.PRIVATE_IP, 0); + SlaveTemplate slaveTemplate = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, spotConfig, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, null, null, true, true, false, "", false, "", false, false, true, ConnectionStrategy.PRIVATE_IP, 0, ""); slaveTemplate.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); List templates = new ArrayList(); @@ -495,8 +495,8 @@ public void provisionOndemandSetsAwsNetworkingOnEc2Request() throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, subnetId, tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, ""); - SlaveTemplate noSubnet = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "", tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, subnetId, tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, "", ""); + SlaveTemplate noSubnet = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "", tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, "", ""); List templates = new ArrayList(); templates.add(orig); @@ -538,8 +538,8 @@ public void provisionOndemandSetsAwsNetworkingOnNetworkInterface() throws Except tags.add(tag1); tags.add(tag2); - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, subnetId, tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, ""); - SlaveTemplate noSubnet = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "", tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, ""); + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, subnetId, tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, "", ""); + SlaveTemplate noSubnet = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, securityGroups, "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "", tags, null, false, null, iamInstanceProfile, true, false, "", associatePublicIp, "", ""); List templates = new ArrayList(); templates.add(orig); diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java index a3839035b..c2b09e5cd 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java @@ -52,7 +52,7 @@ public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRe tags.add(tag2); String instanceId = "123"; - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "") { + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "", "") { @Override protected Object readResolve() { return null; @@ -89,7 +89,7 @@ public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRe tags.add(tag2); String instanceId = "123"; - SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "") { + SlaveTemplate orig = new SlaveTemplate(ami, EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, description, "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", tags, null, false, null, "", true, false, "", false, "", "") { @Override protected Object readResolve() { return null; diff --git a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java index 8acd5eeaa..21997d43c 100644 --- a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java +++ b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java @@ -56,7 +56,7 @@ private void setUpCloud(String label, Node.Mode mode) throws Exception { tags.add(tag1); tags.add(tag2); - SlaveTemplate template = new SlaveTemplate("ami", "foo", null, "default", "zone", InstanceType.M1Large, false, label, mode, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", true, "subnet 456", tags, null, false, null, "", false, false, null, false, ""); + SlaveTemplate template = new SlaveTemplate("ami", "foo", null, "default", "zone", InstanceType.M1Large, false, label, mode, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", true, "subnet 456", tags, null, false, null, "", false, false, null, false, "", ""); List templates = new ArrayList(); templates.add(template); diff --git a/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java b/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java index c1dce8437..4a31b7a98 100644 --- a/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java @@ -26,6 +26,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.comparesEqualTo; import static org.hamcrest.Matchers.emptyString; public class SshHostKeyVerificationStrategyTest { @@ -61,6 +62,9 @@ private List getStrategiesToTest() throws Exception { strategiesToCheck.add(forHardStrategyNotPrinted()); strategiesToCheck.add(forHardStrategyPrinted()); strategiesToCheck.add(forHardStrategyPrintedAndChanged()); + strategiesToCheck.add(forStaticStrategy()); + strategiesToCheck.add(forStaticStrategyWithoutStaticHostKeys()); + strategiesToCheck.add(forStaticStrategyInvalidStaticHostKeys()); strategiesToCheck.add(forSoftStrategy()); strategiesToCheck.add(forAceptNewStrategy()); strategiesToCheck.add(forOffStrategy()); @@ -70,7 +74,7 @@ private List getStrategiesToTest() throws Exception { // Check the hard strategy with a key not printed private StrategyTest forHardStrategyNotPrinted() throws Exception { - return new StrategyTest("-hardStrategyNotPrinted", new CheckNewHardStrategy()) + return new StrategyTest("-hardStrategyNotPrinted", new CheckNewHardStrategy(), "") .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ @@ -92,7 +96,7 @@ private StrategyTest forHardStrategyNotPrinted() throws Exception { // Check the hard strategy with the key printed private StrategyTest forHardStrategyPrinted() throws Exception { - return new StrategyTest("-hardStrategyPrinted", new CheckNewHardStrategy()) + return new StrategyTest("-hardStrategyPrinted", new CheckNewHardStrategy(), "") .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ "is not running, waiting to validate the key against the console", @@ -109,7 +113,7 @@ private StrategyTest forHardStrategyPrinted() throws Exception { // Check the hard strategy with the key printed and the host key is changed afterward private StrategyTest forHardStrategyPrintedAndChanged() throws Exception { - return new StrategyTest("-hardStrategyPrintedAndChanged", new CheckNewHardStrategy()) + return new StrategyTest("-hardStrategyPrintedAndChanged", new CheckNewHardStrategy(), "") .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ "is not running, waiting to validate the key against the console", @@ -131,9 +135,37 @@ private StrategyTest forHardStrategyPrintedAndChanged() throws Exception { ); } + // Check the static strategy + private StrategyTest forStaticStrategy() throws Exception { + return new StrategyTest("-staticStrategy", new CheckStaticStrategy(), conRule.ED255219_PUB_KEY) + .addConnectionAttempt(builder().setState(InstanceState.PENDING) + .setMessagesInLog(new String[]{ + "has been successfully checked against the instance console"})) + + .addConnectionAttempt(builder().setState(InstanceState.PENDING).isChangeHostKey(true) + .setMessagesInLog(new String[]{ + "presented by the instance has changed since first saved"})); + } + + // Check the static strategy without static hostkeys + private StrategyTest forStaticStrategyWithoutStaticHostKeys() throws Exception { + return new StrategyTest("-staticStrategyWithoutStaticHostKeys", new CheckStaticStrategy(), "") + .addConnectionAttempt(builder().setState(InstanceState.PENDING) + .setMessagesInLog(new String[]{ + "No configured static SSH key or none of the statically configured SSH keys are valid"})); + } + + // Check the static strategy with invalid static hostkeys + private StrategyTest forStaticStrategyInvalidStaticHostKeys() throws Exception { + return new StrategyTest("-staticStrategyInvalidStaticHostKeys", new CheckStaticStrategy(), "TEST") + .addConnectionAttempt(builder().setState(InstanceState.PENDING) + .setMessagesInLog(new String[]{ + "The provided static SSH key is invalid"})); + } + // Check the soft strategy private StrategyTest forSoftStrategy() throws Exception { - return new StrategyTest("-softStrategy", new CheckNewSoftStrategy()) + return new StrategyTest("-softStrategy", new CheckNewSoftStrategy(), "") .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ "is not running, waiting to validate the key against the console", @@ -159,7 +191,7 @@ private StrategyTest forSoftStrategy() throws Exception { // Check the accept-new strategy private StrategyTest forAceptNewStrategy() throws Exception { - return new StrategyTest("-acceptNewStrategy", new AcceptNewStrategy()) + return new StrategyTest("-acceptNewStrategy", new AcceptNewStrategy(), "") // We don't even check the console .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ @@ -172,7 +204,7 @@ private StrategyTest forAceptNewStrategy() throws Exception { // Check the off strategy private StrategyTest forOffStrategy() throws Exception { - return new StrategyTest("-offStrategy", new NonVerifyingKeyVerificationStrategy()) + return new StrategyTest("-offStrategy", new NonVerifyingKeyVerificationStrategy(), "") .addConnectionAttempt(builder().setState(InstanceState.PENDING) .setMessagesInLog(new String[]{ @@ -199,8 +231,8 @@ public void check() throws Exception { } } - private StrategyTest(String computerSuffix, SshHostKeyVerificationStrategy strategy) throws Exception { - computer = MockEC2Computer.createComputer(computerSuffix); + private StrategyTest(String computerSuffix, SshHostKeyVerificationStrategy strategy, String staticHostKeys) throws Exception { + computer = MockEC2Computer.createComputer(computerSuffix, staticHostKeys); verifier = new ServerHostKeyVerifierImpl(computer, strategy); } @@ -329,14 +361,16 @@ private static class MockEC2Computer extends EC2Computer { InstanceState state = InstanceState.PENDING; String console = null; EC2AbstractSlave slave; + String staticHostKeys; - public MockEC2Computer(EC2AbstractSlave slave) { + public MockEC2Computer(EC2AbstractSlave slave, String staticHostKeys) { super(slave); this.slave = slave; + this.staticHostKeys = staticHostKeys; } // Create a computer - private static MockEC2Computer createComputer(String suffix) throws Exception { + private static MockEC2Computer createComputer(String suffix, String staticHostKey) throws Exception { final EC2AbstractSlave slave = new EC2AbstractSlave(COMPUTER_NAME + suffix, "id" + suffix, "description" + suffix, "fs", 1, null, "label", null, null, "init", "tmpDir", new ArrayList>(), "remote", "jvm", false, "idle", null, "cloud", false, Integer.MAX_VALUE, null, ConnectionStrategy.PRIVATE_IP, -1) { @Override public void terminate() { @@ -348,7 +382,7 @@ public String getEc2Type() { } }; - return new MockEC2Computer(slave); + return new MockEC2Computer(slave, staticHostKey); } @Override @@ -368,7 +402,7 @@ public EC2AbstractSlave getNode() { @Override public SlaveTemplate getSlaveTemplate() { - return new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, ""); + return new SlaveTemplate("ami-123", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "AMI description", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet-123 subnet-456", null, null, true, null, "", false, false, "", false, "", this.staticHostKeys); } }