Search code examples
amazon-web-servicescdnamazon-cloudfrontaws-api-gateway

How do you add CloudFront in front of API Gateway


API Gateway (APIG), while it uses CloudFront (CF) it does not support CDN edge caching. When I configured a CF distribution to use APIG as the custom origin, I get a permission denied error.

How do I configure CF to fix this?


Solution

  • Until API Gateway (APIG) supports edge caching via its internal use of CloudFront (CF), I have come up with a workaround.

    You can indeed put CF dist in front of APIG, the trick is to force HTTPS only "Viewer Protocol Policy" AND to NOT forward the HOST header because APIG needs SNI.

    I setup my CF "Default Cache Behavior Settings" to not forward any headers, and forced "Viewer Protocol Policy" to "HTTPS Only" and it works. Hope this helps others.

    Here is a CloudFormation resource object that has all the required configuration (Note: I use the convention <stage>--<app name> for StackName):

    CloudFront:  
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          IPV6Enabled: true
          HttpVersion: http2
          Comment: !Join [ '--', [!Ref 'AWS::StackName', ' Cloud Front']]
          Aliases: [!Ref CloudFrontCname]
          ViewerCertificate:
            AcmCertificateArn: !Ref AcmCertificateArn
            SslSupportMethod: sni-only
            MinimumProtocolVersion: TLSv1.1_2016
          Origins:
          - Id: APIGOrigin
            DomainName: !Sub
              - ${apigId}.execute-api.${AWS::Region}.amazonaws.com
              - { apigId: !Ref ApiGatewayLambdaProxy }
            OriginPath: !Sub
              - /${Stage}
              - { Stage: !Select [ "0", !Split [ '--', !Ref 'AWS::StackName' ] ] }
            CustomOriginConfig:
              # HTTPPort: 80
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
            OriginCustomHeaders:
              - HeaderName: 'Verify-From-Cf'
                HeaderValue: !Ref VerifyFromCfHeaderVal
          DefaultCacheBehavior:
            AllowedMethods: ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
            CachedMethods: ["GET", "HEAD", "OPTIONS"]
            ForwardedValues:
              Headers:
              - Access-Control-Request-Headers
              - Access-Control-Request-Method
              - Origin
              - Authorization
              # - Host APIG needs to use SNI
              QueryString: true
            TargetOriginId: APIGOrigin
            ViewerProtocolPolicy: https-only
            Compress: true
            DefaultTTL: 0
          CustomErrorResponses:
          - ErrorCachingMinTTL: 0
            ErrorCode: 400
          - ErrorCachingMinTTL: 1
            ErrorCode: 403
          - ErrorCachingMinTTL: 5
            ErrorCode: 500
    DNSARecord:    
      Type: AWS::Route53::RecordSet
      Properties:
        Comment: !Ref 'AWS::StackName'
        Name: !Ref CloudFrontCname
        Type: A
        HostedZoneName: !Join ['.', [ !Select [1, !Split ['.', !Ref CloudFrontCname]], !Select [2, !Split ['.', !Ref CloudFrontCname]], '']]
        AliasTarget:
          HostedZoneId: !Ref Route53HostedZoneId
          DNSName: !GetAtt CloudFront.DomainName
    DNSAAAARecord:    
      Type: AWS::Route53::RecordSet
      Properties:
        Comment: !Ref 'AWS::StackName'
        Name: !Ref CloudFrontCname
        Type: AAAA
        HostedZoneName: !Join ['.', [ !Select [1, !Split ['.', !Ref CloudFrontCname]], !Select [2, !Split ['.', !Ref CloudFrontCname]], '']]
        AliasTarget:
          HostedZoneId: !Ref Route53HostedZoneId
          DNSName: !GetAtt C
    

    Late 2018 updates

    • CloudFormation finally supports setting SSL proto ver: MinimumProtocolVersion: TLSv1.1_2016
    • I've baked this (and many other) best practices into an OSS project: aws-blueprint