4646import hudson .model .PeriodicWork ;
4747import hudson .model .TaskListener ;
4848import hudson .plugins .ec2 .util .AmazonEC2Factory ;
49+ import hudson .plugins .ec2 .util .AmazonSSMFactory ;
4950import hudson .plugins .ec2 .util .FIPS140Utils ;
5051import hudson .plugins .ec2 .util .KeyPair ;
5152import hudson .security .ACL ;
136137import software .amazon .awssdk .services .ec2 .model .SpotInstanceRequest ;
137138import software .amazon .awssdk .services .ec2 .model .SpotInstanceState ;
138139import software .amazon .awssdk .services .ec2 .model .Tag ;
140+ import software .amazon .awssdk .services .ssm .SsmClient ;
139141import software .amazon .awssdk .services .sts .StsClient ;
140142import software .amazon .awssdk .services .sts .StsClientBuilder ;
141143import software .amazon .awssdk .services .sts .auth .StsAssumeRoleCredentialsProvider ;
@@ -211,6 +213,8 @@ public class EC2Cloud extends Cloud {
211213
212214 private boolean noDelayProvisioning ;
213215
216+ private boolean useSSM ;
217+
214218 private boolean cleanUpOrphanedNodes ;
215219
216220 private transient volatile Ec2Client connection ;
@@ -299,6 +303,9 @@ protected EC2Cloud(
299303
300304 @ CheckForNull
301305 public EC2PrivateKey resolvePrivateKey () {
306+ if (useSSM ) {
307+ return null ;
308+ }
302309 if (!System .getProperty (SSH_PRIVATE_KEY_FILEPATH , "" ).isEmpty ()) {
303310 LOGGER .fine (() -> "(resolvePrivateKey) secret key file configured, will load from disk" );
304311 return EC2PrivateKey .fetchFromDisk ();
@@ -391,6 +398,15 @@ public void setNoDelayProvisioning(boolean noDelayProvisioning) {
391398 this .noDelayProvisioning = noDelayProvisioning ;
392399 }
393400
401+ public boolean isUseSSM () {
402+ return useSSM ;
403+ }
404+
405+ @ DataBoundSetter
406+ public void setUseSSM (boolean useSSM ) {
407+ this .useSSM = useSSM ;
408+ }
409+
394410 public boolean isCleanUpOrphanedNodes () {
395411 return cleanUpOrphanedNodes ;
396412 }
@@ -640,6 +656,9 @@ public Collection<SlaveTemplate> getTemplates(Label label) {
640656 */
641657 @ CheckForNull
642658 public synchronized KeyPair getKeyPair () throws SdkException , IOException {
659+ if (useSSM ) {
660+ return null ;
661+ }
643662 if (usableKeyPair == null ) {
644663 EC2PrivateKey ec2PrivateKey = this .resolvePrivateKey ();
645664 if (ec2PrivateKey != null ) {
@@ -1334,6 +1353,11 @@ public Ec2Client connect() throws SdkException {
13341353 }
13351354 }
13361355
1356+ public SsmClient createSsmClient () {
1357+ return AmazonSSMFactory .getInstance ()
1358+ .connect (createCredentialsProvider (), parseRegion (getRegion ()), parseEndpoint (getAltEC2Endpoint ()));
1359+ }
1360+
13371361 public static SdkHttpClient getHttpClient () {
13381362 Jenkins instance = Jenkins .getInstanceOrNull ();
13391363
@@ -1463,12 +1487,19 @@ public FormValidation doCheckRoleSessionName(
14631487
14641488 @ RequirePOST
14651489 public FormValidation doCheckSshKeysCredentialsId (
1466- @ AncestorInPath ItemGroup context , @ QueryParameter String value ) throws IOException , ServletException {
1490+ @ AncestorInPath ItemGroup context ,
1491+ @ QueryParameter String value ,
1492+ @ QueryParameter boolean useSSM )
1493+ throws IOException , ServletException {
14671494 if (!Jenkins .get ().hasPermission (Jenkins .ADMINISTER )) {
14681495 // Don't do anything if the user is only reading the configuration
14691496 return FormValidation .ok ();
14701497 }
14711498
1499+ if (useSSM ) {
1500+ return FormValidation .ok ();
1501+ }
1502+
14721503 String privateKey ;
14731504 List <FormValidation > validations = new ArrayList <>();
14741505
@@ -1559,7 +1590,8 @@ public FormValidation doTestConnection(
15591590 @ QueryParameter String credentialsId ,
15601591 @ QueryParameter String sshKeysCredentialsId ,
15611592 @ QueryParameter String roleArn ,
1562- @ QueryParameter String roleSessionName )
1593+ @ QueryParameter String roleSessionName ,
1594+ @ QueryParameter boolean useSSM )
15631595 throws IOException , ServletException {
15641596 if (!Jenkins .get ().hasPermission (Jenkins .ADMINISTER )) {
15651597 return FormValidation .ok ();
@@ -1568,30 +1600,6 @@ public FormValidation doTestConnection(
15681600 List <FormValidation > validations = new ArrayList <>();
15691601
15701602 LOGGER .fine (() -> "begin doTestConnection()" );
1571- String privateKey = "" ;
1572- if (System .getProperty (SSH_PRIVATE_KEY_FILEPATH , "" ).isEmpty ()) {
1573- LOGGER .fine (() -> "static credential is in use" );
1574- SSHUserPrivateKey sshCredential = getSshCredential (sshKeysCredentialsId , context );
1575- if (sshCredential != null ) {
1576- privateKey = sshCredential .getPrivateKey ();
1577- } else {
1578- return FormValidation .error (
1579- "Failed to find credential \" " + sshKeysCredentialsId + "\" in store." );
1580- }
1581- } else {
1582- EC2PrivateKey k = EC2PrivateKey .fetchFromDisk ();
1583- if (k == null ) {
1584- validations .add (FormValidation .error (
1585- "Failed to find private key file " + System .getProperty (SSH_PRIVATE_KEY_FILEPATH )));
1586- if (!StringUtils .isEmpty (sshKeysCredentialsId )) {
1587- validations .add (FormValidation .warning (
1588- "Private key file path defined, selected credential will be ignored" ));
1589- }
1590- return FormValidation .aggregate (validations );
1591- }
1592- privateKey = k .getPrivateKey ();
1593- }
1594- LOGGER .fine (() -> "private key found ok" );
15951603
15961604 if (Util .fixEmpty (region ) == null ) {
15971605 region = DEFAULT_EC2_HOST ;
@@ -1603,29 +1611,60 @@ public FormValidation doTestConnection(
16031611 .connect (credentialsProvider , parseRegion (region ), parseEndpoint (altEC2Endpoint ));
16041612 ec2 .describeInstances ();
16051613
1606- if (!privateKey .trim ().isEmpty ()) {
1607- // check if this key exists
1608- EC2PrivateKey pk = new EC2PrivateKey (privateKey );
1609- if (pk .find (ec2 ) == null ) {
1610- validations .add (FormValidation .error (
1611- "The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
1612- + pk .getFingerprint () + ")" ));
1614+ if (useSSM ) {
1615+ validations .add (
1616+ FormValidation .ok ("SSM connection mode selected. SSH key validation skipped." ));
1617+ } else {
1618+ String privateKey = "" ;
1619+ if (System .getProperty (SSH_PRIVATE_KEY_FILEPATH , "" ).isEmpty ()) {
1620+ LOGGER .fine (() -> "static credential is in use" );
1621+ SSHUserPrivateKey sshCredential = getSshCredential (sshKeysCredentialsId , context );
1622+ if (sshCredential != null ) {
1623+ privateKey = sshCredential .getPrivateKey ();
1624+ } else {
1625+ return FormValidation .error (
1626+ "Failed to find credential \" " + sshKeysCredentialsId + "\" in store." );
1627+ }
1628+ } else {
1629+ EC2PrivateKey k = EC2PrivateKey .fetchFromDisk ();
1630+ if (k == null ) {
1631+ validations .add (FormValidation .error (
1632+ "Failed to find private key file "
1633+ + System .getProperty (SSH_PRIVATE_KEY_FILEPATH )));
1634+ if (!StringUtils .isEmpty (sshKeysCredentialsId )) {
1635+ validations .add (FormValidation .warning (
1636+ "Private key file path defined, selected credential will be ignored" ));
1637+ }
1638+ return FormValidation .aggregate (validations );
1639+ }
1640+ privateKey = k .getPrivateKey ();
1641+ }
1642+ LOGGER .fine (() -> "private key found ok" );
1643+
1644+ if (!privateKey .trim ().isEmpty ()) {
1645+ // check if this key exists
1646+ EC2PrivateKey pk = new EC2PrivateKey (privateKey );
1647+ if (pk .find (ec2 ) == null ) {
1648+ validations .add (FormValidation .error (
1649+ "The EC2 key pair private key isn't registered to this EC2 region (fingerprint is "
1650+ + pk .getFingerprint () + ")" ));
1651+ }
16131652 }
1614- }
16151653
1616- if (!System .getProperty (SSH_PRIVATE_KEY_FILEPATH , "" ).isEmpty ()) {
1617- if (!StringUtils .isEmpty (sshKeysCredentialsId )) {
1618- validations .add (
1619- FormValidation .warning ("Using private key file instead of selected credential" ));
1620- } else {
1621- validations .add (FormValidation .ok ("Using private key file" ));
1654+ if (!System .getProperty (SSH_PRIVATE_KEY_FILEPATH , "" ).isEmpty ()) {
1655+ if (!StringUtils .isEmpty (sshKeysCredentialsId )) {
1656+ validations .add (
1657+ FormValidation .warning ("Using private key file instead of selected credential" ));
1658+ } else {
1659+ validations .add (FormValidation .ok ("Using private key file" ));
1660+ }
16221661 }
1623- }
16241662
1625- try {
1626- FIPS140Utils .ensurePrivateKeyInFipsMode (privateKey );
1627- } catch (IllegalArgumentException ex ) {
1628- validations .add (FormValidation .error (ex , ex .getLocalizedMessage ()));
1663+ try {
1664+ FIPS140Utils .ensurePrivateKeyInFipsMode (privateKey );
1665+ } catch (IllegalArgumentException ex ) {
1666+ validations .add (FormValidation .error (ex , ex .getLocalizedMessage ()));
1667+ }
16291668 }
16301669
16311670 validations .add (FormValidation .ok (Messages .EC2Cloud_Success ()));
0 commit comments