Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to set up an AWS Lambda function for returning S3 pre-signed URLs for uploading files.

README

Granted, this is little more than an obfuscated way of having a publicly writable S3 bucket, but if you don’t have a server which can pre-sign URLs for you, this might be an acceptable solution.

For this to work, you take the following steps:

  1. Create a Lambda func, along with a new IAM role, keeping the default code.
  2. Create an API in the API Gateway.
  3. Create a resource in said API.
  4. Create a POST method for that API resource, pointing it to the above Lambda func.
  5. Deploy the API to a new stage.
  6. Verify that you can call the Lambda func using curl followed by the URL shown for the stage, resource, and method.
  7. Create an S3 bucket if you haven’t already, and one or more folders you want to upload to.
  8. Add a bucket policy granting public read access to those folders (a sample is included in this gist).
  9. Create an IAM policy granting write permissions in those folders (also included).
  10. Attach this policy to the IAM role created above.
  11. Update the Lambda func with the code in this gist.
  12. Add a configuration variable named s3_bucket with the name of your S3 bucket.
  13. Verify that you can call it using curl followed by the same URL as previously, followed by the parameters --request POST --header 'Content-Type: application/json --data '{"object_key": "folder/filename.ext"}'
  14. Finally, verify that you can upload a file to the URL returned, using curl --upload-file followed by the path to some file and the URL received in the previous step.

That’s it!

'use strict';
const AWS = require('aws-sdk');
const s3 = new AWS.S3({signatureVersion: 'v4'});
exports.handler = (event, context, callback) => {
const bucket = process.env['s3_bucket'];
if (!bucket) {
callback(new Error(`S3 bucket not set`));
}
const key = event['object_key'];
if (!key) {
callback(new Error('S3 object key missing'));
return;
}
const params = {'Bucket': bucket, 'Key': key};
s3.getSignedUrl('putObject', params, (error, url) => {
if (error) {
callback(error);
} else {
callback(null, {url: url});
}
});
};
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::mybucket/folder1/*"
]
},
{
"Action": [
"s3:PutObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::mybucket/folder2/*"
]
}
]
}
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowPublicRead",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/folder1/*"
},
{
"Sid": "AllowPublicRead",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/folder2/*"
}
]
}
@andreafalzetti

This comment has been minimized.

Copy link

andreafalzetti commented May 29, 2018

Hi, thanks for this!

I've created the lambda and the bucket as specified. The lambda returns the pre-signed URL, which I'm using in the following request which fails with a 403:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>AccessDenied</Code>
    <Message>Access Denied</Message>
    <RequestId>DFEADD09BBEC7AF5</RequestId>
    <HostId>PKsGfBduOrHRPtkc/VVG5mbstj3cqyscGMu/afuDBfl/7BgFmAuWIjwUW3KILGXcG2WEnqW7Knk=</HostId>
</Error>

Any idea why?

@andreafalzetti

This comment has been minimized.

Copy link

andreafalzetti commented May 29, 2018

Update! My bad, during tests, I renamed the bucket and the IAM policy wasn't up to date. Interesting is that the Lambda will still create the pre-signed URL but that URL won't allow you to write into the S3 bucket. Hope it helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.