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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<packaging>hpi</packaging>

<properties>
<changelist>999999-SNAPSHOT</changelist>
<changelist>999999-joom-refresh-creds-SNAPSHOT</changelist>
<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
<jenkins.baseline>2.504</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package io.jenkins.plugins.artifact_manager_jclouds.s3;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
Expand Down Expand Up @@ -54,14 +55,12 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials;
import com.google.common.base.Supplier;

import hudson.Extension;
import hudson.Util;
import io.jenkins.plugins.artifact_manager_jclouds.BlobStoreProvider;
import io.jenkins.plugins.artifact_manager_jclouds.BlobStoreProviderDescriptor;
import io.jenkins.plugins.aws.global_configuration.CredentialsAwsGlobalConfiguration;
import org.jenkinsci.Symbol;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
Expand Down Expand Up @@ -161,42 +160,30 @@ public BlobStoreContext getContext() throws IOException {
* @throws IOException in case of error.
*/
private Supplier<Credentials> getCredentialsSupplier() throws IOException {
// get user credentials from env vars, profiles,...
String accessKeyId;
String secretKey;
String sessionToken;
if (getConfiguration().getDisableSessionToken()) {
AmazonWebServicesCredentials amazonWebServicesCredentials = CredentialsAwsGlobalConfiguration.get().getCredentials();
if (amazonWebServicesCredentials == null) {
throw new IOException("No static AWS credentials found");
resolveJcloudsCredentials();
return () -> {
try {
return resolveJcloudsCredentials();
} catch (IOException x) {
throw new UncheckedIOException("Failed to resolve AWS credentials", x);
}
AwsCredentials awsCredentials = amazonWebServicesCredentials.resolveCredentials();
accessKeyId = awsCredentials.accessKeyId();
secretKey = awsCredentials.secretAccessKey();
sessionToken = "";
} else {
AwsSessionCredentials awsSessionCredentials = CredentialsAwsGlobalConfiguration.get()
.sessionCredentials(getRegion(), CredentialsAwsGlobalConfiguration.get().getCredentialsId());
if(awsSessionCredentials != null ) {
accessKeyId = awsSessionCredentials.accessKeyId();
secretKey = awsSessionCredentials.secretAccessKey();
sessionToken = awsSessionCredentials.sessionToken();
} else {
throw new IOException("No session AWS credentials found");
}
}
};
}

private SessionCredentials resolveJcloudsCredentials() throws IOException {
AwsCredentials awsCredentials = getConfiguration().resolveAwsCredentials(getConfiguration().getDisableSessionToken());
String sessionToken = "";
if (awsCredentials instanceof AwsSessionCredentials) {
sessionToken = ((AwsSessionCredentials) awsCredentials).sessionToken();
}
if (BREAK_CREDS) {
sessionToken = "<broken>";
}

SessionCredentials sessionCredentials = SessionCredentials.builder()
.accessKeyId(accessKeyId)
.secretAccessKey(secretKey)
return SessionCredentials.builder()
.accessKeyId(awsCredentials.accessKeyId())
.secretAccessKey(awsCredentials.secretAccessKey())
.sessionToken(sessionToken)
.build();

return () -> sessionCredentials;
}

@NonNull
Expand All @@ -212,11 +199,11 @@ public URI toURI(@NonNull String container, @NonNull String key) {
}
}

public S3Presigner getS3Presigner(S3Client s3Client) {
public S3Presigner getS3Presigner(S3Client s3Client) throws IOException {
String customEndpoint = getConfiguration().getResolvedCustomEndpoint();
S3Presigner.Builder presignerBuilder = S3Presigner.builder()
.fipsEnabled(FIPS140.useCompliantAlgorithms())
.credentialsProvider(CredentialsAwsGlobalConfiguration.get().getCredentials())
.credentialsProvider(getConfiguration().getAwsCredentialsProvider(getConfiguration().getDisableSessionToken()))
.s3Client(s3Client);
if (StringUtils.isNotBlank(customEndpoint)) {
presignerBuilder.endpointOverride(URI.create(customEndpoint));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@
package io.jenkins.plugins.artifact_manager_jclouds.s3;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials;
import jenkins.util.SystemProperties;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundSetter;
Expand All @@ -55,8 +62,11 @@
import jenkins.model.Jenkins;
import jenkins.security.FIPS140;
import org.jenkinsci.Symbol;
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.auth.credentials.WebIdentityTokenFileCredentialsProvider;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.regions.Region;
Expand Down Expand Up @@ -347,19 +357,137 @@ public Region getRegion() {
return Region.of(regionStr);
}

private S3ClientBuilder getAmazonS3ClientBuilderWithCredentials(boolean disableSessionToken) throws IOException, URISyntaxException {
S3ClientBuilder builder = getAmazonS3ClientBuilder();
AwsCredentialsProvider getAwsCredentialsProvider(boolean disableSessionToken) throws IOException {
resolveAwsCredentials(disableSessionToken);
return () -> {
try {
return resolveAwsCredentials(disableSessionToken);
} catch (IOException x) {
throw new UncheckedIOException("Failed to resolve AWS credentials", x);
}
};
}

@VisibleForTesting
AwsCredentials resolveAwsCredentials(boolean disableSessionToken) throws IOException {
if (disableSessionToken) {
builder = builder.credentialsProvider(CredentialsAwsGlobalConfiguration.get().getCredentials());
} else {
AwsSessionCredentials awsSessionCredentials = CredentialsAwsGlobalConfiguration.get()
.sessionCredentials(getRegion().id(), CredentialsAwsGlobalConfiguration.get().getCredentialsId());
if(awsSessionCredentials != null ) {
builder.credentialsProvider(StaticCredentialsProvider.create(awsSessionCredentials));
} else {
throw new IOException("No session AWS credentials found");
return resolveStaticAwsCredentials();
}
return resolveSessionCredentials(getRegion().id());
}

private AwsCredentials resolveStaticAwsCredentials() throws IOException {
AwsCredentials sharedCredentials = resolveSharedFileCredentials(false);
if (sharedCredentials != null) {
return AwsBasicCredentials.create(sharedCredentials.accessKeyId(), sharedCredentials.secretAccessKey());
}
AmazonWebServicesCredentials amazonWebServicesCredentials = CredentialsAwsGlobalConfiguration.get().getCredentials();
if (amazonWebServicesCredentials == null) {
throw new IOException("No static AWS credentials found");
}
AwsCredentials awsCredentials = amazonWebServicesCredentials.resolveCredentials();
return AwsBasicCredentials.create(awsCredentials.accessKeyId(), awsCredentials.secretAccessKey());
}

@VisibleForTesting
AwsSessionCredentials resolveSessionCredentials(String region) throws IOException {
if (Util.fixEmpty(CredentialsAwsGlobalConfiguration.get().getCredentialsId()) == null) {
AwsSessionCredentials sharedCredentials = resolveSharedFileSessionCredentials();
if (sharedCredentials != null) {
return sharedCredentials;
}
AwsSessionCredentials webIdentityCredentials = resolveWebIdentitySessionCredentials();
if (webIdentityCredentials != null) {
return webIdentityCredentials;
}
}
AwsSessionCredentials awsSessionCredentials = CredentialsAwsGlobalConfiguration.get()
.sessionCredentials(region, CredentialsAwsGlobalConfiguration.get().getCredentialsId());
if (awsSessionCredentials == null) {
throw new IOException("No session AWS credentials found");
}
return awsSessionCredentials;
}

private AwsSessionCredentials resolveSharedFileSessionCredentials() throws IOException {
AwsCredentials awsCredentials = resolveSharedFileCredentials(true);
return awsCredentials instanceof AwsSessionCredentials ? (AwsSessionCredentials) awsCredentials : null;
}

private AwsSessionCredentials resolveWebIdentitySessionCredentials() {
if (Util.fixEmptyAndTrim(System.getenv("AWS_WEB_IDENTITY_TOKEN_FILE")) == null
|| Util.fixEmptyAndTrim(System.getenv("AWS_ROLE_ARN")) == null) {
return null;
}
AwsCredentials awsCredentials = WebIdentityTokenFileCredentialsProvider.create().resolveCredentials();
return awsCredentials instanceof AwsSessionCredentials ? (AwsSessionCredentials) awsCredentials : null;
}

private AwsCredentials resolveSharedFileCredentials(boolean requireSessionToken) throws IOException {
String sharedCredentialsFile = Util.fixEmptyAndTrim(System.getenv("AWS_SHARED_CREDENTIALS_FILE"));
if (sharedCredentialsFile == null) {
return null;
}
return loadSharedFileCredentials(Paths.get(sharedCredentialsFile), getAwsProfileName(), requireSessionToken);
}

@VisibleForTesting
static AwsCredentials loadSharedFileCredentials(Path path, String profileName, boolean requireSessionToken) throws IOException {
Map<String, String> profileEntries = loadSharedFileProfile(path, profileName);
String accessKeyId = Util.fixEmptyAndTrim(profileEntries.get("aws_access_key_id"));
String secretAccessKey = Util.fixEmptyAndTrim(profileEntries.get("aws_secret_access_key"));
String sessionToken = Util.fixEmptyAndTrim(profileEntries.get("aws_session_token"));
if (sessionToken == null) {
sessionToken = Util.fixEmptyAndTrim(profileEntries.get("aws_security_token"));
}
if (accessKeyId == null || secretAccessKey == null) {
return null;
}
if (requireSessionToken) {
return sessionToken == null ? null : AwsSessionCredentials.create(accessKeyId, secretAccessKey, sessionToken);
}
return AwsBasicCredentials.create(accessKeyId, secretAccessKey);
}

@VisibleForTesting
static Map<String, String> loadSharedFileProfile(Path path, String profileName) throws IOException {
Map<String, String> profileEntries = new LinkedHashMap<>();
if (!Files.isRegularFile(path)) {
return profileEntries;
}
String currentProfile = null;
for (String rawLine : Files.readAllLines(path)) {
String line = rawLine.trim();
if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) {
continue;
}
if (line.startsWith("[") && line.endsWith("]")) {
currentProfile = line.substring(1, line.length() - 1).trim();
continue;
}
if (!profileName.equals(currentProfile)) {
continue;
}
int separator = line.indexOf('=');
if (separator < 0) {
continue;
}
profileEntries.put(line.substring(0, separator).trim(), line.substring(separator + 1).trim());
}
return profileEntries;
}

private String getAwsProfileName() {
String profileName = Util.fixEmptyAndTrim(System.getenv("AWS_PROFILE"));
if (profileName == null) {
profileName = Util.fixEmptyAndTrim(System.getenv("AWS_DEFAULT_PROFILE"));
}
return profileName != null ? profileName : "default";
}

private S3ClientBuilder getAmazonS3ClientBuilderWithCredentials(boolean disableSessionToken) throws IOException, URISyntaxException {
S3ClientBuilder builder = getAmazonS3ClientBuilder();
builder = builder.credentialsProvider(getAwsCredentialsProvider(disableSessionToken));
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package io.jenkins.plugins.artifact_manager_jclouds.s3;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Logger;

import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import io.jenkins.plugins.artifact_manager_jclouds.BlobStoreProvider;
import io.jenkins.plugins.artifact_manager_jclouds.JCloudsArtifactManagerFactory;
Expand All @@ -13,11 +16,15 @@
import hudson.util.FormValidation;
import jenkins.model.ArtifactManagerConfiguration;

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.regions.Region;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand All @@ -37,6 +44,9 @@ public class S3BlobStoreConfigTest {
@Rule
public JenkinsRule j = new JenkinsRule();

@Rule
public TemporaryFolder tmp = new TemporaryFolder();

@Test
public void checkConfigurationManually() throws Exception {
S3BlobStore provider = new S3BlobStore();
Expand Down Expand Up @@ -170,4 +180,34 @@ public void checkValidationUseHttpsWithFipsDisabled() {
assertEquals(descriptor.doCheckUseHttp(true).kind , FormValidation.Kind.OK);
assertEquals(descriptor.doCheckUseHttp(false).kind , FormValidation.Kind.OK);
}

@Test
public void loadSharedFileCredentialsReadsLatestSessionToken() throws Exception {
Path credentialsFile = tmp.newFile("aws-credentials").toPath();

Files.writeString(credentialsFile, "[default]\naws_access_key_id=first-key\naws_secret_access_key=first-secret\naws_session_token=first-token\n");
AwsCredentials initial = S3BlobStoreConfig.loadSharedFileCredentials(credentialsFile, "default", true);
assertEquals("first-key", initial.accessKeyId());
assertEquals("first-secret", initial.secretAccessKey());
assertEquals("first-token", ((AwsSessionCredentials) initial).sessionToken());

Files.writeString(credentialsFile, "[default]\naws_access_key_id=second-key\naws_secret_access_key=second-secret\naws_session_token=second-token\n");
AwsCredentials rotated = S3BlobStoreConfig.loadSharedFileCredentials(credentialsFile, "default", true);
assertEquals("second-key", rotated.accessKeyId());
assertEquals("second-secret", rotated.secretAccessKey());
assertEquals("second-token", ((AwsSessionCredentials) rotated).sessionToken());
}

@Test
public void loadSharedFileCredentialsReturnsBasicCredentialsWithoutToken() throws Exception {
Path credentialsFile = tmp.newFile("aws-basic-credentials").toPath();

Files.writeString(credentialsFile, "[default]\naws_access_key_id=static-key\naws_secret_access_key=static-secret\n");

AwsCredentials staticCredentials = S3BlobStoreConfig.loadSharedFileCredentials(credentialsFile, "default", false);
assertThat(staticCredentials, instanceOf(AwsBasicCredentials.class));
assertEquals("static-key", staticCredentials.accessKeyId());
assertEquals("static-secret", staticCredentials.secretAccessKey());
assertNull(S3BlobStoreConfig.loadSharedFileCredentials(credentialsFile, "default", true));
}
}