silvermine / serverless-plugin-cloudfront-lambda-edge

Adds Lambda@Edge support to Serverless
MIT License
296 stars 41 forks source link

How should I configure the serverless.yml file to run a lambda edge function every time an API gateway function is requested? #47

Closed josoroma-zz closed 4 years ago

josoroma-zz commented 4 years ago

How should I configure the serverless.yml file to run a lambda edge function every time an API gateway function is requested?

this is what I am currently trying to do:

functions:
  api:
    handler: course/api.main
    events:
      - cloudFront:
          eventType: 'origin-request'
          origin: 'https://0123456789.execute-api.us-east-1.amazonaws.com'
  create:
    handler: course/create.main
    events:
      - http:
          path: courses
          method: post
          cors: true
          authorizer: aws_iam

Reference: https://serverless.com/blog/lambda-at-edge-support-added

Where course/api.js has:

export const main = (event, context, callback) => {
  console.log('====> event', event);

  const request = event.Records[0].cf.request;

  console.log('====> request', request);

  callback(null, request);
};

I am hitting the following error:

  Serverless Error ---------------------------------------

  An error occurred: CloudFrontDistribution - The function cannot have environment variables. Function: arn:aws:lambda:us-east-1:0123456789:function:scb-courses-api-development-api:4 (Service: AmazonCloudFront; Status Code: 400; Error Code: InvalidLambdaFunctionAssociation; Request ID: 0123456789).

"Current API gateway endpoint" or "execute-api URL" is:

sls --aws-profile=api-dev info --verbose -s development -r us-east-1
ServiceEndpoint: https://0123456789.execute-api.us-east-1.amazonaws.com/development
 sls -v

Serverless: DOTENV: Loading environment variables from .env.development:
Framework Core: 1.55.1
Plugin: 3.2.0
SDK: 2.1.2
Components Core: 1.1.2
Components CLI: 1.4.0
jthomerson commented 4 years ago

You are asking about how to use the Serverless framework's native Lambda@Edge support, which is an entirely different thing than this plugin. This plugin was the original way to use Lambda@Edge with Serverless, and works differently than how they implemented it within the framework. If you switch to using the plugin, follow the directions in our README. If you stick with the native support, you'll need to ask for help through their support channels.

josoroma-zz commented 4 years ago

@jthomerson now my question is, using this plugin, how should I configure the serverless.yml file in order to invoke a function before requesting any apiGatewayRestApi function?

jthomerson commented 4 years ago

@josoroma follow what's in the README. If you have problems with that, post your serverless.yml file so we have a chance of helping you. Otherwise all we can do is tell you how to use the plugin, which the README already does.

josoroma-zz commented 4 years ago

@jthomerson cool! this is my configuration so far:

...

plugins:
  - serverless-bundle # Package our functions with Webpack
  - serverless-offline # Emulates AWS λ and API Gateway on your local machine
  - serverless-dotenv-plugin # Load .env as environment variables
  - '@silvermine/serverless-plugin-cloudfront-lambda-edge' # Associate a Lambda with a CloudFront distribution

...

custom:
  region: ${opt:region, env:AWS_REGION}
  stage: ${opt:stage, env:STAGE}

...
functions:
  create:
    handler: course/create.main
    events:
      - http:
          path: courses
          method: post
          cors: true
          authorizer: aws_iam

  before:
    name: 'scb-courses-api-${self:custom.stage}-before'
    handler: course/before.main
    memorySize: 128
    timeout: 1
    lambdaAtEdge:
      distribution: 'ApiGatewayRestApiDistribution'
      eventType: 'origin-request'
      origin:
        DomainName: '01234567890.execute-api.us-east-1.amazonaws.com'
        OriginPath: '/development'

Not quite sure what to put in Resources for the ServiceEndpoint: https://0123456789.execute-api.us-east-1.amazonaws.com/development

Thanks!

jthomerson commented 4 years ago

@josoroma have you read the README of this project? There is no origin attribute under lambdaAtEdge. You define your own CloudFront distribution, and then your Lambda function's lambdaAtEdge.distribution points to that distribution, which must be in the same serverless.yml.

We do not create a CloudFront distribution for you.

josoroma-zz commented 4 years ago

Yep @jthomerson I got that very clear, in fact I am creating the resource but I just can't make the edge function run before calling any other api-function.

I am getting a Status Code: 403 Forbidden response using the new Api Distribution URL as the new endpoint URL within my current conf/amplify.js file.

Part of my current serverless.yml file

...
plugins:
...
  - '@silvermine/serverless-plugin-cloudfront-lambda-edge' # Associate a Lambda with a CloudFront distribution
...

functions:

  # Call a Lambda@Edge function before each API request.
  # Invoke the "request" interceptor every time a request is sent to the origin.
  request:
    name: 'scb-courses-api-${self:custom.stage}-request-api-gateway-distribution'
    handler: course/interceptors/request.main
    memorySize: 128
    timeout: 1
    lambdaAtEdge:
      distribution: 'ApiGatewayDistribution'
      eventType: 'origin-request'

  # Defines an HTTP API endpoint that calls the main function in create.js
  # - path: url path is /courses
  # - method: POST request
  # - cors: enabled CORS (Cross-Origin Resource Sharing) for browser cross
  #     domain api call
  # - authorizer: authenticate using the AWS IAM role
  create:
    handler: course/create.main
    events:
      - http:
          path: courses
          method: post
          cors: true
          authorizer: aws_iam

...

resources:
  ...
  # cloudfront invoke lambda@edge before API Gateway request
  - ${file(resources/api-gateway-distribution.yml)}
  ...

The resource/distribution is currently created through resources/api-gateway-distribution.yml

  Resources:
    ApiGatewayDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Comment: Api Gateway Distribution
          DefaultCacheBehavior:
            TargetOriginId: ApiGatewayOrigin
            ViewerProtocolPolicy: 'redirect-to-https'
            DefaultTTL: 30
            ForwardedValues:
              QueryString: false
          Enabled: true
          Origins:
            - Id: ApiGatewayOrigin
              DomainName:
                Fn::Join:
                  - "."
                  - - Ref: ApiGatewayRestApi
                    - execute-api.us-east-1.amazonaws.com
              OriginPath: /development
              CustomOriginConfig:
                HTTPPort: 80
                HTTPSPort: 443
                OriginProtocolPolicy: https-only

where course/interceptors/request.js is:

/**
 * https://serverless.com/framework/docs/providers/aws/events/cloudfront
 * https://serverless.com/blog/lambda-at-edge-support-added
 *
 * Add support for associating a Lambda function with a CloudFront distribution
 * to take advantage of the Lambda@Edge features of CloudFront:
 *
 *  - https://github.com/silvermine/serverless-plugin-cloudfront-lambda-edge
 *
 * Instead of calling the URL we did earlier (the API Gateway URL),
 * we are going to use a different URL which points to the CloudFront
 * Distribution:
 *
 *  - https://www.yld.io/blog/caching-in-with-cloudfront-using-serverless
 */

export const main = (event, context, callback) => {
  console.log('*====> event: ', event);

  const request = event.Records[0].cf.request;

  console.log('*====> request: ', request);

  callback(null, request);
};

my current conf/amplify.js file, the one used by the frontend side:

export const amplifyConf = {
  Auth: {
    identityPoolId: '...',
    region: 'us-east-1',
    userPoolId: '...',
    userPoolWebClientId: '...',
  },
  API: {
    endpoints: [
      {
        name: 'ApiGatewayRestApi',
        endpoint: 'https://{ApiGatewayDistributionURL}.cloudfront.net',
      },
    ],
  },
};

export const storageSourceConf = {
  Storage: {
    bucket: '...',
    region: 'us-east-1',
    identityPoolId: '...',
  },
};
jthomerson commented 4 years ago

Sorry @josoroma but this isn't the place to ask for help debugging a problem following someone's blog post. Honestly, I think that blog post (and thus your solution) is probably over-complicating things. Every API Gateway distribution (standard ones, not the new HTTP API ones) has a CloudFront distribution built into it. You can also use APIGW with custom domain names. So, you may not have a need for CF at all. In either case, there's a lot of moving parts that have nothing to do with this plugin. If you go into your AWS (web) console and see that your Lambda function is associated with your CloudFront distribution, then this plugin has done its job. The configuration of the actual distribution and especially your origins is well beyond the scope of this plugin.