Search code examples
amazon-web-servicesaws-lambdaaws-cloudformationaws-api-gatewayserverless-framework

Invoke a Lambda from API Gateway Integration across different stacks using CloudFormation and Serverless Framework


I have a websocket in a proxy service (this service is a CloudFormation stack), my FE app connects to this websocket.

one of the websocket routes, let's call it "startFeedback", needs to invoke a Lambda in another CloudFormation stack. This is the CloudFormation code (using serverless framework) I used for that:

First of all I need a websocket route:

startFeedbackWebsocketsRoute:
    Type: 'AWS::ApiGatewayV2::Route'
    DependsOn:
      - 'WebsocketsApi'
      - 'FeedbackFlowStartFeedbackWebsocketsIntegration'
    Properties:
      ApiId:
        Ref: 'WebsocketsApi'
      RouteKey: 'startFeedback'
      AuthorizationType: 'NONE'
      Target:
        Fn::Join:
          - '/'
          - - 'integrations'
            - Ref: 'FeedbackFlowStartFeedbackWebsocketsIntegration'

then an integration to be invoked by that route:

FeedbackFlowStartFeedbackWebsocketsIntegration:
    Type: 'AWS::ApiGatewayV2::Integration'
    DependsOn: 'WebsocketsApi'
    Properties:
      ApiId:
        Ref: 'WebsocketsApi'
      IntegrationType: 'AWS_PROXY'
      IntegrationUri:
        Fn::Join:
          - ''
          - - 'arn:'
            - Ref: 'AWS::Partition'
            - ':apigateway:'
            - Ref: 'AWS::Region'
            - ':lambda:path/2015-03-31/functions/'
            - Fn::ImportValue: demo-feedback-stack-${sls:stage}-FeedbackFlowStartFeedbackLambdaArn
            - '/invocations'

In the sample code above, I am importing the ARN of the lambda that I wish to invoke

Last thing I need is a Lambda permission to allows API gateway to trigger the Lambda

FeedbackFlowStartFeedbackPermission:
    Type: 'AWS::Lambda::Permission'
    DependsOn:
      - 'WebsocketsApi'
    Properties:
      FunctionName:
            Fn::ImportValue: demo-feedback-stack-${sls:stage}-FeedbackFlowStartFeedbackLambdaArn
      Action: 'lambda:InvokeFunction'
      Principal: 'apigateway.amazonaws.com'

Whenever my app connects to the websocket, it calls $connect route, which invokes a Lambda in the same stack and runs some routine procedures which work fine. But after that when it calls the "startFeedback" route and needs to invoke a lambda from a separate stack, it always returns:

{"message": "Forbidden", "connectionId":***"myConnectionID"***, "requestId":***"myRequestId"***}

I thought it was a problem with permissions, so I tampered around with it and tried different things like creating an IAM role and attaching it to my integration to make sure it can actually invoke the lambda, but without luck.

this is the IAM role I created to try and fix the problem if it was a permissions problem:

FeedbackFlowStartFeedbackWebsocketsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: apigateway.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ApiGatewayInvokeLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: 
                  Fn::ImportValue: demo-feedback-stack-${sls:stage}-FeedbackFlowStartFeedbackLambdaArn

Am I missing something here?


Solution

  • According to AWS Docs for AWS::ApiGatewayV2::Integration, for Websockets we need to always add IntegrationMethod: POST

    I was missing that from my CloudFormation code

    Update: Actually it also turns out that when we manually create API Gateway routes and integrations, serverless framework doesn't understand that those resources that we are adding are related to our WebsocketApi...so serverless framework doesn't understand it has to redeploy the api

    As a workaround... we added the timestamp to websocket description so we force serverless framework to always redeploy the websocket api since it sees a change

    provider: {
        websocketsDescription: `Websockets API for messaging: ${new Date().toISOString()}`
    }