0

Let me start by saying that I have a basic understanding of http callouts and s3 buckets so please feel free to educate me.

My goal is to access mp4 files on s3 buckets from within salesforce, please note that I don't want to download them, I want to click on a link from SF that will take me directly to the s3 file and play it.

In salesforce I have an object called clip__c and a field on it called 's3_path__c'. What I want is to click on the s3_path__c, run apex code to manipulate it, then get a page reference that will take me to the file on s3, ready to be played.

Issue: Since some of the files are very heavy, I can't use GET request (Exceeded max size limit of 6000000).

Proposed workaround:

  1. Use a GET request to get a small file (so I won't exceed the max size) from the s3 server
  2. Get the file, extract the security token and all other needed parameters from the response
  3. Construct new urls using the parameters above and put each of them on the relevant clip__c record.

Example:

https://my-bucket.s3.us-east-1.amazonaws.com/path/to/1st/file.mp4?required-parmas-from-get-small-file
https://my-bucket.s3.us-east-1.amazonaws.com/path/to/2nd/file.mp4?required-parmas-from-get-small-file
https://my-bucket.s3.us-east-1.amazonaws.com/path/to/3rd/file.mp4?required-parmas-from-get-small-file

I assume that all the "required-parmas-from-get-small-file" will stay the same as long as they don't expire (in that case, I'll issue another get callout to get updated values).

Implementation:

To get a sense of what I need, I opened one of the files from the UI, and saw the following url parameters:

https://my-bucket.s3.us-east-1.amazonaws.com/folder/test.mp4?
response-content-disposition=inline&
X-Amz-Security-Token=blablabla&
X-Amz-SignedHeaders=host&
X-Amz-Expires=300&
X-Amz-Credential=blabla%2Fus-east-1%2aws4_request&
X-Amz-Signature=My-Signature-gdfgdfgdfgdfg

Then, I used this great gist by brianmfear to authenticate and create the GET request:

public class AWSS3_GetService extends AWS {
    public override void init() {
        endpoint = new Url('https://s3.amazonaws.com/');
        resource = 'my-bucket.s3.us-east-1.amazonaws.com/folder/small_file.mp4';
        region = 'us-east-1';
        service = 's3';
        method = HttpMethod.XGET;
        accessKey = 'MY-ACCESS-KEY';
        createSigningKey('MY-SECRET-KEY');
    }

}

public abstract class AWS {
  ...
  ...
  ...
    HttpRequest createRequest() {
        init();
        payloadSha256 = EncodingUtil.convertToHex(Crypto.generateDigest('sha-256', payload));
        setHeader('x-amz-content-sha256', payloadSha256);
        setHeader('date', requestTime.formatGmt('E, dd MMM YYYY HH:mm:ss z'));
        if (host == null) {
            host = endpoint.getHost();
        }
        setHeader('host', host);
        HttpRequest request = new HttpRequest();
        request.setMethod(method.name().removeStart('X'));
        if (payload.size() > 0) {
            setHeader('Content-Length', String.valueOf(payload.size()));
            request.setBodyAsBlob(payload);
        }
        String finalEndpoint = new Url(endpoint, resource).toExternalForm(), queryString = createCanonicalQueryString();
        if (queryString != '') {
            finalEndpoint += '?' + queryString;
        }
        request.setEndpoint(finalEndpoint);
        for (String key: headerParams.keySet()) {
            request.setHeader(key, headerParams.get(key));
        }
        String[] headerKeys = new String[0];
        String stringToSign = createStringToSign(headerKeys);
        String tempSignature = EncodingUtil.convertToHex(Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey));
        request.setHeader(
            'Authorization',
            String.format(
                'AWS4-HMAC-SHA256 Credential={0},SignedHeaders={1},Signature={2}',
                new String[] {
                    String.join(new String[] {
                            accessKey,
                            requestTime.formatGMT('YYYYMMdd'),
                            region,
                            service,
                            'aws4_request'
                        }, '/'),
                        String.join(headerKeys, ';'), EncodingUtil.convertToHex(Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey))
                }
            ));

        System.debug('request.getHeader: ' + request.getHeader('Authorization'));
        return request;
    }
}

The authentication and GET request are working.

Then, when I run the following statement:

System.debug('request.getHeader: ' + request.getHeader('Authorization'));

I get the following output:

request.getHeader: AWS4-HMAC-SHA256 
Credential=blabla%2Fus-east-1%2aws4_request,
SignedHeaders=date;host;x-amz-content-sha256,
Signature=My-Signature-gdfgdfgdfgdfg

The output is practically the same as the url I got from the UI, so I have most pieces of the puzzle to construct my own URL, except I don't have the X-Amz-Security-Token and I don't know how to get it.

So my questions are, am I doing it right? Is this the right way to achieve what I need? If so, how do I get the security token from the GET request?

1
  • This is an AWS question - have a look at their docs. Named Credential with AWS v4 signature will generate an Authorization header. That works for many scenarios. Whether X-Amz-Security-Token is needed depends on details of your AWS IAM/security solution architecture. StackOverflow or AWS communities are better forums for asking about that.
    – identigral
    Oct 12 '20 at 23:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.