diff --git a/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/CloudWatchAwsGlobalConfiguration.java b/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/CloudWatchAwsGlobalConfiguration.java
index 439383e..2c9ff7d 100644
--- a/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/CloudWatchAwsGlobalConfiguration.java
+++ b/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/CloudWatchAwsGlobalConfiguration.java
@@ -24,24 +24,12 @@
package io.jenkins.plugins.pipeline_cloudwatch_logs;
-import java.io.IOException;
-
-import edu.umd.cs.findbugs.annotations.NonNull;
-
-import org.apache.commons.lang.StringUtils;
-import org.jenkinsci.Symbol;
-import org.kohsuke.accmod.Restricted;
-import org.kohsuke.accmod.restrictions.NoExternalUse;
-import org.kohsuke.stapler.DataBoundSetter;
-import org.kohsuke.stapler.QueryParameter;
-import org.kohsuke.stapler.interceptor.RequirePOST;
-
+import com.amazonaws.auth.AWSCredentialsProviderChain;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.logs.AWSLogs;
import com.amazonaws.services.logs.AWSLogsClientBuilder;
import com.amazonaws.services.logs.model.FilterLogEventsRequest;
-
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
@@ -49,7 +37,15 @@
import hudson.util.FormValidation;
import io.jenkins.plugins.aws.global_configuration.AbstractAwsGlobalConfiguration;
import io.jenkins.plugins.aws.global_configuration.CredentialsAwsGlobalConfiguration;
+import java.io.IOException;
import jenkins.model.Jenkins;
+import org.apache.commons.lang.StringUtils;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.DataBoundSetter;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* Store the AWS configuration to save it on a separate file
@@ -58,6 +54,9 @@
@Extension
public class CloudWatchAwsGlobalConfiguration extends AbstractAwsGlobalConfiguration {
+ // mutable for tests
+ static AWSCredentialsProviderChain awsCredentialsProviderChain = DefaultAWSCredentialsProviderChain.getInstance();
+
/**
* Name of the CloudWatch log group.
*/
@@ -117,7 +116,7 @@ static AWSLogsClientBuilder getAWSLogsClientBuilder(String region, String creden
CredentialsAwsGlobalConfiguration.get().sessionCredentials(builder, region, credentialsId));
return builder.withCredentials(credentialsProvider);
} else {
- return builder.withCredentials(new DefaultAWSCredentialsProviderChain());
+ return builder.withCredentials(awsCredentialsProviderChain);
}
}
@@ -133,18 +132,18 @@ public FormValidation doCheckLogGroupName(@QueryParameter String logGroupName) {
public FormValidation doValidate(@QueryParameter String logGroupName, @QueryParameter String region,
@QueryParameter String credentialsId) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
- return validate(logGroupName, Util.fixEmptyAndTrim(region), Util.fixEmptyAndTrim(credentialsId));
+ return validate(logGroupName, Util.fixEmptyAndTrim(region), Util.fixEmptyAndTrim(credentialsId), true);
}
@Restricted(NoExternalUse.class)
- FormValidation validate(String logGroupName, String region, String credentialsId) {
+ FormValidation validate(String logGroupName, String region, String credentialsId, boolean abbreviate) {
AWSLogs client;
try {
AWSLogsClientBuilder builder = getAWSLogsClientBuilder(region, credentialsId);
client = builder.build();
} catch (Exception x) {
String msg = processExceptionMessage(x);
- return FormValidation.error("Unable to validate credentials: " + StringUtils.abbreviate(msg, 200));
+ return FormValidation.error("Unable to validate credentials: " + (abbreviate ? StringUtils.abbreviate(msg, 200) : msg));
}
try {
@@ -161,7 +160,7 @@ FormValidation validate(String logGroupName, String region, String credentialsId
}
} catch (Exception x) {
String msg = processExceptionMessage(x);
- return FormValidation.error("Unable to simulate policy restriction: " + StringUtils.abbreviate(msg, 200));
+ return FormValidation.error("Unable to simulate policy restriction: " + (abbreviate ? StringUtils.abbreviate(msg, 200) : msg));
}
return FormValidation.ok("success");
}
diff --git a/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/LogStreamState.java b/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/LogStreamState.java
index 24a34cd..d74cacb 100644
--- a/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/LogStreamState.java
+++ b/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/LogStreamState.java
@@ -46,6 +46,7 @@
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.Credentials;
+import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest;
import com.amazonaws.services.securitytoken.model.GetFederationTokenRequest;
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl;
import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials;
@@ -71,6 +72,7 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.UUID;
import jenkins.security.HMACConfidentialKey;
import jenkins.security.SlaveToMasterCallable;
import jenkins.util.JenkinsJVM;
@@ -190,6 +192,8 @@ Auth authenticate() throws IOException {
if (jenkinsCredentials != null) {
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(jenkinsCredentials.getCredentials());
builder.withCredentials(credentialsProvider);
+ } else {
+ builder.withCredentials(CloudWatchAwsGlobalConfiguration.awsCredentialsProviderChain);
}
AWSCredentialsProvider credentialsProvider = builder.getCredentials();
AWSCredentials masterCredentials = credentialsProvider != null ? credentialsProvider.getCredentials() : null;
@@ -205,7 +209,7 @@ Auth authenticate() throws IOException {
}
if (masterCredentials instanceof AWSSessionCredentials) {
// otherwise would just throw AWSSecurityTokenServiceException: Cannot call GetFederationToken with session credentials
- String role = null;
+ String role = System.getenv("AWS_CHAINED_ROLE"); // TODO define in CloudWatchAwsGlobalConfiguration?
if (jenkinsCredentials instanceof AWSCredentialsImpl) {
role = Util.fixEmpty(((AWSCredentialsImpl) jenkinsCredentials).getIamRoleArn());
}
@@ -227,15 +231,17 @@ Auth authenticate() throws IOException {
private Auth assumeRole(String role, String region, String agentLogStreamName) {
// TODO would be cleaner if AmazonWebServicesCredentials had a getCredentials overload taking a policy
AWSSecurityTokenServiceClientBuilder builder = AWSSecurityTokenServiceClientBuilder.standard();
+ builder.withCredentials(CloudWatchAwsGlobalConfiguration.awsCredentialsProviderChain);
if (region != null) {
builder = builder.withRegion(region);
}
- Auth auth = new Auth(builder.build().assumeRole(new AssumeRoleRequest().
+ Credentials credentials = builder.build().assumeRole(new AssumeRoleRequest().
withRoleArn(role).
- withRoleSessionName("CloudWatchSender"). // TODO does this need to be unique?
+ withRoleSessionName("CloudWatchSender-" + UUID.randomUUID()).
withPolicy(policy(agentLogStreamName))).
- getCredentials(), region, agentLogStreamName);
- LOGGER.log(Level.FINE, "AssumeRole succeeded; using {0}", auth.accessKeyId);
+ getCredentials();
+ Auth auth = new Auth(credentials, region, agentLogStreamName);
+ LOGGER.fine(() -> "AssumeRole succeeded; using " + AWSSecurityTokenServiceClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(new BasicSessionCredentials(credentials.getAccessKeyId(), credentials.getSecretAccessKey(), credentials.getSessionToken()))).build().getCallerIdentity(new GetCallerIdentityRequest()));
return auth;
}
@@ -244,7 +250,7 @@ private Auth assumeRole(String role, String region, String agentLogStreamName) {
*/
private Auth getFederationToken(AWSSecurityTokenServiceClientBuilder builder, String region, String agentLogStreamName) {
Auth auth = new Auth(builder.build().getFederationToken(new GetFederationTokenRequest().
- withName("CloudWatchSender"). // TODO as above?
+ withName("CloudWatchSender-" + UUID.randomUUID()).
withPolicy(policy(agentLogStreamName))).
getCredentials(), region, agentLogStreamName);
LOGGER.log(Level.FINE, "GetFederationToken succeeded; using {0}", auth.accessKeyId);
@@ -272,7 +278,8 @@ void notifyShutdown(String agentLogStreamName) {
if (auth.restricted) {
return null;
} else if (auth.accessKeyId != null) {
- return "Giving up on limiting session credentials to a policy; using " + auth.accessKeyId + " as is";
+ return "Giving up on limiting session credentials to a policy; using " + auth.accessKeyId + " as is: " +
+ AWSSecurityTokenServiceClientBuilder.standard().withCredentials(CloudWatchAwsGlobalConfiguration.awsCredentialsProviderChain).build().getCallerIdentity(new GetCallerIdentityRequest());
} else {
return "No AWS credentials to be found, giving up on limiting to a policy";
}
diff --git a/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/PipelineBridgeTest.java b/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/PipelineBridgeTest.java
index ce95038..49ddd6a 100644
--- a/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/PipelineBridgeTest.java
+++ b/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/PipelineBridgeTest.java
@@ -24,6 +24,8 @@
package io.jenkins.plugins.pipeline_cloudwatch_logs;
+import com.amazonaws.auth.AWSCredentialsProviderChain;
+import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
@@ -59,8 +61,9 @@ static void globalConfiguration() throws Exception {
SystemCredentialsProvider.getInstance().getCredentials().add(new AWSCredentialsImpl(CredentialsScope.GLOBAL, credentialsId, null, null, null, role, null));
CredentialsAwsGlobalConfiguration.get().setCredentialsId(credentialsId);
}
+ CloudWatchAwsGlobalConfiguration.awsCredentialsProviderChain = new AWSCredentialsProviderChain(new V2ProfileCredentialsProvider(), DefaultAWSCredentialsProviderChain.getInstance());
CloudWatchAwsGlobalConfiguration configuration = ExtensionList.lookupSingleton(CloudWatchAwsGlobalConfiguration.class);
- FormValidation logGroupNameValidation = configuration.validate(logGroupName, null, credentialsId);
+ FormValidation logGroupNameValidation = configuration.validate(logGroupName, null, credentialsId, false);
assumeThat(logGroupNameValidation.toString(), logGroupNameValidation.kind, is(FormValidation.Kind.OK));
configuration.setLogGroupName(logGroupName);
}
diff --git a/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/V2ProfileCredentialsProvider.java b/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/V2ProfileCredentialsProvider.java
new file mode 100644
index 0000000..c1d62ac
--- /dev/null
+++ b/src/test/java/io/jenkins/plugins/pipeline_cloudwatch_logs/V2ProfileCredentialsProvider.java
@@ -0,0 +1,57 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2023 CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package io.jenkins.plugins.pipeline_cloudwatch_logs;
+
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.auth.BasicSessionCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+
+/**
+ * Allows use of {@code aws sso login} when running tests.
+ * TODO copied from {@code io.jenkins.plugins.artifact_manager_jclouds.s3};
+ * perhaps move to {@code com.cloudbees.jenkins.plugins.awscredentials}
+ */
+public class V2ProfileCredentialsProvider implements AWSCredentialsProvider {
+
+ private final ProfileCredentialsProvider delegate = ProfileCredentialsProvider.create();
+
+ @Override public AWSCredentials getCredentials() {
+ AwsCredentials credentials = delegate.resolveCredentials();
+ if (credentials instanceof AwsSessionCredentials) {
+ AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials;
+ return new BasicSessionCredentials(sessionCredentials.accessKeyId(), sessionCredentials.secretAccessKey(), sessionCredentials.sessionToken());
+ } else {
+ return new BasicAWSCredentials(credentials.accessKeyId(), credentials.secretAccessKey());
+ }
+ }
+
+ @Override public void refresh() {
+ assert false;
+ }
+}