Serverless-application-model: Configuring S3 for static files

Created on 27 Jan 2018  路  9Comments  路  Source: aws/serverless-application-model

Hi

My app is divided into two projects: a backend (lambdas) and frontend SPA (static files on S3), and I would like to use API Gateway as a proxy for S3.

Follow the documentation, would a SAM template like this:

# https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md#packing-artifacts
MyApi:
    Type: AWS::Serverless::Api
    Properties:
        DefinitionUri: s3://<mybucket>/<my-openapi-file-path>

With a swagger like Swagger Definitions of the Sample API as an Amazon S3 Proxy
, be the way to go?

So far, I don't see a clear support on SAM for static files (frontend). Is it on the roadmap or isn't SAM meant for that?

arecloudformation aretroubleshooting typquestion

Most helpful comment

Thanks @0xdevalias for providing an example. Closing this for now as using CloudFormation in your SAM template (which is totally valid and common) is the way to do this. We are considering adding some SAM magic for SPAs.

All 9 comments

wouldn't enabling static website hosting for your s3 bucket work for you?
If your use-case is more complicated what do you expect to achieve by proxying through gateway?

@quarryman I can't set a custom domain and SSL only using S3, I would need CloudFront or API Gateway, unless I'm missing something.

API Gateway seems more flexible than CloudFront, I can make /path/2/ to return path/index.html instead of trying /path/2/index.html.

Also on S3, I didn't find a way to define a S3 resource with SAM, I would need to write a script or CF outside of the SAM scope.

@phstc You can intermix Vanilla CloudFormation with SAM resources. You can define an S3 bucket and other CloudFormation Resources without any issues.

Is there a SAM way of doing this?

I also would love to see a pure SAM approach to this; without a single line of Cloudformation.

While it would be nice to have a 'SAM-native' way of doing this, until that time, this is the CloudFormation that works for me:

Frontend:
    Type: AWS::S3::Bucket
    Properties:
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: index.html # Note: This is required for handling direct links to paths such as /foo/bar in a React single page app
      LoggingConfiguration:
        DestinationBucketName: !Ref Logs
        LogFilePrefix: "logs-frontend-s3/logs-frontend-s3"

S3FrontendBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref Frontend
      PolicyDocument:
        Statement:
          - Effect: "Allow"
            Principal: '*'
            Action:
              - "s3:GetObject"
            Resource: !Sub "${Frontend.Arn}/*"

And then adding CloudFront in front of it all (which becomes quite a bit more verbose..):

  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: Frontend
        Enabled: true
        Origins:
          - Id: FrontendS3Origin
            DomainName: !GetAtt Frontend.DomainName
            S3OriginConfig:
              OriginAccessIdentity: ""
#              OriginAccessIdentity: !Sub "${FrontendOriginAccessIdentity}" # TODO: This lets us lock s3 to only be accessible through here.. empty=disabled: https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_S3Origin.html
        Aliases:
          - !Ref FrontendDomain
        DefaultRootObject: index.html
        CustomErrorResponses:
          # Note: This is required for handling direct links to paths such as /foo/bar in a React single page app
          - ErrorCode: 403
            ResponseCode: 200
            ResponsePagePath: "/index.html"
            ErrorCachingMinTTL: 5
          - ErrorCode: 404
            ResponseCode: 200
            ResponsePagePath: "/index.html"
            ErrorCachingMinTTL: 5
        DefaultCacheBehavior:
          TargetOriginId: FrontendS3Origin
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          DefaultTTL: 5
          MinTTL: 0
          MaxTTL: 5
          ForwardedValues:
            QueryString: false
          Compress: true
        HttpVersion: http2
        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-viewercertificate.html
        ViewerCertificate:
          CloudFrontDefaultCertificate: true
#          AcmCertificateArn: !Ref Certificate # TODO: This needs to be in us-east-1 for it to work with CloudFront..
#          SslSupportMethod: sni-only
        Logging:
          Bucket: !GetAtt Logs.DomainName
          Prefix: logs-cloudfront-s3
          IncludeCookies: false

Thanks @0xdevalias for providing an example. Closing this for now as using CloudFormation in your SAM template (which is totally valid and common) is the way to do this. We are considering adding some SAM magic for SPAs.

Just one question , how will the static file be uploaded into the s3 bucket ? Or do we need to do that step manually?

@turjachaudhuri For my workflow, seperately and mostly manual. I have a step in my Makefile.

Makefile

deploy-frontend:
    ./deploy-frontend.sh $(APP)

deploy-frontend.sh

#!/bin/sh
set -e

FRONTENDBUCKET=`./getOutputData.sh ${1} FrontendBucket`
echo FRONTENDBUCKET=${FRONTENDBUCKET}

cd ../frontend/build
aws s3 sync . "s3://${FRONTENDBUCKET}" --exclude '.*' --exclude '*/.*' --exclude '*.map' --delete

getOutputData.sh

#!/bin/sh
set -e

JQOUTPUTMAP='.Stacks | .[].Outputs | [.[] | {(.OutputKey): .OutputValue}] | reduce .[] as $item ({}; . + $item)'
JQGETKEY="| .${2} // \"N/A\""

aws cloudformation describe-stacks --stack-name ${1} | jq -e --raw-output "${JQOUTPUTMAP}${JQGETKEY}"
Was this page helpful?
0 / 5 - 0 ratings