diff --git a/pom.xml b/pom.xml index a955dd4..e01516f 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ org.jenkins-ci.plugins plugin - 4.86 + 4.88 @@ -54,7 +54,9 @@ 999999-SNAPSHOT - 2.387.3 + + 2.452 + ${jenkins.baseline}.4 jenkinsci/${project.artifactId}-plugin Max Low @@ -65,8 +67,8 @@ io.jenkins.tools.bom - bom-2.387.x - 2543.vfb_1a_5fb_9496d + bom-${jenkins.baseline}.x + 3761.vd922730f0fd2 pom import @@ -74,6 +76,14 @@ + + io.jenkins.plugins.aws-java-sdk2 + aws-java-sdk2-ec2 + + + io.jenkins.plugins.aws-java-sdk2 + aws-java-sdk2-sts + org.jenkins-ci.plugins credentials diff --git a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java index 65bb65e..bfe5c8d 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AWSCredentialsImpl.java @@ -25,23 +25,10 @@ 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.AWSSessionCredentials; 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; @@ -52,6 +39,8 @@ import hudson.util.FormValidation; import hudson.util.Secret; import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; @@ -60,6 +49,24 @@ 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.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 { @@ -159,67 +166,112 @@ 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 resolveCredentials(String mfaToken) { + AwsCredentials initialCredentials = AwsBasicCredentials.create(accessKey, secretKey.getPlainText()); + + AssumeRoleRequest.Builder assumeRequest = createAssumeRoleRequest(iamRoleArn, iamExternalId) + .serialNumber(iamMfaSerialNumber) + .tokenCode(mfaToken) + .durationSeconds(this.getStsTokenDuration()); - AssumeRoleRequest assumeRequest = createAssumeRoleRequest(iamRoleArn, iamExternalId) - .withSerialNumber(iamMfaSerialNumber) - .withTokenCode(mfaToken) - .withDurationSeconds(this.getStsTokenDuration()); + StsClient stsClient = getStsClient(initialCredentials); + AssumeRoleResponse assumeResult = stsClient.assumeRole(assumeRequest.build()); - AWSSecurityTokenService awsSecurityTokenService = getAWSSecurityTokenService(initialCredentials); - AssumeRoleResult assumeResult = awsSecurityTokenService.assumeRole(assumeRequest); + return AwsSessionCredentials.create( + assumeResult.credentials().accessKeyId(), + assumeResult.credentials().secretAccessKey(), + assumeResult.credentials().sessionToken()); + } + + /** + * @deprecated use {@link #resolveCredentials()} + */ + @Deprecated + @Override + public AWSCredentials getCredentials() { + return fromAwsCredentials(resolveCredentials()); + } + /** + * @deprecated use {@link #resolveCredentials(String)} + */ + @Deprecated + @Override + public AWSCredentials getCredentials(String mfaToken) { + return fromAwsCredentials(resolveCredentials(mfaToken)); + } + + private static AWSCredentials fromAwsCredentials(AwsCredentials awsCredentials) { + Objects.requireNonNull(awsCredentials); + if (awsCredentials instanceof AwsSessionCredentials) { + AwsSessionCredentials awsSessionCredentials = (AwsSessionCredentials) awsCredentials; + return fromAwsSessionCredentials(awsSessionCredentials); + } + return new BasicAWSCredentials( + awsCredentials.accessKeyId(), + awsCredentials.secretAccessKey(), + awsCredentials.accountId().orElse(null), + awsCredentials.providerName().orElse(null)); + } + + private static AWSSessionCredentials fromAwsSessionCredentials(AwsSessionCredentials awsSessionCredentials) { + Objects.requireNonNull(awsSessionCredentials); return new BasicSessionCredentials( - assumeResult.getCredentials().getAccessKeyId(), - assumeResult.getCredentials().getSecretAccessKey(), - assumeResult.getCredentials().getSessionToken()); + awsSessionCredentials.accessKeyId(), + awsSessionCredentials.secretAccessKey(), + awsSessionCredentials.sessionToken(), + awsSessionCredentials.accountId().orElse(null), + awsSessionCredentials.providerName().orElse(null)); } + /** + * @deprecated removed without replacement + */ + @Deprecated @Override public void refresh() { // no-op @@ -233,63 +285,65 @@ 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(getHttpClient()); 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 = getHttpClient(); + 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 getHttpClient() { Jenkins instance = Jenkins.getInstanceOrNull(); ProxyConfiguration proxy = instance != null ? instance.proxy : null; - ClientConfiguration clientConfiguration = new ClientConfiguration(); + 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()); + software.amazon.awssdk.http.apache.ProxyConfiguration.Builder proxyConfiguration = + software.amazon.awssdk.http.apache.ProxyConfiguration.builder() + .endpoint(URI.create(String.format("http://%s:%s", proxy.name, proxy.port))); + if (proxy.getUserName() != null) { + proxyConfiguration.username(proxy.getUserName()); + proxyConfiguration.password(Secret.toString(proxy.getSecretPassword())); + } + builder.proxyConfiguration(proxyConfiguration.build()); } - return clientConfiguration; + return builder.build(); } @Extension @@ -325,31 +379,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, @@ -360,26 +414,34 @@ public FormValidation doCheckSecretKey( } } - AmazonEC2 ec2 = new AmazonEC2Client(awsCredentials, getClientConfiguration()); + Ec2Client ec2 = Ec2Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) + .httpClient(getHttpClient()) + .build(); // TODO 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) { return FormValidation.error(e.getMessage()); } } diff --git a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentials.java b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentials.java index b375234..c942a6f 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentials.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentials.java @@ -33,17 +33,28 @@ import com.cloudbees.plugins.credentials.common.StandardCredentials; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Util; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; /** - * A {@link AWSCredentialsProvider} that is bound to the Jenkins {@link Credentials} api. + * A {@link AwsCredentialsProvider} that is bound to the Jenkins {@link Credentials} api. + * + * For compatibility reasons, this class also implements {@link AWSCredentialsProvider}. */ @NameWith(value = AmazonWebServicesCredentials.NameProvider.class, priority = 1) -public interface AmazonWebServicesCredentials extends StandardCredentials, AWSCredentialsProvider { +public interface AmazonWebServicesCredentials + extends StandardCredentials, AwsCredentialsProvider, AWSCredentialsProvider { /** Serial UID from 1.16. */ long serialVersionUID = -8931505925778535681L; String getDisplayName(); + AwsCredentials resolveCredentials(String mfaToken); + + /** + * @deprecated use {@link #resolveCredentials(String)} + */ + @Deprecated AWSCredentials getCredentials(String mfaToken); /** diff --git a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentialsBinding.java b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentialsBinding.java index 6df57a6..f409b84 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentialsBinding.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/awscredentials/AmazonWebServicesCredentialsBinding.java @@ -25,12 +25,6 @@ package com.cloudbees.jenkins.plugins.awscredentials; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSSessionCredentials; -import com.amazonaws.auth.AWSSessionCredentialsProvider; -import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; -import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; @@ -50,6 +44,12 @@ import org.jenkinsci.plugins.credentialsbinding.MultiBinding; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +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.services.sts.StsClient; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider; +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; /** * @author Nicolas De Loof @@ -131,39 +131,40 @@ protected Class type() { @Override public MultiEnvironment bind(@NonNull Run build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { - AWSCredentialsProvider provider = getCredentials(build); + AwsCredentialsProvider provider = getCredentials(build); if (!StringUtils.isEmpty(this.roleArn)) { provider = this.assumeRoleProvider(provider); } - AWSCredentials credentials = provider.getCredentials(); + AwsCredentials credentials = provider.resolveCredentials(); Map m = new HashMap(); - m.put(accessKeyVariable, credentials.getAWSAccessKeyId()); - m.put(secretKeyVariable, credentials.getAWSSecretKey()); + m.put(accessKeyVariable, credentials.accessKeyId()); + m.put(secretKeyVariable, credentials.secretAccessKey()); // If role has been assumed, STS requires AWS_SESSION_TOKEN variable set too. - if (credentials instanceof AWSSessionCredentials) { - m.put(SESSION_TOKEN_VARIABLE_NAME, ((AWSSessionCredentials) credentials).getSessionToken()); + if (credentials instanceof AwsSessionCredentials) { + m.put(SESSION_TOKEN_VARIABLE_NAME, ((AwsSessionCredentials) credentials).sessionToken()); } return new MultiEnvironment(m); } - private AWSSessionCredentialsProvider assumeRoleProvider(AWSCredentialsProvider baseProvider) { - AWSSecurityTokenService stsClient = AWSCredentialsImpl.buildStsClient(baseProvider); + private AwsCredentialsProvider assumeRoleProvider(AwsCredentialsProvider baseProvider) { + StsClient stsClient = AWSCredentialsImpl.buildStsClient(baseProvider); String roleSessionName = StringUtils.defaultIfBlank(this.roleSessionName, "Jenkins"); - STSAssumeRoleSessionCredentialsProvider.Builder assumeRoleProviderBuilder = - new STSAssumeRoleSessionCredentialsProvider.Builder(this.roleArn, roleSessionName) - .withStsClient(stsClient); + AssumeRoleRequest.Builder assumeRoleRequest = + AssumeRoleRequest.builder().roleArn(this.roleArn).roleSessionName(roleSessionName); if (this.roleSessionDurationSeconds > 0) { - assumeRoleProviderBuilder = - assumeRoleProviderBuilder.withRoleSessionDurationSeconds(this.roleSessionDurationSeconds); + assumeRoleRequest.durationSeconds(this.roleSessionDurationSeconds); } - return assumeRoleProviderBuilder.build(); + return StsAssumeRoleCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(assumeRoleRequest.build()) + .build(); } @Override