Summary
I have a platform running in AWS which is exposed via REST APIs backed by AWS Lambda. The platform stores metadata in DynamoDB and content in S3. Users are authenticated by Cognito and are stored in a User Pool. The platform manages permissions to control which users can access (and whether they can modify) which metadata (and therefore the associated content).
I can use STS to exchange users' JWT tokens for STS tokens (using the Cognito Identity Pool) and grant them permission to read or write to a particular object in S3. Creating the STS token is handled by the platform. This works perfectly when accessing S3 directly and appears to be a perfect use case for STS.
Two asides to address some comments:
- Pre-signed S3 URLs are not secure enough as anyone with the link can access the content
- I have to upload directly to S3 as I'm not running any servers and Lambdas aren't appropriate to stream my content through due to the temporary space limits in the Lambda (512MB) and the amount of time a request can execute through API Gateway (29 seconds)
I would like to add CloudFront in front of the S3 bucket to benefit from its CDN capabilities; this will reduce data transfer costs if content can be served from the edge rather than retrieving from S3 each time. This is where I am having problems - I do not know how to send the STS token to CloudFront such that it will pass it through to S3.
Question: how do I use STS credentials to read/write from an S3 bucket when it is fronted by CloudFront?
Further details
I have created two roles which can be assumed by STS. One which has s3:ListBucket
and one which has s3:PutObject
. I've then used Postman to perform GET
and PUT
requests on my bucket, specifying the access key, secret key and session token from the STS assume-role-with-web-identity
call.
Both GET
and PUT
operations and both work when accessing S3 directly but neither work when using CloudFront. Interestingly I get different errors for GET
and PUT
.
Error from GET
:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>No AWSAccessKey was presented.</Message>
<RequestId><!-- snip --></RequestId>
<HostId><!-- snip --></HostId>
</Error>
Error from PUT
:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId><!-- snip --></AWSAccessKeyId>
<StringToSign><!-- snip --></StringToSign>
<SignatureProvided><!-- snip --></SignatureProvided>
<StringToSignBytes><!-- snip --></StringToSignBytes>
<CanonicalRequest><!-- snip --></CanonicalRequest>
<CanonicalRequestBytes><!-- snip --></CanonicalRequestBytes>
<RequestId><!-- snip --></RequestId>
<HostId><!-- snip --></HostId>
</Error>
I have removed some values from the responses but can provide them if they are necessary.