Quick helpful article to get you to setup an API gateway which acts as a S3 proxy using Cloudformation script.
This is a Tidbit, basically whenever you see
Tidbit:in the title,
it means I am going to simply throw some helpful code without
explaining too much around it.
What you can use this for?
This is a cheap solution for your application to upload files via a REST API leveraging API gateways authorization options while optionally making the uploaded object available to public, you can even pull private objects via this API by passing the correct Authorization header.
Here is a simple depiction of a possible flow:

The code
I have uploaded the entire code on GitHub here.
Make sure you go through the serverless.yml file and replace stuff.
In case you don’t know what serverless framework is, I have written an article to explicitly remedy that here.
Here is the excerpt of the Cloudformation script that is available in the repository:
Resources:
s3filesbucket:
Type: 'AWS::S3::Bucket'
Properties:
AccessControl: PublicReadWrite
BucketName: ${env:bucketNamePrefix}<your bucket name>
VersioningConfiguration:
Status: Suspended
Tags:
- Key: Environment
Value: ${opt:stage}
WebsiteConfiguration:
IndexDocument: index.html
route53BucketDNS: #this adds a DNS recordset which can be used for public viewing of objects
Type: "AWS::Route53::RecordSet"
Properties:
HostedZoneId: <hosted zone id>
Name: ${env:bucketNamePrefix}<your bucket name>
ResourceRecords:
- ${env:bucketNamePrefix}<your bucket name>.s3-website-us-east-1.amazonaws.com
TTL: 300
Type: CNAME
s3Proxy:
Type: "AWS::ApiGateway::RestApi"
Properties:
BinaryMediaTypes: #add or substract MIME types here
- image/png
- image/jpg
- image/gif
- image/x-icon
- application/octet-stream
Description: Storage service for ${opt:stage} environment
FailOnWarnings: false
Name: ${env:apiGatewayPrefix}fileStorage #api gateway name
s3ProxyAuthorizer: #custom authorizer for the API gateway which adds stuff to the bucket
Type: "AWS::ApiGateway::Authorizer"
Properties:
AuthorizerResultTtlInSeconds: 300
AuthorizerUri: arn:aws:apigateway:us-east-1:lambda:path//2015-03-31/functions/arn:aws:lambda:us-east-1:<your acc id>:function:${opt:stage}<auth fn name>/invocations
IdentitySource: method.request.header.Authorization
IdentityValidationExpression: ^Bearer.+
Name: CommonAuthorizer
RestApiId:
Ref: "s3Proxy"
Type: TOKEN
s3ProxyAnyMethod:
Type: "AWS::ApiGateway::Method"
Properties:
ApiKeyRequired: false
AuthorizationType: CUSTOM
AuthorizerId:
Ref: "s3ProxyAuthorizer"
HttpMethod: ANY
Integration:
Credentials: arn:aws:iam::<your acc id>:role/s3ProxyRole
IntegrationHttpMethod: ANY
IntegrationResponses:
- StatusCode: 200
PassthroughBehavior: WHEN_NO_MATCH
RequestParameters:
integration.request.header.Content-Disposition: method.request.header.Content-Disposition
integration.request.header.Content-Type: method.request.header.Content-Type
integration.request.header.x-amz-acl: method.request.header.x-amz-acl
integration.request.path.key: method.request.querystring.key
Type: AWS
Uri: arn:aws:apigateway:us-east-1:s3:path/${env:bucketNamePrefix}<your bucket name>/{key}
MethodResponses:
- StatusCode: 200
RequestParameters:
method.request.header.Content-Disposition: false
method.request.header.Content-Type: false
method.request.header.x-amz-acl: false
method.request.querystring.key: false
ResourceId:
Fn::GetAtt:
- "s3Proxy"
- "RootResourceId"
RestApiId:
Ref: "s3Proxy"
ApiGatewayDeploymentthisiswhatiwillreplace:
Type: 'AWS::ApiGateway::Deployment'
Properties:
RestApiId:
Ref: "s3Proxy"
StageName: ${opt:stage}
DependsOn:
- s3ProxyAnyMethod
Points to note:
- The last part of the YAML declares an API Gateway deployment which will make the deployed changes available to public, the predicament is that unless you change something in that section (and there is nothing to change there ever), Cloudformation will not detect that as a change and will never deploy your changeset even though it will update every change, this is solved by replacing the aptly named
thisiswhatiwillreplacepart of the name with epoch time during the build process. - The build is via AWS Codebuild (Get started with it).
- Go through the entire script, edit as required, I have replaced some account specific stuff with placeholders like
oretc.
The script will deploy the following:
- The API gateway acting as the proxy and deploy it.
- An S3 bucket corresponding to the ‘stage’ that you deploy in.
- Create a DNS recordset in
Route 53to point to your bucket to have a custom domain name to access your public objects.
Calling the service
Put an object
PUT https://<your api id here>execute-api.us-east-1.amazonaws.com/prod?key=blah.png HTTP/1.1 Content-Type: image/png User-Agent: Fiddler Host: <your api id here>.execute-api.us-east-1.amazonaws.com Authorization: Bearer <some auth token> Content-Disposition: inline x-amz-acl: public-read Content-Length: 205523 <binary data here>
The header x-amz-acl: public-read will make the object available to public, to keep it private, simply omit the header.
Get a private object
GET https://<your api id here>.execute-api.us-east-1.amazonaws.com/prod?key=blah.png HTTP/1.1 Content-Type: image/png User-Agent: Fiddler Host: <your api id here>.execute-api.us-east-1.amazonaws.com Authorization: Bearer <some auth token> Content-Disposition: inline
Delete an object
DELETE https://<your api id here>.execute-api.us-east-1.amazonaws.com/prod?key=blah.png HTTP/1.1 Content-Type: image/png User-Agent: Fiddler Host: <your api id here>.execute-api.us-east-1.amazonaws.com Authorization: Bearer <some auth token> Content-Disposition: inline
If you liked this article, you can choose to follow this blog/subscribe to email alerts (floating follow button {bottom-right} or below comments in mobile) so that you know when any future posts come about.



Helpful post! One question. How do you secure the GET private object endpoint?
LikeLike
Because the private endpoints are behind AWS API gateway, you can use a custom authorizer. See this AWS documentation http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html, and then these samples of custom authorizers: https://github.com/awslabs/aws-apigateway-lambda-authorizer-blueprints
LikeLike
Hi, AFAIK
AccessControl: PublicReadWritemakes the S3 bucket world readable and writable. Do you think it’s a good idea to do so? Is this intended?Cheers
Soenke
LikeLike
No.. that’s a big goofup.. public-read was what was intended
LikeLike