Skip to content

Commit 4c9f03a

Browse files
rsandellMRamonLeon
authored andcommitted
SECURITY-1408, SECURITY-381, SECURITY-1528
    Co-Authored-By: Jeff Thompson <[email protected]>     Co-Authored-By: Matt Sicker <[email protected]>     Co-Authored-By: rsandell <[email protected]>
1 parent f520245 commit 4c9f03a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2194
-50
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ target
1111
/bin
1212
/work
1313
/src_generated
14+
.history
15+
.factorypath

README.md

+150
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@
44
[![GitHub release](https://img.shields.io/github/release/jenkinsci/ec2-plugin.svg?label=changelog)](https://github.com/jenkinsci/ec2-plugin/releases/latest)
55
[![Gitter](https://badges.gitter.im/ec2-plugin/Lobby.svg)](https://gitter.im/ec2-plugin/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
66

7+
# Table of contents
8+
* [Introduction](#introduction)
9+
* [Usage](#usage)
10+
* [Spot Instances](#spot-instances)
11+
* [Enable Spot Request](#enable-spot-request)
12+
* [Configure Jenkins for Spot Support](#configure-jenkins-for-spot-support)
13+
* [Configure AMI for Spot Support](#configure-ami-for-spot-support)
14+
* [IAM setup](#iam-setup)
15+
* [Configure plugin via Groovy script](#configure-plugin-via-groovy-script)
16+
* [Security](#security)
17+
* [Securing the connection to Unix AMIs](#securing-the-connection-to-unix-amis)
18+
* [Strategies](#strategies)
19+
* [Check New Hard](#check-new-hard)
20+
* [Check New Soft](#check-new-soft)
21+
* [Accept New](#accept-new)
22+
* [Off](#off)
23+
* [New AMIs](#new-amis)
24+
* [Upgrade - Existing AMIs](#upgrade---existing-amis)
25+
* [Securing the connection to Windows AMIs](#securing-the-connection-to-windows-amis)
26+
* [AMI Set Up](#ami-set-up)
27+
* [Known Issues](#known-issues)
28+
* [Authentication Timeout](#authentication-timeout)
29+
* [Amazon Linux build/connectivity issues](#amazon-linux-buildconnectivity-issues)
30+
* [Change Log](#change-log)
31+
732
# Introduction
833

934
Allow Jenkins to start agents on
@@ -419,6 +444,131 @@ jenkins.clouds.add(amazonEC2Cloud)
419444
jenkins.save()
420445
```
421446

447+
# Security
448+
## Securing the connection to Unix AMIs
449+
When you set up a template for a *Unix* instance (`Type AMI` field), you can select the strategy used to guarantee the
450+
instance you're connecting to is the expected one. You should use a strong strategy to guarantee that a
451+
_[man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)_ cannot be performed.
452+
453+
You can select your strategy under the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field of
454+
every configured AMI.
455+
456+
The plugin provides several strategies because each one has its own requirements. So providing more than one allows
457+
administrators to use the one best fits to their environment. These strategies are:
458+
459+
### Strategies
460+
#### Check New Hard
461+
This strategy checks the SSH host key provided by the instance with the key printed out in the instance console during
462+
the instance initialization. If the key is not found, the plugin **doesn't allow** the connection to the instance to
463+
guarantee the instance is the right one. If the key is found and it is the same as the one presented by the instance,
464+
then it's saved to be used on future connections, so the console is only checked once.
465+
466+
Requirements:
467+
468+
* The AMI used should print the key used. It's a common behaviour, for example the _Amazon Linux 2_ AMI prints it
469+
out. You can consult the AMI documentation to figure it out.
470+
* The launch timeout should be long enough to allow the plugin to check the instance console. With this strategy, the
471+
plugin waits for the console to be available, which can take a few minutes. The _Launch Timeout in seconds_ field should
472+
have a number to allow that, for example 600 (10 minutes). By default there is no timeout, so it's safe.
473+
474+
The expected format on the instance console is `algorithm base64-public-key` at the beginning of a line. For example:
475+
```
476+
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
477+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
478+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
479+
```
480+
Recommended for:
481+
482+
This strategy is the most secure. It's recommended for every instance if you can meet the requirements. We recommend,
483+
whenever possible, configuring each AMI with _Stop/Disconnect on Idle Timeout_ to take advantage of the ssh host key
484+
cache allowing next connections to be done faster.
485+
486+
#### Check New Soft
487+
This strategy checks the SSH host key provided by the instance with the key printed out in the instance console during
488+
the instance initialization. If the key is not found, the plugin **allows** the connection to the instance in order to
489+
guarantee the instance is the right one. If the key is found and it is the same as the one presented by the instance,
490+
then it's saved to be used on future connections, so the console is only checked once.
491+
492+
Requirements:
493+
494+
* The AMI used may print the key used to guarantee the instance is the right one, but **it's not mandatory**.
495+
* The launch timeout should be long enough to allow the plugin to check the instance console. With this strategy, the
496+
plugin waits for the console to be available, which can take a few minutes. The _Launch Timeout in seconds_ field should
497+
have a number to allow that. For example 600 (10 minutes). By default there is no timeout, so it's safe. If the timeout
498+
expires, the connection is not done.
499+
500+
The expected format on the instance console is `algorithm base64-public-key` at the beginning of a line. For example:
501+
```
502+
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
503+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
504+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
505+
```
506+
Recommended for:
507+
508+
This strategy is the default one for AMIs created with a former version of the plugin. It doesn't break any connection
509+
because the plugin connects to the instance even when the key is not found on the console. The only point to take into
510+
account is you need to have the right timeout to allow the plugin to get the instance console. This strategy is recommended
511+
when upgrading from a previous version of the plugin. _Check New Hard_ is the safest strategy, so you should
512+
consider migrating to it. We recommend, whenever possible, configuring each AMI with _Stop/Disconnect on Idle Timeout_
513+
to take advantage of the ssh host key cache allowing next connections to be done faster.
514+
515+
#### Accept New
516+
This strategy doesn't check any key on the console. It accepts the key provided by the instance on the first
517+
connection. Then, the key is saved to be used on future connections to detect a Man-in-the-Middle attack (the host
518+
key has changed).
519+
520+
Requirements:
521+
* N/A
522+
523+
Recommended for:
524+
525+
This strategy is recommended when your AMIs don't print out the host keys on the console. The _Check New Soft_ cannot be
526+
used, but at least, you can catch a man-in-the-middle attack on further connections to the same instance. If the attack
527+
was already perpetrated you cannot detect that. Again, the _Check New Hard_ is the safest strategy.
528+
529+
#### Off
530+
This strategy neither checks any key on the console, nor checks future connections to the same instance with a saved
531+
key. It accepts blindly the key provided by the instance on the first and further connections.
532+
533+
Requirements:
534+
* N/A
535+
536+
Recommended for:
537+
538+
This strategy is not recommended because of its lack of security. It is the strategy used for prior versions of the plugin.
539+
540+
### New AMIs
541+
The default strategy for every new instance is the _Check New Hard_ one. You can select a strategy per AMI. It's under
542+
the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field.
543+
544+
### Upgrade - Existing AMIs
545+
You may upgrade from a Jenkins installation with a former plugin version without this security mechanism. The default
546+
strategy for every existing instance is the _Check New Soft_ strategy. This guarantees your jobs are not going to stop
547+
working and improves the situation. We recommend, if possible, upgrading to the _Check New Hard_ strategy to be safer
548+
against a _Man in the Middle attack_.
549+
550+
## Securing the connection to Windows AMIs
551+
When you configure a template for a *Windows* instance (`Type AMI` field), you can use HTTPS and disallow
552+
self-signed certificates. This guarantees the instance you're connecting to is the expected one and a
553+
[man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) cannot be performed.
554+
555+
### AMI Set Up
556+
Before securely connecting to the instance, you need to 1) configure the AMI, 2)install the
557+
certificate, 3) configure WinRM properly and 4) set the firewall rules to allow the connection. You can find some
558+
guidance at the `AMI Type` field help, under the template configuration on your Jenkins instance.
559+
560+
Tips:
561+
* When the `Allow Self Signed Certificate` field is checked, the plugin checks the CA which issued the
562+
certificate and verifies the host it is connecting to is present on the certificate. If the field is not checked, both checks are skipped.
563+
* The EC2 plugin connects to the instance using either an IP address. It does not use the DNS name. You must configure WinRM with a certificate which includes
564+
the **IP** of the instance. Something like:
565+
```
566+
#3: ObjectId: 2.5.29.17 Criticality=false
567+
SubjectAlternativeName [
568+
DNSName: myhostname.com
569+
IPAddress: 111.222.333.444 <--------------
570+
]
571+
```
422572
# Known Issues
423573

424574
## Authentication Timeout

pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ THE SOFTWARE.
246246
<version>${jcasc.version}</version>
247247
<scope>test</scope>
248248
</dependency>
249+
<dependency>
250+
<groupId>org.testcontainers</groupId>
251+
<artifactId>testcontainers</artifactId>
252+
<version>1.8.3</version>
253+
<scope>test</scope>
254+
</dependency>
249255
</dependencies>
250256

251257
<dependencyManagement>
@@ -420,6 +426,7 @@ THE SOFTWARE.
420426
<configuration>
421427
<compatibleSinceVersion>1.45</compatibleSinceVersion>
422428
<pluginFirstClassLoader>true</pluginFirstClassLoader>
429+
<minimumJavaVersion>8</minimumJavaVersion>
423430
</configuration>
424431
</plugin>
425432
<plugin>

src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java

+2
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,14 @@ public FormValidation doCheckCloudName(@QueryParameter String value) {
162162
return FormValidation.ok();
163163
}
164164

165+
@RequirePOST
165166
public ListBoxModel doFillRegionItems(
166167
@QueryParameter String altEC2Endpoint,
167168
@QueryParameter boolean useInstanceProfileForCredentials,
168169
@QueryParameter String credentialsId)
169170

170171
throws IOException, ServletException {
172+
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
171173

172174
ListBoxModel model = new ListBoxModel();
173175

src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java

+4
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,10 @@ public int getBootDelay() {
735735
public boolean isSpecifyPassword() {
736736
return amiType.isWindows() && ((WindowsData) amiType).isSpecifyPassword();
737737
}
738+
739+
public boolean isAllowSelfSignedCertificate() {
740+
return amiType.isWindows() && ((WindowsData) amiType).isAllowSelfSignedCertificate();
741+
}
738742

739743
public static ListBoxModel fillZoneItems(AWSCredentialsProvider credentialsProvider, String region) {
740744
ListBoxModel model = new ListBoxModel();

src/main/java/hudson/plugins/ec2/EC2Cloud.java

+20
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
import hudson.util.HttpResponses;
117117
import hudson.util.Secret;
118118
import hudson.util.StreamTaskListener;
119+
import org.kohsuke.stapler.interceptor.RequirePOST;
119120

120121
/**
121122
* Hudson's view of EC2.
@@ -325,6 +326,7 @@ public synchronized KeyPair getKeyPair() throws AmazonClientException, IOExcepti
325326
/**
326327
* Debug command to attach to a running instance.
327328
*/
329+
@RequirePOST
328330
public void doAttach(StaplerRequest req, StaplerResponse rsp, @QueryParameter String id)
329331
throws ServletException, IOException, AmazonClientException {
330332
checkPermission(PROVISION);
@@ -338,6 +340,7 @@ public void doAttach(StaplerRequest req, StaplerResponse rsp, @QueryParameter St
338340
rsp.sendRedirect2(req.getContextPath() + "/computer/" + node.getNodeName());
339341
}
340342

343+
@RequirePOST
341344
public HttpResponse doProvision(@QueryParameter String template) throws ServletException, IOException {
342345
checkPermission(PROVISION);
343346
if (template == null) {
@@ -959,6 +962,21 @@ public FormValidation doCheckPrivateKey(@QueryParameter String value) throws IOE
959962
return FormValidation.ok();
960963
}
961964

965+
/**
966+
* Tests the connection settings.
967+
*
968+
* Overriding needs to {@code @RequirePOST}
969+
* @param ec2endpoint
970+
* @param useInstanceProfileForCredentials
971+
* @param credentialsId
972+
* @param privateKey
973+
* @param roleArn
974+
* @param roleSessionName
975+
* @param region
976+
* @return the validation result
977+
* @throws IOException
978+
* @throws ServletException
979+
*/
962980
protected FormValidation doTestConnection(URL ec2endpoint, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String roleArn, String roleSessionName, String region)
963981
throws IOException, ServletException {
964982
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
@@ -986,7 +1004,9 @@ protected FormValidation doTestConnection(URL ec2endpoint, boolean useInstancePr
9861004
}
9871005
}
9881006

1007+
@RequirePOST
9891008
public ListBoxModel doFillCredentialsIdItems() {
1009+
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
9901010
return new StandardListBoxModel()
9911011
.withEmptySelection()
9921012
.withMatching(

src/main/java/hudson/plugins/ec2/EC2Computer.java

+10
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ public String getConsoleOutput() throws AmazonClientException {
9797
return ec2.getConsoleOutput(request).getOutput();
9898
}
9999

100+
/**
101+
* Gets the EC2 decoded console output.
102+
* @since TODO
103+
*/
104+
public String getDecodedConsoleOutput() throws AmazonClientException {
105+
AmazonEC2 ec2 = getCloud().connect();
106+
GetConsoleOutputRequest request = new GetConsoleOutputRequest(getInstanceId());
107+
return ec2.getConsoleOutput(request).getDecodedOutput();
108+
}
109+
100110
/**
101111
* Obtains the instance state description in EC2.
102112
*

src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private long internalCheck(EC2Computer computer) {
174174
return 1;
175175
}
176176
long launchTimeout = node.getLaunchTimeoutInMillis();
177-
if(uptime > launchTimeout){
177+
if (launchTimeout > 0 && uptime > launchTimeout){
178178
// Computer is offline and startup time has expired
179179
LOGGER.info("Startup timeout of " + computer.getName() + " after "
180180
+ uptime +
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package hudson.plugins.ec2;
25+
26+
import edu.umd.cs.findbugs.annotations.NonNull;
27+
import hudson.plugins.ec2.ssh.verifiers.AcceptNewStrategy;
28+
import hudson.plugins.ec2.ssh.verifiers.CheckNewHardStrategy;
29+
import hudson.plugins.ec2.ssh.verifiers.CheckNewSoftStrategy;
30+
import hudson.plugins.ec2.ssh.verifiers.NonVerifyingKeyVerificationStrategy;
31+
import hudson.plugins.ec2.ssh.verifiers.SshHostKeyVerificationStrategy;
32+
33+
public enum HostKeyVerificationStrategyEnum {
34+
CHECK_NEW_HARD("check-new-hard", "yes", new CheckNewHardStrategy()),
35+
CHECK_NEW_SOFT("check-new-soft", "accept-new", new CheckNewSoftStrategy()),
36+
ACCEPT_NEW("accept-new", "accept-new", new AcceptNewStrategy()),
37+
OFF("off", "off", new NonVerifyingKeyVerificationStrategy());
38+
39+
private final String displayText;
40+
private final SshHostKeyVerificationStrategy strategy;
41+
private final String sshCommandEquivalentFlag;
42+
43+
HostKeyVerificationStrategyEnum(@NonNull String displayText, @NonNull String sshCommandEquivalentFlag, @NonNull SshHostKeyVerificationStrategy strategy) {
44+
this.displayText = displayText;
45+
this.sshCommandEquivalentFlag = sshCommandEquivalentFlag;
46+
this.strategy = strategy;
47+
}
48+
49+
@NonNull
50+
public SshHostKeyVerificationStrategy getStrategy() {
51+
return strategy;
52+
}
53+
54+
public boolean equalsDisplayText(String other) {
55+
return this.displayText.equals(other);
56+
}
57+
58+
@NonNull
59+
public String getDisplayText() {
60+
return displayText;
61+
}
62+
63+
@NonNull
64+
public String getSshCommandEquivalentFlag() {
65+
return sshCommandEquivalentFlag;
66+
}
67+
}

0 commit comments

Comments
 (0)