Skip to content

Commit

Permalink
Use AWS SDK for Java 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
basil committed Nov 26, 2024
1 parent 1538a34 commit 44f273e
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 131 deletions.
24 changes: 16 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.86</version>
<version>5.3</version>
<relativePath />
</parent>

Expand All @@ -54,7 +54,9 @@

<properties>
<changelist>999999-SNAPSHOT</changelist>
<jenkins.version>2.387.3</jenkins.version>
<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
<jenkins.baseline>2.479</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.1</jenkins.version>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<spotbugs.effort>Max</spotbugs.effort>
<spotbugs.threshold>Low</spotbugs.threshold>
Expand All @@ -65,15 +67,25 @@
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.387.x</artifactId>
<version>2543.vfb_1a_5fb_9496d</version>
<artifactId>bom-${jenkins.baseline}.x</artifactId>
<version>3696.vb_b_4e2d1a_0542</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.jenkins.plugins.aws-java-sdk2</groupId>
<artifactId>aws-java-sdk2-ec2</artifactId>
<version>2.29.21-5.v31a_f7d69ea_f9</version>
</dependency>
<dependency>
<groupId>io.jenkins.plugins.aws-java-sdk2</groupId>
<artifactId>aws-java-sdk2-sts</artifactId>
<version>2.29.21-5.v31a_f7d69ea_f9</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
Expand All @@ -94,10 +106,6 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>variant</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.aws-java-sdk</groupId>
<artifactId>aws-java-sdk-ec2</artifactId>
</dependency>
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-extensions</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,16 @@

package com.cloudbees.jenkins.plugins.awscredentials;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.cloudbees.plugins.credentials.CredentialsDescriptor;
import com.cloudbees.plugins.credentials.CredentialsScope;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ProxyConfiguration;
import hudson.Util;
import hudson.util.FormValidation;
import hudson.util.Secret;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
Expand All @@ -60,6 +43,25 @@
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.verb.POST;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.http.apache.ProxyConfiguration;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.StsClientBuilder;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;

public class AWSCredentialsImpl extends BaseAmazonWebServicesCredentials {

Expand Down Expand Up @@ -159,70 +161,65 @@ public boolean requiresToken() {
}

@Override
public AWSCredentials getCredentials() {
AWSCredentials initialCredentials = new BasicAWSCredentials(accessKey, secretKey.getPlainText());
public AwsCredentials resolveCredentials() {
AwsCredentials initialCredentials = AwsBasicCredentials.create(accessKey, secretKey.getPlainText());

if (StringUtils.isBlank(iamRoleArn)) {
return initialCredentials;
} else {
AWSCredentialsProvider baseProvider;
AwsCredentialsProvider baseProvider;
// Handle the case of delegation to instance profile
if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey.getPlainText())) {
baseProvider = null;
} else {
baseProvider = new AWSStaticCredentialsProvider(initialCredentials);
baseProvider = StaticCredentialsProvider.create(initialCredentials);
}

AWSSecurityTokenService client = buildStsClient(baseProvider);
StsClient client = buildStsClient(baseProvider);

AssumeRoleRequest assumeRequest =
createAssumeRoleRequest(iamRoleArn, iamExternalId).withDurationSeconds(this.getStsTokenDuration());
AssumeRoleRequest.Builder assumeRequest =
createAssumeRoleRequest(iamRoleArn, iamExternalId).durationSeconds(this.getStsTokenDuration());

AssumeRoleResult assumeResult = client.assumeRole(assumeRequest);
AssumeRoleResponse assumeResult = client.assumeRole(assumeRequest.build());

return new BasicSessionCredentials(
assumeResult.getCredentials().getAccessKeyId(),
assumeResult.getCredentials().getSecretAccessKey(),
assumeResult.getCredentials().getSessionToken());
return AwsSessionCredentials.create(
assumeResult.credentials().accessKeyId(),
assumeResult.credentials().secretAccessKey(),
assumeResult.credentials().sessionToken());
}
}

private static String determineClientRegion() {
private static Region determineClientRegion() {
// Check for available region from the SDK, otherwise specify default
String clientRegion = null;
DefaultAwsRegionProviderChain sdkRegionLookup = new DefaultAwsRegionProviderChain();
Region clientRegion = null;
AwsRegionProvider sdkRegionLookup = new DefaultAwsRegionProviderChain();
try {
clientRegion = sdkRegionLookup.getRegion();
} catch (RuntimeException e) {
LOGGER.log(Level.WARNING, "Could not find default region using SDK lookup.", e);
}
if (clientRegion == null) {
clientRegion = Regions.DEFAULT_REGION.getName();
clientRegion = Region.US_WEST_2;
}
return clientRegion;
}

@Override
public AWSCredentials getCredentials(String mfaToken) {
AWSCredentials initialCredentials = new BasicAWSCredentials(accessKey, secretKey.getPlainText());
public AwsCredentials getCredentials(String mfaToken) {
AwsCredentials initialCredentials = AwsBasicCredentials.create(accessKey, secretKey.getPlainText());

AssumeRoleRequest assumeRequest = createAssumeRoleRequest(iamRoleArn, iamExternalId)
.withSerialNumber(iamMfaSerialNumber)
.withTokenCode(mfaToken)
.withDurationSeconds(this.getStsTokenDuration());
AssumeRoleRequest.Builder assumeRequest = createAssumeRoleRequest(iamRoleArn, iamExternalId)
.serialNumber(iamMfaSerialNumber)
.tokenCode(mfaToken)
.durationSeconds(this.getStsTokenDuration());

AWSSecurityTokenService awsSecurityTokenService = getAWSSecurityTokenService(initialCredentials);
AssumeRoleResult assumeResult = awsSecurityTokenService.assumeRole(assumeRequest);
StsClient stsClient = getStsClient(initialCredentials);
AssumeRoleResponse assumeResult = stsClient.assumeRole(assumeRequest.build());

return new BasicSessionCredentials(
assumeResult.getCredentials().getAccessKeyId(),
assumeResult.getCredentials().getSecretAccessKey(),
assumeResult.getCredentials().getSessionToken());
}

@Override
public void refresh() {
// no-op
return AwsSessionCredentials.create(
assumeResult.credentials().accessKeyId(),
assumeResult.credentials().secretAccessKey(),
assumeResult.credentials().sessionToken());
}

@Override
Expand All @@ -233,63 +230,62 @@ public String getDisplayName() {
return accessKey + ":" + iamRoleArn;
}

/*package*/ static AWSSecurityTokenService buildStsClient(AWSCredentialsProvider provider) {
/*package*/ static StsClient buildStsClient(AwsCredentialsProvider provider) {
// Check for available region from the SDK, otherwise specify default
String clientRegion = determineClientRegion();
Region clientRegion = determineClientRegion();

AWSSecurityTokenServiceClientBuilder builder = AWSSecurityTokenServiceClientBuilder.standard()
.withRegion(clientRegion)
.withClientConfiguration(getClientConfiguration());
StsClientBuilder builder = StsClient.builder().region(clientRegion).httpClient(getClientConfiguration());

if (provider != null) {
builder = builder.withCredentials(provider);
builder = builder.credentialsProvider(provider);
}

return builder.build();
}

private static AssumeRoleRequest createAssumeRoleRequest(String iamRoleArn, String iamExternalId) {
AssumeRoleRequest retval =
new AssumeRoleRequest().withRoleArn(iamRoleArn).withRoleSessionName("Jenkins");
private static AssumeRoleRequest.Builder createAssumeRoleRequest(String iamRoleArn, String iamExternalId) {
AssumeRoleRequest.Builder retval =
AssumeRoleRequest.builder().roleArn(iamRoleArn).roleSessionName("Jenkins");
if (iamExternalId != null && !iamExternalId.isEmpty()) {
return retval.withExternalId(iamExternalId);
return retval.externalId(iamExternalId);
}
return retval;
}

/**
* Provides the {@link AWSSecurityTokenService} for a given {@link AWSCredentials}
* Provides the {@link StsClient} for a given {@link AwsCredentials}
*
* @param awsCredentials
* @return {@link AWSSecurityTokenService}
* @return {@link StsClient}
*/
private static AWSSecurityTokenService getAWSSecurityTokenService(AWSCredentials awsCredentials) {
ClientConfiguration clientConfiguration = getClientConfiguration();
String clientRegion = determineClientRegion();
return AWSSecurityTokenServiceClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withClientConfiguration(clientConfiguration)
private static StsClient getStsClient(AwsCredentials awsCredentials) {
SdkHttpClient clientConfiguration = getClientConfiguration();
Region clientRegion = determineClientRegion();
return StsClient.builder()
.region(clientRegion)
.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
.httpClient(clientConfiguration)
.build();
}

/**
* Provides the {@link ClientConfiguration}
* Provides the {@link SdkHttpClient}
*
* @return {@link ClientConfiguration}
* @return {@link SdkHttpClient}
*/
private static ClientConfiguration getClientConfiguration() {
private static SdkHttpClient getClientConfiguration() {
Jenkins instance = Jenkins.getInstanceOrNull();

ProxyConfiguration proxy = instance != null ? instance.proxy : null;
ClientConfiguration clientConfiguration = new ClientConfiguration();
hudson.ProxyConfiguration proxy = instance != null ? instance.proxy : null;
ApacheHttpClient.Builder builder = ApacheHttpClient.builder();
if (proxy != null && proxy.name != null && !proxy.name.isEmpty()) {
clientConfiguration.setProxyHost(proxy.name);
clientConfiguration.setProxyPort(proxy.port);
clientConfiguration.setProxyUsername(proxy.getUserName());
clientConfiguration.setProxyPassword(proxy.getPassword());
builder.proxyConfiguration(ProxyConfiguration.builder()
.endpoint(URI.create(String.format("%s:%s", proxy.name, proxy.port)))
.username(proxy.getUserName())
.password(proxy.getPassword())
.build());
}
return clientConfiguration;
return builder.build();
}

@Extension
Expand Down Expand Up @@ -325,31 +321,31 @@ public FormValidation doCheckSecretKey(
return FormValidation.error(Messages.AWSCredentialsImpl_SpecifySecretAccessKey());
}

AWSCredentials awsCredentials = new BasicAWSCredentials(
AwsCredentials awsCredentials = AwsBasicCredentials.create(
accessKey, Secret.fromString(secretKey).getPlainText());

// If iamRoleArn is specified, swap out the credentials.
if (!StringUtils.isBlank(iamRoleArn)) {

AssumeRoleRequest assumeRequest =
createAssumeRoleRequest(iamRoleArn, iamExternalId).withDurationSeconds(stsTokenDuration);
AssumeRoleRequest.Builder assumeRequest =
createAssumeRoleRequest(iamRoleArn, iamExternalId).durationSeconds(stsTokenDuration);

if (!StringUtils.isBlank(iamMfaSerialNumber)) {
if (StringUtils.isBlank(iamMfaToken)) {
return FormValidation.error(Messages.AWSCredentialsImpl_SpecifyMFAToken());
}
assumeRequest =
assumeRequest.withSerialNumber(iamMfaSerialNumber).withTokenCode(iamMfaToken);
assumeRequest.serialNumber(iamMfaSerialNumber).tokenCode(iamMfaToken);
}

try {
AWSSecurityTokenService awsSecurityTokenService = getAWSSecurityTokenService(awsCredentials);
AssumeRoleResult assumeResult = awsSecurityTokenService.assumeRole(assumeRequest);
StsClient stsClient = getStsClient(awsCredentials);
AssumeRoleResponse assumeResult = stsClient.assumeRole(assumeRequest.build());

awsCredentials = new BasicSessionCredentials(
assumeResult.getCredentials().getAccessKeyId(),
assumeResult.getCredentials().getSecretAccessKey(),
assumeResult.getCredentials().getSessionToken());
awsCredentials = AwsSessionCredentials.create(
assumeResult.credentials().accessKeyId(),
assumeResult.credentials().secretAccessKey(),
assumeResult.credentials().sessionToken());
} catch (RuntimeException e) {
LOGGER.log(
Level.WARNING,
Expand All @@ -360,26 +356,34 @@ public FormValidation doCheckSecretKey(
}
}

AmazonEC2 ec2 = new AmazonEC2Client(awsCredentials, getClientConfiguration());
Ec2Client ec2 = Ec2Client.builder()
.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
.httpClient(getClientConfiguration())
.build();

// TODO better/smarter validation of the credentials instead of verifying the permission on EC2.READ in

Check warning on line 364 in src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: better/smarter validation of the credentials instead of verifying the permission on EC2.READ in
// us-east-1
String region = "us-east-1";
try {
DescribeAvailabilityZonesResult zonesResult = ec2.describeAvailabilityZones();
DescribeAvailabilityZonesResponse zonesResult = ec2.describeAvailabilityZones();
return FormValidation.ok(Messages.AWSCredentialsImpl_CredentialsValidWithAccessToNZones(
zonesResult.getAvailabilityZones().size()));
} catch (AmazonServiceException e) {
if (HttpURLConnection.HTTP_UNAUTHORIZED == e.getStatusCode()) {
zonesResult.availabilityZones().size()));
} catch (AwsServiceException e) {
if (HttpURLConnection.HTTP_UNAUTHORIZED
== e.awsErrorDetails().sdkHttpResponse().statusCode()) {
return FormValidation.warning(Messages.AWSCredentialsImpl_CredentialsInValid(e.getMessage()));
} else if (HttpURLConnection.HTTP_FORBIDDEN == e.getStatusCode()) {
} else if (HttpURLConnection.HTTP_FORBIDDEN
== e.awsErrorDetails().sdkHttpResponse().statusCode()) {
return FormValidation.ok(
Messages.AWSCredentialsImpl_CredentialsValidWithoutAccessToAwsServiceInZone(
e.getServiceName(), region, e.getErrorMessage() + " (" + e.getErrorCode() + ")"));
e.awsErrorDetails().serviceName(),
region,
e.awsErrorDetails().errorMessage() + " ("
+ e.awsErrorDetails().errorCode() + ")"));
} else {
return FormValidation.error(e.getMessage());
}
} catch (AmazonClientException e) {
} catch (SdkException e) {

Check warning on line 386 in src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 153-386 are not covered by tests
return FormValidation.error(e.getMessage());
}
}
Expand Down
Loading

0 comments on commit 44f273e

Please sign in to comment.