I have created a customer key for AWS S3 Server side encryption using customer key (SSE-C).
I am able to upload the object using the key. But when I generate a presigned URL using AWS Java SDK the URL is getting created successfully but when I hit that URL I am getting the below error.
SignatureDoesNotMatch
The request signature we calculated does not match the signature you provided. Check your key and signing method.
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.SSEAlgorithm;
import com.amazonaws.services.s3.model.SSECustomerKey;
import com.amazonaws.util.Base64;
import com.amazonaws.util.Md5Utils;
public class GeneratePresignedURL {
public GeneratePresignedURL() throws IOException {
String bucketName = "abctest";
String keyName = "testnew.mp4";
try {
SSECustomerKey SSE_KEY = new SSECustomerKey("KLgsVafKowMCfKDsbIh597CmMUSoPBn6QJ8OIGxAMBw=");
ClientConfiguration cnf = new ClientConfiguration();
cnf.withSignerOverride("AWSS3V4SignerType");
AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withClientConfiguration(cnf)
.withCredentials(new ProfileCredentialsProvider()).build();
// Set the presigned URL to expire after one hour.
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 60;
expiration.setTime(expTimeMillis);
// Generate the presigned URL.
System.out.println("Generating pre-signed URL.");
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName,
keyName).withMethod(HttpMethod.PUT).withExpiration(expiration).withSSECustomerKey(SSE_KEY);
generatePresignedUrlRequest.setSSECustomerKeyAlgorithm(SSEAlgorithm.AES256);
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toExternalForm());
} catch (AmazonServiceException e) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
e.printStackTrace();
} catch (SdkClientException e) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
e.printStackTrace();
}
}
I have tried to follow the code from https://aws.amazon.com/blogs/developer/generating-amazon-s3-pre-signed-urls-with-sse-c-part-5-finale/
I have no problem in using pre-signed url for SSE S3, I am only facing issue with SSE-C
I have tried setting default encryption and other config but of no help. Any pointers would be of great help.
Thanks,
AK
You can not call pre-signed URL direct via browser
You need to pass headers in the request
x-amz-server-side-encryption-customer-algorithm
x-amz-server-side-encryption-customer-key
x-amz-server-side-encryption-customer-key-MD5
Please check the document for more information
https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
When using the presigned URL to upload a new object, retrieve an existing object, or retrieve only object metadata, you must provide all the encryption headers in your client application.
Please check the sample code calling signed URL via Unirest library
https://github.com/pavanpawar4591/s3signurlwith-sse-c
public static void getPreSignedURL() throws URISyntaxException {
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 60;
expiration.setTime(expTimeMillis);
System.out.println("Generating pre-signed URL.");
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, keyName)
.withMethod(HttpMethod.GET).withExpiration(expiration).withSSECustomerKey(SSE_KEY);
// generatePresignedUrlRequest.setContentType("video/mp4");
generatePresignedUrlRequest.setSSECustomerKeyAlgorithm(SSEAlgorithm.AES256);
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toURI() + " With key: " + SSE_KEY);
System.out.println("------------------------------");
//https://aws.amazon.com/blogs/developer/generating-amazon-s3-pre-signed-urls-with-sse-c-part-4/
// refer to above doc
try {
HttpResponse<String> response = Unirest.get(url.toURI().toString())
.header(Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM, SSEAlgorithm.AES256.getAlgorithm())
.header(Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY, Base64.encodeAsString(SECRET_KEY.getEncoded()))
.header(Headers.SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5,
Md5Utils.md5AsBase64(SECRET_KEY.getEncoded()))
.header("cache-control", "no-cache").header("postman-token", "d3453c38-1b59-a12e-fd97-dbe2150eadf5")
.asString();
System.out.println(response.getStatus());
System.out.println(response.getStatusText());
System.out.println(response.getBody());
} catch (UnirestException e) {
e.printStackTrace();
}
}
Thanks #pavan
Any client that needs to use SSE-C, the client must be capable of sending the Below headers.
When using the presigned URL to retrieve an existing object, or retrieve only object metadata, we need to provide all the encryption headers in your client application.
For S3 managed or KMS managed server side encryption, we can generate a presigned URL and directly paste that into a browser or player.
However, this is not true for SSE-C objects because in addition to
the presigned URL, you also need to include HTTP headers that are
specific to SSE-C objects. Therefore, you can use the presigned URL
for SSE-C objects only programmatically.
Upload:
I opted for S3 managed keys instead of customer provided client key.
FileInputStream fin = new FileInputStream(uploadFileName);
byte fileContent[] = new byte[(int) uploadFileName.length()];
// Reads up to certain bytes of data from this input stream into an array of
// bytes.
fin.read(fileContent);
// create string from byte array
// Specify server-side encryption.
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(fileContent.length);
objectMetadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
PutObjectRequest putRequest = new PutObjectRequest(bucketName, keyName,
new ByteArrayInputStream(fileContent), objectMetadata);
// Upload the object and check its encryption status.
PutObjectResult putResult = s3Client.putObject(putRequest);
System.out.println("Object \"" + keyName + "\" uploaded with SSE.");
GET Presigned URL:
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 60;
expiration.setTime(expTimeMillis);
System.out.println("Generating pre-signed URL.");
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, keyName)
.withMethod(HttpMethod.GET).withExpiration(expiration);
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toURI());
Reference:
https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
Related
I need some help for uploading large files into s3 bucket from salesforce apex server side.
I need to be able to split a blob and upload it to aws s3 bucket using Http PUT operation. I am able to do that upto 12 MB file in a single upload because that is the PUT request body size limit in Apex .
So i need to be able to upload using multipart operation. I noticed s3 allows to upload in parts and gives back a uploadId. wondering if anyone has already done this before in salesforce apex code. it would be greatly appreciated.
Thanks in advance
Parbati Bose.
Here is the code
public with sharing class AWSS3Service {
private static Http http;
#auraEnabled
public static void uploadToAWSS3( String fileToUpload , String filenm , String doctype){
fileToUpload = EncodingUtil.urlDecode(fileToUpload, 'UTF-8');
filenm = EncodingUtil.urlEncode(filenm , 'UTF-8'); // encode the filename in case there are special characters in the name
String filename = 'Storage' + '/' + filenm ;
String attachmentBody = fileToUpload;
String formattedDateString = DateTime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
// s3 bucket!
String key = '**********' ;
String secret = '********' ;
String bucketname = 'testbucket' ;
String region = 's3-us-west-2' ;
String host = region + '.' + 'amazonaws.com' ; //aws server base url
try{
HttpRequest req = new HttpRequest();
http = new Http() ;
req.setMethod('PUT');
req.setEndpoint('https://' + bucketname + '.' + host + '/' + filename );
req.setHeader('Host', bucketname + '.' + host);
req.setHeader('Content-Encoding', 'UTF-8');
req.setHeader('Content-Type' , doctype);
req.setHeader('Connection', 'keep-alive');
req.setHeader('Date', formattedDateString);
req.setHeader('ACL', 'public-read-write');
String stringToSign = 'PUT\n\n' +
doctype + '\n' +
formattedDateString + '\n' +
'/' + bucketname + '/' + filename;
Blob mac = Crypto.generateMac('HMACSHA1', blob.valueof(stringToSign),blob.valueof(secret));
String signed = EncodingUtil.base64Encode(mac);
String authHeader = 'AWS' + ' ' + key + ':' + signed;
req.setHeader('Authorization',authHeader);
req.setBodyAsBlob(EncodingUtil.base64Decode(fileToUpload)) ;
HttpResponse response = http.send(req);
Log.debug('response from aws s3 is ' + response.getStatusCode() + ' and ' + response.getBody());
}catch(Exception e){
Log.debug('error in connecting to s3 ' + e.getMessage());
throw e ;
}
}
I was looking at this same issue in the last couple of days, unfortunately you are going to be better performing this transfer from outside of Salesforce due to the APEX Heap size limit of 12MB.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
Whilst it would be possible to write files using multipart, it seems you cannot get them out of the database for splitting them into chunks you can send. A similar question was asked on the stackexchange -
https://salesforce.stackexchange.com/questions/264015/how-to-retrieve-file-content-from-content-document-in-chunks-using-soql
The AWS SDK for Java exposes a high-level API, called TransferManager,
that simplifies multipart uploads (see Uploading Objects Using
Multipart Upload API). You can upload data from a file or a stream.
You can also set advanced options, such as the part size you want to
use for the multipart upload, or the number of concurrent threads you
want to use when uploading the parts. You can also set optional object
properties, the storage class, or the ACL. You use the
PutObjectRequest and the TransferManagerConfiguration classes to set
these advanced options.
Here is the sample code from https://docs.aws.amazon.com/AmazonS3/latest/dev/HLuploadFileJava.html.
You can adapt to your Salesforce Apex code:
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;
import com.amazonaws.services.s3.transfer.Upload;
import java.io.File;
public class HighLevelMultipartUpload {
public static void main(String[] args) throws Exception {
Regions clientRegion = Regions.DEFAULT_REGION;
String bucketName = "*** Bucket name ***";
String keyName = "*** Object key ***";
String filePath = "*** Path for file to upload ***";
try {
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(new ProfileCredentialsProvider())
.build();
TransferManager tm = TransferManagerBuilder.standard()
.withS3Client(s3Client)
.build();
// TransferManager processes all transfers asynchronously,
// so this call returns immediately.
Upload upload = tm.upload(bucketName, keyName, new File(filePath));
System.out.println("Object upload started");
// Optionally, wait for the upload to finish before continuing.
upload.waitForCompletion();
System.out.println("Object upload complete");
} catch (AmazonServiceException e) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
e.printStackTrace();
} catch (SdkClientException e) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
e.printStackTrace();
}
}
}
I am trying to secure s3 resources and i have created temporary credential using sts service in the same region where bucket exist . I am using these credential to create a pre signed url which will expire after one hour. These url are images and will be shown on a mobile app . But after implementation, we are experiencing slowness in the response from s3 . Does anyone experienced same behaviour ?
This is how i am generating pre signed url
try {
AmazonS3 s3Client = new AmazonS3Client(getTemporaryAwsCredentials());
// Generate the pre signed URL.
LOGGER.info("Generating pre-signed URL for " + unsignedUrl);
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, fileName).withMethod(HttpMethod.GET);
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
LOGGER.info("Pre-Signed URL: " + url.toString());
return url.toString();
} catch (Exception e) {
LOGGER.error("Error while generating pre signed url ", e);
}
private AWSCredentials getTemporaryAwsCredentials() {
AWSSecurityTokenServiceClient sts_client = new AWSSecurityTokenServiceClient(getCredentials());
sts_client.setEndpoint("sts.us-west-2.amazonaws.com");
GetSessionTokenRequest session_token_request = new GetSessionTokenRequest();
session_token_request.setDurationSeconds(43200);
GetSessionTokenResult session_token_result = sts_client.getSessionToken(session_token_request);
Credentials session_creds = session_token_result.getCredentials();
BasicSessionCredentials sessionCredentials = new BasicSessionCredentials(
session_creds.getAccessKeyId(),
session_creds.getSecretAccessKey(),
session_creds.getSessionToken());
return sessionCredentials;
}
I was following the example from https://docs.aws.amazon.com/AmazonS3/latest/dev/ShareObjectPreSignedURLJavaSDK.html to create pre-signed s3 urls (v4) and I get Access Denied error when I try to access the signed the url
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>0FB02ECDDF5EAC7B</RequestId>
<HostId>vA+mmsv9PCunNe5uPkPrmpqqN3vFctQ13c9dIRlKWTYsT0zNA1V9g+4YS+lCItrBlyQtdHpyspg=</HostId>
</Error>
The following is the code snippet
public class GeneratePresignedURL {
public static void main(String[] args) throws IOException {
String clientRegion = "us-east-1";
String bucketName = "com-example-bucket";
String objectKey = "path/to/file.img"; // No leading `/`
// https://com-example-bucket.s3.amazonaws.com/path/to/file.img
try {
AWSCredentialsProvider awsCredentialsProvider = new DefaultAWSCredentialsProviderChain();
// Assuming that us-east-1 defaults to v4, couldn't find a way to set it explicitly
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(awsCredentialsProvider)
.build();
// Set the presigned URL to expire after 10 minutes.
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 10;
expiration.setTime(expTimeMillis);
// Generate the presigned URL.
System.out.println("Generating pre-signed URL.");
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucketName, objectKey)
.withMethod(HttpMethod.GET)
.withExpiration(expiration);
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toString());
/*
sample signature:
https://com-example-bucket.s3.amazonaws.com/path/to/file.img?X-Amz-Security-Token=FQoDYXdzEDcaDLjUOdj2hDTZvWUQaiK3AxulqM%2BOPlp%2Bnq71P0LyuI0vj8tT%2F9i24Wd3jY8dUbudWbhUH9IAsPnl7asujO90GlaFP4dXujDDLwIakMjCJSfOFM4IoGJz8XtcjXkqJCNaenbrTA%2F3PfSl%2Fe9wQwJlY8gOu8%2Byioq2ElHULMKv52nEZj8s3v4dD0pGHQTYc4hGV7ty9CYwXNgz6w3TREhxuFdAewNgTRnY1uFNy7on6NDF5IE15vlJ2PxqrX53ZMLKP%2FdU8i5BcpZ3ySVhNpBpU3GJAPMOh%2B2ztCAk1zPjW4G0N5n9BlnjTMGs3vGBb9IW%2F8dzAoxaG9U9%2B%2FCp8euJN562dYYSZ9wmQgsfOVqc5OksdnHVkPJW400ObOcKmc9mqIRyqA%2B3Mv4z0%2Fx6iLYRJ3UaloFSGbmR6VlIxMl%2F67aHrmCnBE23a1%2BNMWgzLx%2FogqZy3CD%2F%2Fs6Jt1qkxUrRwC0RPK93LHD74qm8rjqZcEKFrBOrZsYtcl3zKgRIEHCbatQ7dwT634sdF0MwaD0vwTsbsStZDW903k5C%2FDuz4rEmkPv6c5CmFvxp4xOkUtMbDk4B8Z641CoeAMMOKICH%2FlW7%2F1as3nQo07Ow2QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20180621T214222Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3000&X-Amz-Credential=<access_key>%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=<signature>
*/
}
catch(AmazonServiceException e) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
e.printStackTrace();
}
catch(SdkClientException e) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
e.printStackTrace();
}
}
}
The signature format seems to be correct and I am not sure if I am missing some other s3 client config.
Since I don't get a signature mismatch error or invalid url error, I assume that the Access Key used to sign the url is correct.
Here are steps to generate aws-s3 pre-signed url to access the content stored in s3 through java can create with simple step
First add maven dependency in your pom
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.870</version>
</dependency>
Get S3 credential accessKey, secretKey, region of s3 storage it must needed
Write java class
First get s3Client connection using below code snippet providing your own credential
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
final AmazonS3 s3Client = AmazonS3ClientBuilder.standard().withCredentials(new
AWSStaticCredentialsProvider(awsCreds)).withRegion(region).build();
You must have your bucketName if you don't have create bucket and store your content
Use below code snippet to generate pre-signed url
// Set the expiry time
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 60;
expiration.setTime(expTimeMillis);
Pass your objectKey is noting but your existed s3 base filename
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucketName, objecKey)
.withMethod(HttpMethod.GET)
.withExpiration(expiration);
URL url = s3client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toString());
It is due to the region mismatch. You have set clientRegion to be us-east-1 but if you are accessing it from any other region it will give access denied error. You can only access the object from us-east-1 region or just change it to your region. It's ap-south-1 for Indian clients.
The issue seems to be that the role didn't have permissions to access the s3 bucket path/to/file
Try to add .withPathStyleAccessEnabled(true) as mentioned in below snap.
// Assuming that us-east-1 defaults to v4, couldn't find a way to set it explicitly
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withPathStyleAccessEnabled(true)
.withCredentials(awsCredentialsProvider)
.build();
Change/check this change CROS properties of AWS S3 bucket.
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
Be mindful of assigning the cognito auth-role with access to S3 else the permission will be denied in case you are using the cognito-credentials user pool session to provide the required access to s3client. This was an issue which i was facing as well.
import com.amazonaws.AmazonServiceException;
import com.amazonaws.HttpMethod;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.11.163</version>
</dependency>
String GeneratePresignedUrlAndUploadObject() {
String accesskey = "XXXXXXXXXXXXXXXXXXXXXX";
String secretkey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
String bucketName = "your-bucket-Name";
Regions regions = Regions.US_EAST_1;
String objecKey = "your-file-name-you-need-url";
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accesskey, secretkey);
//
final AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds)).withRegion(regions).build();
// Set the expiry time
java.util.Date expiration = new java.util.Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 60;
expiration.setTime(expTimeMillis);
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objecKey)
.withMethod(HttpMethod.GET).withExpiration(expiration);
//
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
System.out.println("Pre-Signed URL: " + url.toString());
return url.toString();
}
I am trying to use Amazon SES to send email to my corporate account. I have copied by aws access keys to ~/.aws/credentials file. Seems i am consistently getting the error:
Error message: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'POST
/
amz-sdk-invocation-id:638ed10a-22cd-12f5-c9a5-25a672a6c38d
amz-sdk-retry:3/136/485
host:email.us-east-1.amazonaws.com
user-agent:aws-sdk-java/1.11.172 Mac_OS_X/10.11.5 Java_HotSpot(TM)_64-Bit_Server_VM/25.60-b23/1.8.0_60
x-amz-date:20170808T040309Z
amz-sdk-invocation-id;amz-sdk-retry;host;user-agent;x-amz-date
1c21e3af09924eec311e370c8e6710c0bc3fa2027fe213db11f02b22d79b6c91'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20170808T040309Z
20170808/us-east-1/ses/aws4_request
82a2179c0dda36b0a4f80c111a8119cc8a65d768a0ff65de4c9b7ccf69a6b68a' (Service: AmazonSimpleEmailService; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: 7dbfbcaa-7bee-11e7-a445-27be327c79a0)
com.amazonaws.services.simpleemail.model.AmazonSimpleEmailServiceException: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'POST
/
amz-sdk-invocation-id:638ed10a-22cd-12f5-c9a5-25a672a6c38d
amz-sdk-retry:3/136/485
host:email.us-east-1.amazonaws.com
user-agent:aws-sdk-java/1.11.172 Mac_OS_X/10.11.5 Java_HotSpot(TM)_64-Bit_Server_VM/25.60-b23/1.8.0_60
x-amz-date:20170808T040309Z
amz-sdk-invocation-id;amz-sdk-retry;host;user-agent;x-amz-date
1c21e3af09924eec311e370c8e6710c0bc3fa2027fe213db11f02b22d79b6c91'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20170808T040309Z
20170808/us-east-1/ses/aws4_request
82a2179c0dda36b0a4f80c111a8119cc8a65d768a0ff65de4c9b7ccf69a6b68a' (Service: AmazonSimpleEmailService; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: 7dbfbcaa-7bee-11e7-a445-27be327c79a0)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1587)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1257)
Code base:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.Properties;
import java.util.UUID;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.RawMessage;
import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;
public class SESClient {
private static String EMAIL_FROM = "##########";
private static String EMAIL_REPLY_TO = "##########";
private static String EMAIL_RECIPIENT = "##########";
// Remember to use two slashes in place of each slash.
private static Regions AWS_REGION = Regions.US_EAST_1;
private static String EMAIL_SUBJECT = "Amazon SES email test";
private static String EMAIL_BODY_TEXT = "This MIME email was sent through Amazon SES using SendRawEmail.";
public static void main(String[] args) throws AddressException, MessagingException, IOException {
Session session = Session.getDefaultInstance(new Properties());
MimeMessage message = new MimeMessage(session);
message.setSubject(EMAIL_SUBJECT, "UTF-8");
message.setFrom(new InternetAddress(EMAIL_FROM));
message.setReplyTo(new Address[]{new InternetAddress(EMAIL_REPLY_TO)});
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(EMAIL_RECIPIENT));
// Cover wrap
MimeBodyPart wrap = new MimeBodyPart();
// Alternative TEXT/HTML content
MimeMultipart cover = new MimeMultipart("alternative");
MimeBodyPart html = new MimeBodyPart();
cover.addBodyPart(html);
wrap.setContent(cover);
MimeMultipart content = new MimeMultipart("related");
message.setContent(content);
content.addBodyPart(wrap);
String[] attachmentsFiles = new String[]{
//EMAIL_ATTACHMENTS
};
StringBuilder sb = new StringBuilder();
for (String attachmentFileName : attachmentsFiles) {
String id = UUID.randomUUID().toString();
sb.append("<img src=\"cid:");
sb.append(id);
sb.append("\" alt=\"ATTACHMENT\"/>\n");
MimeBodyPart attachment = new MimeBodyPart();
DataSource fds = new FileDataSource(attachmentFileName);
attachment.setDataHandler(new DataHandler(fds));
attachment.setHeader("Content-ID", "<" + id + ">");
attachment.setFileName(fds.getName());
content.addBodyPart(attachment);
}
html.setContent("<html><body><h1>HTML</h1>\n" + EMAIL_BODY_TEXT + "</body></html>", "text/html");
try {
System.out.println("Attempting to send an email through Amazon SES by using the AWS SDK for Java...");
/*
* The ProfileCredentialsProvider will return your [default]
* credential profile by reading from the credentials file
* located at
* (~/.aws/credentials).
*
* TransferManager manages a pool of threads, so we create a
* single instance and share it throughout our application.
*/
AWSCredentials credentials = null;
try {
credentials = new ProfileCredentialsProvider().getCredentials();
} catch (Exception e) {
throw new AmazonClientException(
"Cannot load the credentials from the credential profiles file. " +
"Please make sure that your credentials file is at the correct " +
"location (~/.aws/credentials), and is in valid format.",
e);
}
AmazonSimpleEmailServiceClient client = new AmazonSimpleEmailServiceClient(credentials);
Region REGION = Region.getRegion(AWS_REGION);
client.setRegion(REGION);
// Print the raw email content on the console
PrintStream out = System.out;
message.writeTo(out);
// Send the email.
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
message.writeTo(outputStream);
RawMessage rawMessage = new RawMessage(ByteBuffer.wrap(outputStream.toByteArray()));
SendRawEmailRequest rawEmailRequest = new SendRawEmailRequest(rawMessage);
client.sendRawEmail(rawEmailRequest);
System.out.println("Email sent!");
} catch (Exception ex) {
System.out.println("Email Failed");
System.err.println("Error message: " + ex.getMessage());
ex.printStackTrace();
}
}
}
I am trying to upload a file to Amazon S3 bucket using Groovy script. I tried the following code
#Grab( 'net.java.dev.jets3t:jets3t:0.9.0' )
import org.jets3t.service.impl.rest.httpclient.RestS3Service
import org.jets3t.service.security.AWSCredentials
import org.jets3t.service.model.*
import java.io.*;
bucketName='bucketname'
accessKey='accesskey'
secretKey='secretkey'
folder='D:/'
public putS3() {}
def login = new AWSCredentials( accessKey, secretKey )
def expiry = new GregorianCalendar( 2011,0,1 ).time
def s3 = new RestS3Service( login )
def bucket = new S3Bucket( bucketName )
args.each{fileName->
def key="$folder/$fileName"
def s3obj=new S3Object(bucket,newFile('D:/sample.txt'))
s3obj.key = key
println "\nUploading $fileName to $bucketName/$key"
s3obj = s3.putObject( bucket, s3obj )
def link = s3.createSignedGetUrl( bucketName, key, login, expiry, false )
println "$fileName : $link"
}
code in the args block is not getting executed. When I execute this in Groovy Console it displays the result as []. Kindly help me where am I going wrong?
I don't have an S3 account to test with but here's a simplied example based upon the documenation:
import org.jets3t.service.impl.rest.httpclient.RestS3Service
import org.jets3t.service.model.S3Bucket
import org.jets3t.service.model.S3Object
import org.jets3t.service.security.AWSCredentials
#Grab('net.java.dev.jets3t:jets3t:0.9.0')
accessKey = 'accesskey'
secretKey = 'secretkey'
bucketName = 'bucketname'
fileName = 'D:\\sample.txt'
credentials = new AWSCredentials(accessKey, secretKey)
service = new RestS3Service(credentials)
bucket = new S3Bucket(bucketName)
file = new File(fileName)
fileObject = new S3Object(file)
fileObject.key = fileName
service.putObject(bucket, fileObject)
expiryTime = new Date() + 1 // 24 hours from current date
link = service.createSignedGetUrl(bucket.name, fileObject.key, expiryTime)
println "$fileName : $link"
I came accross that example recently as I attempt to code a quick groovy to upload to a S3 Bucket, however, all my attempts have ended up with 301
org.jets3t.service.S3ServiceException: Service Error Message. -- ResponseCode: 301, ResponseStatus: Moved Permanently, XML Error Message: <?xml version="1.0" encoding="UTF-8"?><Error><Code>PermanentRedirect</Code><Message>The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.</Message><Endpoint>bucketname.s3.amazonaws.com</Endpoint><Bucket>bucketname</Bucket><RequestId>4CF5EDE9EF604DBB</RequestId><HostId>89KoQLvd93pXhnxJGcEaziSrSOPFRNXqbfPfY7LTe03z5rvVLAVx7UnFkts/Qe1fQ7eOWsaAL7A=</HostId></Error>
at org.jets3t.service.S3Service.putObject(S3Service.java:2358)
at org.jets3t.service.S3Service$putObject.call(Unknown Source)
at awsBucketDrop.run(awsBucketDrop.groovy:21)
At first I though this was a bucket location issue, as I found reference around 301 for that, however I've modified the code to swap to the bucket location, to no avail.
import org.jets3t.service.model.S3Bucket
import org.jets3t.service.model.S3Object
import org.jets3t.service.security.AWSCredentials
#Grab('net.java.dev.jets3t:jets3t:0.9.4')
accessKey = '<key>'
secretKey = '<secret>'
bucketName = '<bucketname>'
fileName = '<fileLocation>'
credentials = new AWSCredentials(accessKey, secretKey)
service = new RestS3Service(credentials)
bucket = new S3Bucket(bucketName,"eu-west-1")
println bucket.getLocation()
file = new File(fileName)
fileObject = new S3Object(file)
fileObject.key = fileName
service.putObject(bucket, fileObject)
expiryTime = new Date() + 1 // 24 hours from current date
link = service.createSignedGetUrl(bucket.name, fileObject.key, expiryTime)
println "$fileName : $link"
Now, this sdk has not been updated since 2015, so it's a fairly old SDK so I get the feeling it's no longer compatible (and might have some url hard coded into it) but if you had similar experience that you managed to solve out, let me know.
Thanks
If you come accross this and need a working groovy, I've loosely modified this code to work as a simple groovy
import com.amazonaws.AmazonServiceException;
import com.amazonaws.SdkClientException;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.File;
import java.io.IOException;
Regions clientRegion = Regions.EU_WEST_1;
String bucketName = "bucketname";
String stringObjKeyName = "stringObjKeyName";
String fileObjKeyName = "fileObjKeyName";
String fileName = "fileLocation";
try {
//This code expects that you have AWS credentials set up per:
// https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.build();
// Upload a text string as a new object.
s3Client.putObject(bucketName, stringObjKeyName, "Uploaded String Object");
// Upload a file as a new object with ContentType and title specified.
PutObjectRequest request = new PutObjectRequest(bucketName, fileObjKeyName, new File(fileName));
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("plain/text");
metadata.addUserMetadata("x-amz-meta-title", "someTitle");
request.setMetadata(metadata);
s3Client.putObject(request);
} catch (AmazonServiceException e) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
e.printStackTrace();
} catch (SdkClientException e) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
e.printStackTrace();
}