22
votes

I've been all over the web searching for an answer to this.

Essentially, we're spinning up an API using Swagger, which is awesome and works great, but one thing doesn't work... When we make a call to an Endpoint, we get a 500 error (it's not a 500 error that we're providing either it's one from AWS). The error states "Execution failed due to configuration error: Invalid permissions on Lambda function" (https://youtu.be/H4LM_jw5zzs <- This is a video, from another user, of the error I'm getting).

I've gone down many ratholes, and have found an answer... It involves using the AWS CLI and looks a bit like this:

aws lambda add-permission \
--function-name FUNCTION_NAME \
--statement-id STATEMENT_ID \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:us-east-1:ACCOUNT_ID:API_ID/*/METHOD/ENDPOINT"

This is great and all, but we are using CloudFormation to spin up everything and we want this to be automated. Is there an easier way to go about this? Is there something in CloudFormation that will give us the resource policy that we need?

I'm hitting a bit of a wall with this, but I've been working on it for a few hours today and it's a bit of a blocker for our API release, so any help would be much appreciated. :)

3
For what it's worth, you can also do this via AWS SDK too. I use it for Go, so it's here: docs.aws.amazon.com/sdk-for-go/api/service/lambda/… ... What I don't see if any way to list existing permissions. So I just tolerate the errors about it already existing in my deploy tool. Weird. I mean I didn't even see anything about needing this permission either and it's not in any exported Swagger either.Tom

3 Answers

27
votes

There is a CloudFormation solution to this problem. See the following CloudFormation snippet:

"Permission": {
    "Type": "AWS::Lambda::Permission",
    "Properties": {
        "FunctionName": { "Fn::GetAtt": [ "Lambda", "Arn" ] },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": { "Fn::Join": [ "", [
            "arn:aws:execute-api:",
            { "Ref": "AWS::Region" }, ":",
            { "Ref": "AWS::AccountId" }, ":",
            { "Ref": "API" },
            "/*/*/*"
        ] ] }
    }
}

This grants API Gateway permissions to launch your Lambda function. Variables in this snippet you need to change are Lambda (line 4) and API (line 11).

2
votes

For the invoke permissions:

    "APIInvokePermission": {
  "Type": "AWS::Lambda::Permission",
  "Properties": {
    "FunctionName": {
      "Ref": "YOUR_LAMBDA_FUNCTION_RESOURCE_NAME"
    },
    "Action": "lambda:InvokeFunction",
    "Principal": "apigateway.amazonaws.com",
    "SourceArn": {
      "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${YOUR_REST_API_RESOURCE_NAME}/*/*/*"
    }
  }
},
2
votes

Thanks https://twitter.com/edjgeek for helping me get this straight in my head.

This GIST shows how to use AWS::Serverless:Function with Events to automatically generate the needed AWS::Lambda::Permission to allow APIGateway (for a given route) to invoke your Lambda:

https://gist.github.com/rainabba/68df1567cbd0c4930d428c8953dc2316

Both of the following approaches assume an api such as (many fields omitted for readability):

  MyApi:
    Type: 'AWS::Serverless::Api'
    Properties:
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: openapi.yaml

Using "SAM Events"

The most relevant bit (I've omitted many required fields):

  MyLambdaFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      Events:
        MyRouteEventToProxy:
          Type: Api
          Properties:
            Method: POST
            Path: '/some-route/{pathParm}'
            RestApiId: !Ref MyApi # ResourceName of AWS::Serverless::Api
            Auth:
              Authorizer: NONE

Using "openapi binding"

If you'd rather declare the binding in the openapi.yaml, then see the following project (no Lambda/Events required). This approach requires an explict role to allow invoke.

template.yaml relevant bits:

  MyLambdaFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      # Events: # No need for Events when binding from openapi.yaml
  MyHttpApiRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "apigateway.amazonaws.com"
            Action: 
              - "sts:AssumeRole"

openapi.yaml relevant bits:

paths:
  post:
      x-amazon-apigateway-integration:
        httpMethod: POST
        uri:
          Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}:live/invocations"
        contentHandling: "CONVERT_TO_TEXT"
        type: aws_proxy
        credentials:
          Fn::GetAtt: [MyHttpApiRole, Arn]

https://github.com/aws-samples/sessions-with-aws-sam/tree/master/http-api-direct-integration