Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JENKINS-71655: Add support for AMIs without instance metadata support #874

Merged
merged 9 commits into from
Jul 26, 2023
64 changes: 33 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* [Amazon Linux build/connectivity issues](#amazon-linux-buildconnectivity-issues)
* [Downgrade from 1.50.2, 1.49.2, 1.46.3 to a previous version](#downgrade-from-1502-1492-1463-to-a-previous-version)
* [Change Log](#change-log)

# Introduction

Allow Jenkins to start agents on
Expand Down Expand Up @@ -336,6 +336,7 @@ def slaveTemplateUsEast1Parameters = [
usePrivateDnsName: false,
userData: '',
zone: '',
metadataSupported: true,
metadataEndpointEnabled: true,
metadataTokensRequired: true, // `true` enforces IMDSv2 only (over IMDSv1), an important AWS security best practice
metadataHopsLimit: 1,
Expand Down Expand Up @@ -444,6 +445,7 @@ SlaveTemplate slaveTemplateUsEast1 = new SlaveTemplate(
slaveTemplateUsEast1Parameters.hostKeyVerificationStrategy,
slaveTemplateUsEast1Parameters.tenancy,
slaveTemplateUsEast1Parameters.ebsEncryptRootVolume,
slaveTemplateUsEast1Parameters.metadataSupported,
slaveTemplateUsEast1Parameters.metadataEndpointEnabled,
slaveTemplateUsEast1Parameters.metadataTokensRequired,
slaveTemplateUsEast1Parameters.metadataHopsLimit,
Expand Down Expand Up @@ -484,45 +486,45 @@ jenkins.save()
# Security
## Securing the connection to Unix AMIs
When you set up a template for a *Unix* instance (`Type AMI` field), you can select the strategy used to guarantee the
instance you're connecting to is the expected one. You should use a strong strategy to guarantee that a
instance you're connecting to is the expected one. You should use a strong strategy to guarantee that a
_[man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)_ cannot be performed.

You can select your strategy under the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field of
every configured AMI.
You can select your strategy under the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field of
every configured AMI.

The plugin provides several strategies because each one has its own requirements. So providing more than one allows
administrators to use the one best fits to their environment. These strategies are:

### Strategies
#### Check New Hard
This strategy checks the SSH host key provided by the instance with the key printed out in the instance console during
the instance initialization. If the key is not found, the plugin **doesn't allow** the connection to the instance to
the instance initialization. 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.

Requirements:

* The AMI used should print the key used. It's a common behaviour, for example the _Amazon Linux 2_ AMI prints it
* The AMI used should print the key used. It's a common behaviour, for example the _Amazon Linux 2_ AMI prints it
out. You can consult the AMI documentation to figure it out.
* The launch timeout should be long enough to allow the plugin to check the instance console. With this strategy, the
plugin waits for the console to be available, which can take a few minutes. The _Launch Timeout in seconds_ field should
have a number to allow that, for example 600 (10 minutes). By default there is no timeout, so it's safe.

The expected format on the instance console is `algorithm base64-public-key` at the beginning of a line. For example:
```
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
```
Recommended for:

This strategy is the most secure. It's recommended for every instance if you can meet the requirements. 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.
cache allowing next connections to be done faster.

#### Check New Soft
This strategy checks the SSH host key provided by the instance with the key printed out in the instance console during
the instance initialization. If the key is not found, the plugin **allows** the connection to the instance in order to
the instance initialization. If the key is not found, the plugin **allows** the connection to the instance in order 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.

Expand All @@ -536,9 +538,9 @@ expires, the connection is not done.

The expected format on the instance console is `algorithm base64-public-key` at the beginning of a line. For example:
```
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFNGfKpPS/UT2jAEa0+9aZneku2a7TVwN+MjGesm65DDGnXPcM9TM9BsiOE+s4Vo6aCT9L/TVrtDFa0hqbnqc8=
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm0sVqkjSuaPg8e7zfaKXt3b1hE1tBwFsB18NOWv5ow
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNTngsAxOCpZwt+IBqJSQ9MU2qVNYzP4D5i1OHfIRXCrnAuJ54GtFzZEZqqo4e1e/JqBQOX3ZPsaegbkzl2uq5FzfFcFoYYXg5gL7htlZ1I2k6/2iIBv7CHAjbpXMkH8WoF2C3vZFRMWLs20ikQpED+9m11VejE19+kqJwLMopyAtq+/mCgiv4nw5QWh3rrrEcbgzuxYoMD0t9daqBq1V0lzRqL36ALVySy7oDjr3YzCN+wMXe1I36kv3lSeCHXnhc53ubrBIsRakWLBndHhPqyyAOMEjdby/O/EQ2PR7vBpH5MaseaJwvRRDPQ6qt4sV8lk0tEt9qbdb1prFRB4W1
```
Recommended for:

Expand Down Expand Up @@ -573,33 +575,33 @@ Requirements:
Recommended for:

This strategy is not recommended because of its lack of security. It is the strategy used for prior versions of the plugin.

### New AMIs
The default strategy for every new instance is the _Check New Hard_ one. You can select a strategy per AMI. It's under
the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field.
The default strategy for every new instance is the _Check New Hard_ one. You can select a strategy per AMI. It's under
the _Advanced..._ configuration, on the _Host Key Verification Strategy_ field.

### Upgrade - Existing AMIs
You may upgrade from a Jenkins installation with a former plugin version without this security mechanism. The default
strategy for every existing instance is the _Check New Soft_ strategy. This guarantees your jobs are not going to stop
working and improves the situation. We recommend, if possible, upgrading to the _Check New Hard_ strategy to be safer
against a _Man in the Middle attack_.
against a _Man in the Middle attack_.

## Securing the connection to Windows AMIs
When you configure a template for a *Windows* instance (`Type AMI` field), you can use HTTPS and disallow
self-signed certificates. This guarantees the instance you're connecting to is the expected one and a
When you configure a template for a *Windows* instance (`Type AMI` field), you can use HTTPS and disallow
self-signed certificates. This guarantees the instance you're connecting to is the expected one and a
[man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) cannot be performed.

### AMI Set Up
Before securely connecting to the instance, you need to 1) configure the AMI, 2)install the
certificate, 3) configure WinRM properly and 4) set the firewall rules to allow the connection. You can find some
Before securely connecting to the instance, you need to 1) configure the AMI, 2)install the
certificate, 3) configure WinRM properly and 4) set the firewall rules to allow the connection. You can find some
guidance at the `AMI Type` field help, under the template configuration on your Jenkins instance.

Tips:
* When the `Allow Self Signed Certificate` field is checked, the plugin allows self-signed certificates. It means that
the plugin neither checks the CA which issued the certificate, nor verifies the host it is connecting to is present on
* When the `Allow Self Signed Certificate` field is checked, the plugin allows self-signed certificates. It means that
the plugin neither checks the CA which issued the certificate, nor verifies the host it is connecting to is present on
the certificate. If the field is not checked, this kind of certificates are not allowed and both verifications are performed.
* 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
the **IP** of the instance. Something like:
* 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
the **IP** of the instance. Something like:
```
#3: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
Expand Down Expand Up @@ -649,19 +651,19 @@ This issue can be solved in different ways:
[documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/amazon-linux-ami-basics.html#security-updates))
3. Force security upgrade through the "init-script" in the Cloud
configuration

![](docs/images/init-scripts.png)

## Downgrade from 1.50.2, 1.49.2, 1.46.3 to a previous version
If you updated to 1.50.2, or 1.49.2 or 1.46.3 and you need to downgrade back to the previous version, be sure to **remove** the
file `JENKINS_HOME/ec2.xml` before doing that. This file is created if you click on the button *Dismiss these messages* of
the monitor that warns you when there are some template with a weak strategy. If you don't do that, Jenkins will start and
file `JENKINS_HOME/ec2.xml` before doing that. This file is created if you click on the button *Dismiss these messages* of
the monitor that warns you when there are some template with a weak strategy. If you don't do that, Jenkins will start and
will overwrite its config.xml file **losing your cloud configuration**.

At this moment, it seems related with a race-condition between the plugin and the `OldDataMonitor` class of Jenkins Core.
At this moment, it seems related with a race-condition between the plugin and the `OldDataMonitor` class of Jenkins Core.
See https://issues.jenkins-ci.org/browse/JENKINS-62231

# Change Log

Newer changelogs are posted [here](https://github.com/jenkinsci/ec2-plugin/releases),
Newer changelogs are posted [here](https://github.com/jenkinsci/ec2-plugin/releases),
1.45 and older releases can be found [here](https://github.com/jenkinsci/ec2-plugin/blob/master/CHANGELOG.md)
17 changes: 14 additions & 3 deletions src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
*/
@SuppressWarnings("serial")
public abstract class EC2AbstractSlave extends Slave {
public static final Boolean DEFAULT_METADATA_SUPPORTED = Boolean.TRUE;
public static final Boolean DEFAULT_METADATA_ENDPOINT_ENABLED = Boolean.TRUE;
public static final Boolean DEFAULT_METADATA_TOKENS_REQUIRED = Boolean.TRUE;
public static final Integer DEFAULT_METADATA_HOPS_LIMIT = 1;
Expand Down Expand Up @@ -111,6 +112,7 @@ public abstract class EC2AbstractSlave extends Slave {
public final Tenancy tenancy;
private String instanceType;

private Boolean metadataSupported;
private Boolean metadataEndpointEnabled;
private Boolean metadataTokensRequired;
private Integer metadataHopsLimit;
Expand Down Expand Up @@ -155,8 +157,7 @@ public abstract class EC2AbstractSlave extends Slave {

public static final String TEST_ZONE = "testZone";

public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy<EC2Computer> retentionStrategy, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy,
Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit)
public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy<EC2Computer> retentionStrategy, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported)
throws FormException, IOException {
super(name, remoteFS, launcher);
setNumExecutors(numExecutors);
Expand Down Expand Up @@ -185,14 +186,20 @@ public EC2AbstractSlave(String name, String instanceId, String templateDescripti
this.metadataEndpointEnabled = metadataEndpointEnabled;
this.metadataTokensRequired = metadataTokensRequired;
this.metadataHopsLimit = metadataHopsLimit;
this.metadataSupported = metadataSupported;
readResolve();
}

@Deprecated
public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy<EC2Computer> retentionStrategy, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit)
throws FormException, IOException {
this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED);
}

@Deprecated
public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy<EC2Computer> retentionStrategy, String initScript, String tmpDir, List<? extends NodeProperty<?>> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List<EC2Tag> tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy)
throws FormException, IOException {
this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT);

}

@Deprecated
Expand Down Expand Up @@ -773,6 +780,10 @@ public int getBootDelay() {
return amiType.getBootDelayInMillis();
}

public Boolean getMetadataSupported() {
return metadataSupported;
}

public Boolean getMetadataEndpointEnabled() {
return metadataEndpointEnabled;
}
Expand Down
Loading