Skip to content
Closed
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
34 changes: 25 additions & 9 deletions src/main/java/hudson/plugins/s3/ClientHelper.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package hudson.plugins.s3;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.RegionUtils;
import com.amazonaws.regions.Regions;
Expand All @@ -18,20 +22,32 @@ public class ClientHelper {
public final static String DEFAULT_AMAZON_S3_REGION_NAME = System.getProperty(
"hudson.plugins.s3.DEFAULT_AMAZON_S3_REGION",
com.amazonaws.services.s3.model.Region.US_Standard.toAWSRegion().getName());
/**
* An identifier for the assumed role session.
*/
public final static String DEFAULT_ROLE_SESSION_NAME = "jenkins-s3-plugin";

public static AmazonS3Client createClient(String accessKey, String secretKey, boolean useRole, String region, ProxyConfiguration proxy)
{
public static AmazonS3Client createClient(String accessKey, String secretKey, boolean useRole, String assumeRoleArn, String region, ProxyConfiguration proxy) {
Region awsRegion = getRegionFromString(region);

ClientConfiguration clientConfiguration = getClientConfiguration(proxy, awsRegion);

final AmazonS3Client client;
if (useRole) {
client = new AmazonS3Client(clientConfiguration);
final AWSCredentialsProvider credentials;
if (assumeRoleArn.isEmpty()) {
if (useRole) {
credentials = InstanceProfileCredentialsProvider.getInstance();
} else {
credentials = new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey));
}
} else {
client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey), clientConfiguration);
if (useRole) {
credentials = new STSAssumeRoleSessionCredentialsProvider(assumeRoleArn, DEFAULT_ROLE_SESSION_NAME);
} else {
credentials = new STSAssumeRoleSessionCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey), assumeRoleArn, DEFAULT_ROLE_SESSION_NAME);
}
}

final AmazonS3Client client = new AmazonS3Client(credentials, clientConfiguration);
client.setRegion(awsRegion);

return client;
Expand Down Expand Up @@ -89,13 +105,13 @@ public static ClientConfiguration getClientConfiguration(@Nonnull ProxyConfigura
}

private static boolean shouldUseProxy(ProxyConfiguration proxy, String hostname) {
if(proxy == null) {
if (proxy == null) {
return false;
}

boolean shouldProxy = true;
for(Pattern p : proxy.getNoProxyHostPatterns()) {
if(p.matcher(hostname).matches()) {
for (Pattern p : proxy.getNoProxyHostPatterns()) {
if (p.matcher(hostname).matches()) {
shouldProxy = false;
break;
}
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/hudson/plugins/s3/Entry.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.amazonaws.regions.Region;
import com.amazonaws.regions.RegionUtils;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import hudson.Extension;
import hudson.model.Describable;
import hudson.model.Descriptor;
Expand Down Expand Up @@ -43,7 +44,7 @@ public final class Entry implements Describable<Entry> {
* Stores the Region Value
*/
public String selectedRegion;

/**
* Do not publish the artifacts when build fails
*/
Expand All @@ -58,12 +59,17 @@ public final class Entry implements Describable<Entry> {
* Let Jenkins manage the S3 uploaded artifacts
*/
public boolean managedArtifacts;

/**
* Use S3 server side encryption when uploading the artifacts
*/
public boolean useServerSideEncryption;

/**
* Use this canned ACL when uploading
*/
public String cannedACL;

/**
* Flatten directories
*/
Expand Down Expand Up @@ -93,7 +99,7 @@ public final class Entry implements Describable<Entry> {
@DataBoundConstructor
public Entry(String bucket, String sourceFile, String excludedFile, String storageClass, String selectedRegion,
boolean noUploadOnFailure, boolean uploadFromSlave, boolean managedArtifacts,
boolean useServerSideEncryption, boolean flatten, boolean gzipFiles, boolean keepForever,
boolean useServerSideEncryption, String cannedACL, boolean flatten, boolean gzipFiles, boolean keepForever,
boolean showDirectlyInBrowser, List<MetadataPair> userMetadata) {
this.bucket = bucket;
this.sourceFile = sourceFile;
Expand All @@ -104,6 +110,7 @@ public Entry(String bucket, String sourceFile, String excludedFile, String stora
this.uploadFromSlave = uploadFromSlave;
this.managedArtifacts = managedArtifacts;
this.useServerSideEncryption = useServerSideEncryption;
this.cannedACL = cannedACL;
this.flatten = flatten;
this.gzipFiles = gzipFiles;
this.keepForever = keepForever;
Expand Down Expand Up @@ -134,6 +141,14 @@ public ListBoxModel doFillStorageClassItems() {
return model;
}

public ListBoxModel doFillCannedACLItems() {
final ListBoxModel model = new ListBoxModel();
for (CannedAccessControlList cacl : CannedAccessControlList.values()) {
model.add(cacl.toString(), cacl.name());
}
return model;
}

public ListBoxModel doFillSelectedRegionItems() {
final ListBoxModel model = new ListBoxModel();
for (Region r : regions) {
Expand Down
43 changes: 41 additions & 2 deletions src/main/java/hudson/plugins/s3/S3BucketPublisher.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

public final class S3BucketPublisher extends Recorder implements SimpleBuildStep {

Expand Down Expand Up @@ -262,7 +263,7 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath ws, @Nonnull Launc
final Map<String, String> escapedMetadata = buildMetadata(envVars, entry);

final List<FingerprintRecord> records = Lists.newArrayList();
final List<FingerprintRecord> fingerprints = profile.upload(run, bucket, paths, filenames, escapedMetadata, storageClass, selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption, entry.gzipFiles);
final List<FingerprintRecord> fingerprints = profile.upload(run, bucket, paths, filenames, escapedMetadata, storageClass, selRegion, entry.uploadFromSlave, entry.managedArtifacts, entry.useServerSideEncryption, entry.cannedACL, entry.gzipFiles);

for (FingerprintRecord fingerprintRecord : fingerprints) {
records.add(fingerprintRecord);
Expand Down Expand Up @@ -502,7 +503,45 @@ public FormValidation doLoginCheck(final StaplerRequest req, StaplerResponse rsp

final String defaultRegion = ClientHelper.DEFAULT_AMAZON_S3_REGION_NAME;
final AmazonS3Client client = ClientHelper.createClient(
accessKey, secretKey, useRole, defaultRegion, Jenkins.getActiveInstance().proxy);
accessKey, secretKey, useRole, "", defaultRegion, Jenkins.getActiveInstance().proxy);

try {
client.listBuckets();
} catch (AmazonClientException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
return FormValidation.error("Can't connect to S3 service: " + e.getMessage());
}
return FormValidation.ok("Check passed!");
}

@SuppressWarnings("unused")
public FormValidation doAssumeRoleArnCheck(final StaplerRequest req, StaplerResponse rsp) {
final String assumeRoleArn = Util.fixNull(req.getParameter("assumeRoleArn"));
if (assumeRoleArn.isEmpty()) {
return FormValidation.ok();
}
if (!Pattern.matches("(?i)arn:aws:iam::\\d+:role/.+", assumeRoleArn.toLowerCase())) {
return FormValidation.error("Does not look like a ARN");
}

final String useIAMCredential = Util.fixNull(req.getParameter("useRole"));
final boolean useRole = Boolean.parseBoolean(useIAMCredential);
if (useRole) {
// Won't be able to validate this because IAM role on master could be different on slave(s)
return FormValidation.ok();
}

final String accessKey = Util.fixNull(req.getParameter("accessKey"));
if (accessKey.isEmpty())
return FormValidation.ok("Please, enter accessKey");

final String secretKey = Util.fixNull(req.getParameter("secretKey"));
if (secretKey.isEmpty())
return FormValidation.ok("Please, enter secretKey");

final String defaultRegion = ClientHelper.DEFAULT_AMAZON_S3_REGION_NAME;
final AmazonS3Client client = ClientHelper.createClient(
accessKey, secretKey, useRole, assumeRoleArn, defaultRegion, Jenkins.getActiveInstance().proxy);

try {
client.listBuckets();
Expand Down
20 changes: 13 additions & 7 deletions src/main/java/hudson/plugins/s3/S3Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class S3Profile {
private final String name;
private final String accessKey;
private final Secret secretKey;
private final String assumeRoleArn;
private final int maxUploadRetries;
private final int uploadRetryTime;
private final int maxDownloadRetries;
Expand All @@ -40,9 +41,10 @@ public class S3Profile {
private final int signedUrlExpirySeconds;

@DataBoundConstructor
public S3Profile(String name, String accessKey, String secretKey, boolean useRole, int signedUrlExpirySeconds, String maxUploadRetries, String uploadRetryTime, String maxDownloadRetries, String downloadRetryTime, boolean keepStructure) {
public S3Profile(String name, String accessKey, String secretKey, boolean useRole, String assumeRoleArn, int signedUrlExpirySeconds, String maxUploadRetries, String uploadRetryTime, String maxDownloadRetries, String downloadRetryTime, boolean keepStructure) {
this.name = name;
this.useRole = useRole;
this.assumeRoleArn = assumeRoleArn;
this.maxUploadRetries = parseWithDefault(maxUploadRetries, 5);
this.uploadRetryTime = parseWithDefault(uploadRetryTime, 5);
this.maxDownloadRetries = parseWithDefault(maxDownloadRetries, 5);
Expand Down Expand Up @@ -83,6 +85,8 @@ public final String getAccessKey() {
return accessKey;
}

public final String getAssumeRoleArn() { return assumeRoleArn; }

public final Secret getSecretKey() {
return secretKey;
}
Expand Down Expand Up @@ -112,7 +116,7 @@ public int getSignedUrlExpirySeconds() {
}

public AmazonS3Client getClient(String region) {
return ClientHelper.createClient(accessKey, Secret.toString(secretKey), useRole, region, getProxy());
return ClientHelper.createClient(accessKey, Secret.toString(secretKey), useRole, assumeRoleArn, region, getProxy());
}

public List<FingerprintRecord> upload(Run<?, ?> run,
Expand All @@ -125,6 +129,7 @@ public List<FingerprintRecord> upload(Run<?, ?> run,
final boolean uploadFromSlave,
final boolean managedArtifacts,
final boolean useServerSideEncryption,
final String cannedACL,
final boolean gzipFiles) throws IOException, InterruptedException {
final List<FingerprintRecord> fingerprints = new ArrayList<>(fileNames.size());

Expand All @@ -145,11 +150,11 @@ public List<FingerprintRecord> upload(Run<?, ?> run,

final MasterSlaveCallable<String> upload;
if (gzipFiles) {
upload = new S3GzipCallable(accessKey, secretKey, useRole, dest, userMetadata,
storageClass, selregion, useServerSideEncryption, getProxy());
upload = new S3GzipCallable(accessKey, secretKey, useRole, assumeRoleArn, dest, userMetadata,
storageClass, selregion, useServerSideEncryption, cannedACL, getProxy());
} else {
upload = new S3UploadCallable(accessKey, secretKey, useRole, dest, userMetadata,
storageClass, selregion, useServerSideEncryption, getProxy());
upload = new S3UploadCallable(accessKey, secretKey, useRole, assumeRoleArn, dest, userMetadata,
storageClass, selregion, useServerSideEncryption, cannedACL, getProxy());
}

final FingerprintRecord fingerprintRecord = repeat(maxUploadRetries, uploadRetryTime, dest, new Callable<FingerprintRecord>() {
Expand Down Expand Up @@ -241,7 +246,7 @@ public List<FingerprintRecord> downloadAll(Run build,
fingerprints.add(repeat(maxDownloadRetries, downloadRetryTime, dest, new Callable<FingerprintRecord>() {
@Override
public FingerprintRecord call() throws IOException, InterruptedException {
final String md5 = target.act(new S3DownloadCallable(accessKey, secretKey, useRole, dest, artifact.getRegion(), getProxy()));
final String md5 = target.act(new S3DownloadCallable(accessKey, secretKey, useRole, assumeRoleArn, dest, artifact.getRegion(), getProxy()));
return new FingerprintRecord(true, dest.bucketName, target.getName(), artifact.getRegion(), md5);
}
}));
Expand Down Expand Up @@ -292,6 +297,7 @@ public String toString() {
", accessKey='" + accessKey + '\'' +
", secretKey=" + secretKey +
", useRole=" + useRole +
", assumeRoleArn=" + assumeRoleArn +
'}';
}

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/hudson/plugins/s3/Uploads.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hudson.plugins.s3;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.transfer.TransferManager;
Expand All @@ -21,9 +22,13 @@ private Uploads() {}
private final transient HashMap<FilePath, Upload> startedUploads = new HashMap<>();
private final transient HashMap<FilePath, InputStream> openedStreams = new HashMap<>();

public Upload startUploading(TransferManager manager, FilePath file, InputStream inputsStream, String bucketName, String objectName, ObjectMetadata metadata) throws AmazonServiceException {
public Upload startUploading(TransferManager manager, FilePath file, InputStream inputsStream, String bucketName, String objectName, ObjectMetadata metadata, CannedAccessControlList cannedAcl) throws AmazonServiceException {
final PutObjectRequest request = new PutObjectRequest(bucketName, objectName, inputsStream, metadata);

if (!cannedAcl.equals(CannedAccessControlList.Private)) {
request.setCannedAcl(cannedAcl);
}

// Set the buffer size (ReadLimit) equal to the multipart upload size,
// allowing us to resend data if the connection breaks.
request.getRequestClientOptions().setReadLimit(MULTIPART_UPLOAD_THRESHOLD);
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/hudson/plugins/s3/callable/S3BaseUploadCallable.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hudson.plugins.s3.callable;

import com.amazonaws.services.s3.internal.Mimetypes;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import hudson.FilePath;
import hudson.ProxyConfiguration;
Expand All @@ -21,16 +22,18 @@ public abstract class S3BaseUploadCallable extends S3Callable<String> {
private final String storageClass;
private final Map<String, String> userMetadata;
private final boolean useServerSideEncryption;
private final String cannedACLName;


public S3BaseUploadCallable(String accessKey, Secret secretKey, boolean useRole,
public S3BaseUploadCallable(String accessKey, Secret secretKey, boolean useRole, String assumeRoleArn,
Destination dest, Map<String, String> userMetadata, String storageClass, String selregion,
boolean useServerSideEncryption, ProxyConfiguration proxy) {
super(accessKey, secretKey, useRole, selregion, proxy);
boolean useServerSideEncryption, String cannedACLName, ProxyConfiguration proxy) {
super(accessKey, secretKey, useRole, assumeRoleArn, selregion, proxy);
this.dest = dest;
this.storageClass = storageClass;
this.userMetadata = userMetadata;
this.useServerSideEncryption = useServerSideEncryption;
this.cannedACLName = cannedACLName;
}

/**
Expand Down Expand Up @@ -89,4 +92,11 @@ protected ObjectMetadata buildMetadata(FilePath filePath) throws IOException, In
public Destination getDest() {
return dest;
}

public CannedAccessControlList getCannedAcl() {
if (cannedACLName == null) {
return CannedAccessControlList.Private;
}
return CannedAccessControlList.valueOf(cannedACLName);
}
}
8 changes: 5 additions & 3 deletions src/main/java/hudson/plugins/s3/callable/S3Callable.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@ abstract class S3Callable<T> implements FileCallable<T> {
private final String accessKey;
private final Secret secretKey;
private final boolean useRole;
private final String assumeRoleArn;
private final String region;
private final ProxyConfiguration proxy;

private static transient HashMap<String, TransferManager> transferManagers = new HashMap<>();

S3Callable(String accessKey, Secret secretKey, boolean useRole, String region, ProxyConfiguration proxy) {
S3Callable(String accessKey, Secret secretKey, boolean useRole, String assumeRoleArn, String region, ProxyConfiguration proxy) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.useRole = useRole;
this.assumeRoleArn = assumeRoleArn;
this.region = region;
this.proxy = proxy;
}

protected synchronized TransferManager getTransferManager() {
final String uniqueKey = getUniqueKey();
if (transferManagers.get(uniqueKey) == null) {
final AmazonS3 client = ClientHelper.createClient(accessKey, Secret.toString(secretKey), useRole, region, proxy);
final AmazonS3 client = ClientHelper.createClient(accessKey, Secret.toString(secretKey), useRole, assumeRoleArn, region, proxy);
transferManagers.put(uniqueKey, new TransferManager(client));
}

Expand All @@ -45,6 +47,6 @@ public void checkRoles(RoleChecker roleChecker) throws SecurityException {
}

private String getUniqueKey() {
return region + '_' + secretKey + '_' + accessKey + '_' + useRole;
return region + '_' + secretKey + '_' + accessKey + '_' + useRole + '_' + assumeRoleArn;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public final class S3DownloadCallable extends S3Callable<String>
private static final long serialVersionUID = 1L;
private final Destination dest;

public S3DownloadCallable(String accessKey, Secret secretKey, boolean useRole, Destination dest, String region, ProxyConfiguration proxy)
public S3DownloadCallable(String accessKey, Secret secretKey, boolean useRole, String assumeRoleArn, Destination dest, String region, ProxyConfiguration proxy)
{
super(accessKey, secretKey, useRole, region, proxy);
super(accessKey, secretKey, useRole, assumeRoleArn, region, proxy);
this.dest = dest;
}

Expand Down
Loading