Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ THE SOFTWARE.
<groupId>io.jenkins.plugins.aws-java-sdk2</groupId>
<artifactId>aws-java-sdk2-ec2</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.plugins.aws-java-sdk2</groupId>
<artifactId>aws-java-sdk2-ssm</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.plugins.mina-sshd-api</groupId>
<artifactId>mina-sshd-api-core</artifactId>
Expand Down
125 changes: 80 additions & 45 deletions src/main/java/hudson/plugins/ec2/EC2Cloud.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import hudson.model.PeriodicWork;
import hudson.model.TaskListener;
import hudson.plugins.ec2.util.AmazonEC2Factory;
import hudson.plugins.ec2.util.AmazonSSMFactory;
import hudson.plugins.ec2.util.FIPS140Utils;
import hudson.plugins.ec2.util.KeyPair;
import hudson.security.ACL;
Expand Down Expand Up @@ -136,6 +137,7 @@
import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest;
import software.amazon.awssdk.services.ec2.model.SpotInstanceState;
import software.amazon.awssdk.services.ec2.model.Tag;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.StsClientBuilder;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
Expand Down Expand Up @@ -211,6 +213,8 @@

private boolean noDelayProvisioning;

private boolean useSSM;

private boolean cleanUpOrphanedNodes;

private transient volatile Ec2Client connection;
Expand Down Expand Up @@ -299,6 +303,9 @@

@CheckForNull
public EC2PrivateKey resolvePrivateKey() {
if (useSSM) {
return null;
}
if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
LOGGER.fine(() -> "(resolvePrivateKey) secret key file configured, will load from disk");
return EC2PrivateKey.fetchFromDisk();
Expand Down Expand Up @@ -391,6 +398,15 @@
this.noDelayProvisioning = noDelayProvisioning;
}

public boolean isUseSSM() {
return useSSM;
}

@DataBoundSetter
public void setUseSSM(boolean useSSM) {
this.useSSM = useSSM;
}

public boolean isCleanUpOrphanedNodes() {
return cleanUpOrphanedNodes;
}
Expand Down Expand Up @@ -640,6 +656,9 @@
*/
@CheckForNull
public synchronized KeyPair getKeyPair() throws SdkException, IOException {
if (useSSM) {
return null;
}
if (usableKeyPair == null) {
EC2PrivateKey ec2PrivateKey = this.resolvePrivateKey();
if (ec2PrivateKey != null) {
Expand Down Expand Up @@ -1334,6 +1353,11 @@
}
}

public SsmClient createSsmClient() {
return AmazonSSMFactory.getInstance()
.connect(createCredentialsProvider(), parseRegion(getRegion()), parseEndpoint(getAltEC2Endpoint()));

Check warning on line 1358 in src/main/java/hudson/plugins/ec2/EC2Cloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1357-1358 are not covered by tests
}

public static SdkHttpClient getHttpClient() {
Jenkins instance = Jenkins.getInstanceOrNull();

Expand Down Expand Up @@ -1463,12 +1487,17 @@

@RequirePOST
public FormValidation doCheckSshKeysCredentialsId(
@AncestorInPath ItemGroup context, @QueryParameter String value) throws IOException, ServletException {
@AncestorInPath ItemGroup context, @QueryParameter String value, @QueryParameter boolean useSSM)
throws IOException, ServletException {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
// Don't do anything if the user is only reading the configuration
return FormValidation.ok();
}

if (useSSM) {
return FormValidation.ok();
}

String privateKey;
List<FormValidation> validations = new ArrayList<>();

Expand Down Expand Up @@ -1559,7 +1588,8 @@
@QueryParameter String credentialsId,
@QueryParameter String sshKeysCredentialsId,
@QueryParameter String roleArn,
@QueryParameter String roleSessionName)
@QueryParameter String roleSessionName,
@QueryParameter boolean useSSM)
throws IOException, ServletException {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return FormValidation.ok();
Expand All @@ -1568,33 +1598,9 @@
List<FormValidation> validations = new ArrayList<>();

LOGGER.fine(() -> "begin doTestConnection()");
String privateKey = "";
if (System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
LOGGER.fine(() -> "static credential is in use");
SSHUserPrivateKey sshCredential = getSshCredential(sshKeysCredentialsId, context);
if (sshCredential != null) {
privateKey = sshCredential.getPrivateKey();
} else {
return FormValidation.error(
"Failed to find credential \"" + sshKeysCredentialsId + "\" in store.");
}
} else {
EC2PrivateKey k = EC2PrivateKey.fetchFromDisk();
if (k == null) {
validations.add(FormValidation.error(
"Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH)));
if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
validations.add(FormValidation.warning(
"Private key file path defined, selected credential will be ignored"));
}
return FormValidation.aggregate(validations);
}
privateKey = k.getPrivateKey();
}
LOGGER.fine(() -> "private key found ok");

if (Util.fixEmpty(region) == null) {

Check warning on line 1602 in src/main/java/hudson/plugins/ec2/EC2Cloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1602 is only partially covered, one branch is missing
region = DEFAULT_EC2_HOST;

Check warning on line 1603 in src/main/java/hudson/plugins/ec2/EC2Cloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1603 is not covered by tests
}

AwsCredentialsProvider credentialsProvider = createCredentialsProvider(
Expand All @@ -1603,29 +1609,58 @@
.connect(credentialsProvider, parseRegion(region), parseEndpoint(altEC2Endpoint));
ec2.describeInstances();

if (!privateKey.trim().isEmpty()) {
// check if this key exists
EC2PrivateKey pk = new EC2PrivateKey(privateKey);
if (pk.find(ec2) == null) {
validations.add(FormValidation.error(
"The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
+ pk.getFingerprint() + ")"));
if (useSSM) {

Check warning on line 1612 in src/main/java/hudson/plugins/ec2/EC2Cloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 1612 is only partially covered, one branch is missing
validations.add(FormValidation.ok("SSM connection mode selected. SSH key validation skipped."));
} else {
String privateKey = "";
if (System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
LOGGER.fine(() -> "static credential is in use");
SSHUserPrivateKey sshCredential = getSshCredential(sshKeysCredentialsId, context);
if (sshCredential != null) {
privateKey = sshCredential.getPrivateKey();
} else {
return FormValidation.error(
"Failed to find credential \"" + sshKeysCredentialsId + "\" in store.");
}
} else {
EC2PrivateKey k = EC2PrivateKey.fetchFromDisk();
if (k == null) {
validations.add(FormValidation.error(
"Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH)));
if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
validations.add(FormValidation.warning(
"Private key file path defined, selected credential will be ignored"));
}
return FormValidation.aggregate(validations);
}
privateKey = k.getPrivateKey();
}
LOGGER.fine(() -> "private key found ok");

if (!privateKey.trim().isEmpty()) {
// check if this key exists
EC2PrivateKey pk = new EC2PrivateKey(privateKey);
if (pk.find(ec2) == null) {
validations.add(FormValidation.error(
"The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
+ pk.getFingerprint() + ")"));
}
}
}

if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
validations.add(
FormValidation.warning("Using private key file instead of selected credential"));
} else {
validations.add(FormValidation.ok("Using private key file"));
if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) {
if (!StringUtils.isEmpty(sshKeysCredentialsId)) {
validations.add(
FormValidation.warning("Using private key file instead of selected credential"));
} else {
validations.add(FormValidation.ok("Using private key file"));
}
}
}

try {
FIPS140Utils.ensurePrivateKeyInFipsMode(privateKey);
} catch (IllegalArgumentException ex) {
validations.add(FormValidation.error(ex, ex.getLocalizedMessage()));
try {
FIPS140Utils.ensurePrivateKeyInFipsMode(privateKey);
} catch (IllegalArgumentException ex) {
validations.add(FormValidation.error(ex, ex.getLocalizedMessage()));
}
}

validations.add(FormValidation.ok(Messages.EC2Cloud_Success()));
Expand Down
78 changes: 73 additions & 5 deletions src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hudson.plugins.ec2.ssh.EC2MacLauncher;
import hudson.plugins.ec2.ssh.EC2UnixLauncher;
import hudson.plugins.ec2.ssh.EC2WindowsSSHLauncher;
import hudson.plugins.ec2.ssm.EC2SSMLauncher;
import hudson.plugins.ec2.win.EC2WindowsLauncher;
import hudson.slaves.NodeProperty;
import java.io.IOException;
Expand Down Expand Up @@ -433,6 +434,71 @@
Boolean metadataSupported,
Boolean enclaveEnabled)
throws FormException, IOException {
this(
name,
instanceId,
templateDescription,
remoteFS,
numExecutors,
labelString,
mode,
initScript,
tmpDir,
nodeProperties,
remoteAdmin,
javaPath,
jvmopts,
stopOnTerminate,
idleTerminationMinutes,
publicDNS,
privateDNS,
tags,
cloudName,
launchTimeout,
amiType,
connectionStrategy,
maxTotalUses,
tenancy,
metadataEndpointEnabled,
metadataTokensRequired,
metadataHopsLimit,
metadataSupported,
enclaveEnabled,
false);
}

public EC2OndemandSlave(
String name,
String instanceId,
String templateDescription,
String remoteFS,
int numExecutors,
String labelString,
Mode mode,
String initScript,
String tmpDir,
List<? extends NodeProperty<?>> nodeProperties,
String remoteAdmin,
String javaPath,
String jvmopts,
boolean stopOnTerminate,
String idleTerminationMinutes,
String publicDNS,
String privateDNS,
List<EC2Tag> tags,
String cloudName,
int launchTimeout,
AMITypeData amiType,
ConnectionStrategy connectionStrategy,
int maxTotalUses,
Tenancy tenancy,
Boolean metadataEndpointEnabled,
Boolean metadataTokensRequired,
Integer metadataHopsLimit,
Boolean metadataSupported,
Boolean enclaveEnabled,
boolean useSSM)
throws FormException, IOException {
super(
name,
instanceId,
Expand All @@ -441,11 +507,13 @@
numExecutors,
mode,
labelString,
(amiType.isWinRMAgent()
? new EC2WindowsLauncher()
: (amiType.isWindows()
? new EC2WindowsSSHLauncher()
: (amiType.isMac() ? new EC2MacLauncher() : new EC2UnixLauncher()))),
useSSM

Check warning on line 510 in src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 510 is only partially covered, one branch is missing
? new EC2SSMLauncher()

Check warning on line 511 in src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 511 is not covered by tests
: (amiType.isWinRMAgent()

Check warning on line 512 in src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 512 is only partially covered, one branch is missing
? new EC2WindowsLauncher()
: (amiType.isWindows()
? new EC2WindowsSSHLauncher()
: (amiType.isMac() ? new EC2MacLauncher() : new EC2UnixLauncher()))),
new EC2RetentionStrategy(idleTerminationMinutes),
initScript,
tmpDir,
Expand Down
56 changes: 53 additions & 3 deletions src/main/java/hudson/plugins/ec2/EC2SpotSlave.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import hudson.model.Descriptor.FormException;
import hudson.plugins.ec2.ssh.EC2UnixLauncher;
import hudson.plugins.ec2.ssh.EC2WindowsSSHLauncher;
import hudson.plugins.ec2.ssm.EC2SSMLauncher;
import hudson.plugins.ec2.win.EC2WindowsLauncher;
import hudson.slaves.NodeProperty;
import java.io.IOException;
Expand Down Expand Up @@ -137,18 +138,67 @@
ConnectionStrategy connectionStrategy,
int maxTotalUses)
throws FormException, IOException {
this(
name,
spotInstanceRequestId,
templateDescription,
remoteFS,
numExecutors,
mode,
initScript,
tmpDir,
labelString,
nodeProperties,
remoteAdmin,
javaPath,
jvmopts,
idleTerminationMinutes,
tags,
cloudName,
launchTimeout,
amiType,
connectionStrategy,
maxTotalUses,
false);
}

public EC2SpotSlave(
String name,
String spotInstanceRequestId,
String templateDescription,
String remoteFS,
int numExecutors,
Mode mode,
String initScript,
String tmpDir,
String labelString,
List<? extends NodeProperty<?>> nodeProperties,
String remoteAdmin,
String javaPath,
String jvmopts,
String idleTerminationMinutes,
List<EC2Tag> tags,
String cloudName,
int launchTimeout,
AMITypeData amiType,
ConnectionStrategy connectionStrategy,
int maxTotalUses,
boolean useSSM)
throws FormException, IOException {

super(
name,
"",
templateDescription,
remoteFS,
numExecutors,
mode,
labelString,
(amiType.isWinRMAgent()
? new EC2WindowsLauncher()
: (amiType.isWindows() ? new EC2WindowsSSHLauncher() : new EC2UnixLauncher())),
useSSM
? new EC2SSMLauncher()
: (amiType.isWinRMAgent()

Check warning on line 199 in src/main/java/hudson/plugins/ec2/EC2SpotSlave.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 141-199 are not covered by tests
? new EC2WindowsLauncher()
: (amiType.isWindows() ? new EC2WindowsSSHLauncher() : new EC2UnixLauncher())),
new EC2RetentionStrategy(idleTerminationMinutes),
initScript,
tmpDir,
Expand Down
Loading
Loading