AWS Lambda and API Gateway are becoming synonymous with “serverless infrastructure” and getting more and more popular. To deploy them in repeatable way, one of the tools I recommend is CloudFormation. There are many ways you can define your API and your Lambda, but when connecting the two with CloudFormation there’s usually something that many people miss, and only notice when {"message": "Internal server error"} is thrown from their API Gateway endpoint.

If you enable logs in API Gateway, you’ll probably notice the following log error:

Execution failed due to configuration error: Invalid permissions on Lambda function

You know that little popup that shows up when you modify your API Gateway integration through the Management Console?
api gateway lambda popup

It creates a permission which allows API Gateway to execute your Lambda function. When creating API Gateway with Lambda integration via CloudFormation, you need to create that permission yourself!

Example using troposphere:

from troposphere import awslambda

template.add_resource(awslambda.Permission(
    "LambdaPermission",
    Action="lambda:InvokeFunction",
    FunctionName="< YOUR LAMBDA ARN HERE >",
    Principal="apigateway.amazonaws.com",
    SourceArn=Join("", [
        "arn:aws:execute-api:",
        Ref("AWS::Region"),
        ":",
        Ref("AWS::AccountId"),
        ":",
        < YOUR API ID HERE >,
        "/",
        < YOUR API STAGE NAME HERE >
        "/*/*"
    ])
))

The API Gateway ARN (in this case referenced as SourceArn) looks like this:

arn:aws:execute-api:REGION:YOUR_ACCOUNT_ID:api_id/stage_name/HTTP_METHOD/resource

Unfortunately there’s no function in CloudFormation that will generate that for us at the moment, which means we have to put it together manually. Good news is, in the above example, if you create AWS::ApiGateway::RestApi and AWS::ApiGateway::Stage in the same template, you can simply reference them using Ref in place of < YOUR API ID HERE > and < YOUR API STAGE NAME HERE >

How this looks when it’s CloudFormation’s JSON?

"LambdaPermission": {
            "Properties": {
                "Action": "lambda:InvokeFunction",
                "FunctionName": "< YOUR LAMBDA ARN HERE >",
                "Principal": "apigateway.amazonaws.com",
                "SourceArn": {
                    "Fn::Join": [
                        "",
                        [
                            "arn:aws:execute-api:",
                            {
                                "Ref": "AWS::Region"
                            },
                            ":",
                            {
                                "Ref": "AWS::AccountId"
                            },
                            ":",
                            < YOUR API ID HERE >,
                            "/",
                            < YOUR API STAGE NAME HERE >,
                            "/*/*"
                        ]
                    ]
                }
            },
            "Type": "AWS::Lambda::Permission"
        }

Lambda’s ARN is returned simply by Ref on the Lambda object (at least that we don’t have to glue together manually!).

Was this post helpful to you? Yes!


Leave a comment