0
votes

I'm having a really stupid error and I feel very dumb :) after many tries, I decided to reduce my problem to its absolute minimum and see if I can try to isolate whatever I'm doing wrong.

Following is a CloudFormation template which:

  • Creates a root role
  • Creates a policy which attaches CloudWatch and S3 permissions to the role
  • Creates an API Gateway
  • Creates a GET method
  • Creates a POST method
  • Creates a simple Lambda with inline code in NodeJS
  • Creates permissions for the POST method to allow the Lambda to be executed.
  • Creates a Stage and API account

Everything creates fine, no errors, yet when I'm trying to execute the API Gateway POST to test the communication with the Lambda, I'm getting an error:

Fri Jul 19 18:31:41 UTC 2019 : Endpoint request body after transformations: 
Fri Jul 19 18:31:41 UTC 2019 : Sending request to https://lambda.us-<REGION>.amazonaws.com/2015-03-31/functions/arn:aws:lambda:<REGION>:<ACCOUNT_REMOVED>:function:FileUpload/invocations
Fri Jul 19 18:31:41 UTC 2019 : Execution failed due to configuration error: Invalid permissions on Lambda function
Fri Jul 19 18:31:41 UTC 2019 : Method completed with status: 500

I've read many tutorials, the AWS reference, and compared against other API Gateways I have already running as example, but no dice, I'm doing something really stupid, maybe someone can spot it in my CloudFormation template.

Thank you so much

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "AWS CloudFormation Template to create file server",
    "Parameters": {
        "Environment": {
            "Description": "Environment name",
            "Type": "String",
            "Default": "dev",
            "AllowedValues" : ["dev", "int", "stg", "prd"],
            "ConstraintDescription": "must be dev, int, stg or prd"
        },
        "deploymentVersion": {
            "Description": "Version of the lambda code",
            "Type": "String"
        }
    },
    "Resources": {
        "RootRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "RoleName": {"Fn::Join": ["", ["kepler-", {"Ref": "Environment"} ,"-keplerfiles-rootrole"]]},
                "AssumeRolePolicyDocument": {
                    "Version" : "2012-10-17",
                    "Statement": [ {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": [ "apigateway.amazonaws.com", "lambda.amazonaws.com" ]
                        },
                        "Action": [ "sts:AssumeRole" ]
                    } ]
                }
            }
        },
        "RolePolicies": {
            "Type": "AWS::IAM::Policy",
            "Properties": {
                "PolicyName": {"Fn::Join": ["", ["kepler-", {"Ref": "Environment"} ,"-keplerfiles-rootpolicy"]]},
                "PolicyDocument": {
                   "Version" : "2012-10-17",
                   "Statement": [ {
                      "Effect": "Allow",
                      "Action": [
                        "logs:CreateLogGroup",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents",
                        "s3:*"
                        ],
                      "Resource": "*"
                    } ]
                },
                "Roles": [ { "Ref": "RootRole" } ]
            }
        },
        "FilesRestApi": {
            "Type": "AWS::ApiGateway::RestApi",
            "Properties": {
                "Name" : "Kepler-FileServer-Next",
                "Description" : "API used for uploading and downloading files from S3",
                "FailOnWarnings" : true
            }
        },
        "RootFileResource": {
            "Type": "AWS::ApiGateway::Resource",
            "Properties": {
                "RestApiId": {"Ref": "FilesRestApi"},
                "ParentId": {"Fn::GetAtt": ["FilesRestApi", "RootResourceId"]},
                "PathPart": "keplerfiles2"
            }
        },
        "FolderResource": {
            "Type": "AWS::ApiGateway::Resource",
            "Properties": {
                "RestApiId": {"Ref": "FilesRestApi"},
                "ParentId": {"Ref": "RootFileResource"},
                "PathPart": "{folder}"
            }
        },
        "ItemResource": {
            "Type": "AWS::ApiGateway::Resource",
            "Properties": {
                "RestApiId": {"Ref": "FilesRestApi"},
                "ParentId": {"Ref": "FolderResource"},
                "PathPart": "{item}"
            }
        },
        "FileGetMethod": {
            "Type": "AWS::ApiGateway::Method",
            "Properties": {
                "AuthorizationType": "NONE",
                "HttpMethod": "GET",
                "RequestParameters" : {
                    "method.request.path.item": false,
                    "method.request.path.folder": false
                },
                "Integration": {
                    "Credentials" : {"Fn::GetAtt": ["RootRole", "Arn"]},
                    "Type": "AWS",
                    "IntegrationHttpMethod": "GET",
                    "PassthroughBehavior": "WHEN_NO_MATCH",
                    "RequestParameters" : {
                        "integration.request.path.object": "method.request.path.item",
                        "integration.request.header.x-amz-acl": "'authenticated-read'",
                        "integration.request.path.bucket": "method.request.path.folder"
                    },
                    "Uri": {"Fn::Join" : ["",
                        ["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":s3:path/{bucket}/{object}"]
                    ]},
                    "IntegrationResponses": [
                        {
                            "StatusCode": 500
                        },
                        {
                            "ResponseTemplates": {
                                "text/csv": "$input.body"
                            },
                            "SelectionPattern" : "2\\d{2}",
                            "StatusCode" : 200
                        }
                    ]
                },
                "ResourceId": {"Ref": "ItemResource"},
                "RestApiId": {"Ref": "FilesRestApi"},
                "MethodResponses": [
                    {
                        "ResponseModels": {
                            "text/csv": "Empty"
                        },
                        "StatusCode": 200
                    },
                    {
                        "StatusCode": 400
                    }
                ]
            }
        },
        "FilePostMethod": {
            "DependsOn": "FilePostPermission",
            "Type": "AWS::ApiGateway::Method",
            "Properties": {
                "AuthorizationType": "NONE",
                "HttpMethod": "POST",
                "Integration": {
                    "Credentials" : {"Fn::GetAtt": ["RootRole", "Arn"]},
                    "Type": "AWS",
                    "IntegrationHttpMethod": "POST",
                    "Uri": {"Fn::Join" : ["",
                        ["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["SaveFileLambda", "Arn"]}, "/invocations"]
                    ]},
                    "IntegrationResponses": [
                        {
                            "StatusCode": 200
                        }
                    ]
                },
                "ResourceId": {"Ref": "RootFileResource"},
                "RestApiId": {"Ref": "FilesRestApi"},
                "MethodResponses": [
                    {
                        "StatusCode": 200
                    },
                    {
                        "StatusCode": 400
                    }
                ]
            }
        },
        "FilePostPermission": {
            "Type": "AWS::Lambda::Permission",
            "Properties": {
                "Action": "lambda:InvokeFunction",
                "FunctionName": {"Fn::GetAtt": ["SaveFileLambda", "Arn"]},
                "Principal": "apigateway.amazonaws.com",
                "SourceArn": {"Fn::Join": ["", [
                    "arn:aws:execute-api:",
                    {"Ref": "AWS::Region"}, ":",
                    {"Ref": "AWS::AccountId"}, ":",
                    {"Ref": "FilesRestApi"},
                    "/*"]
                ]}
            }
        },
        "SaveFileLambda": {
            "Type": "AWS::Lambda::Function",
            "Properties": {
                "FunctionName" : "FileUpload",
                "Description" : "Save file to S3",
                "Handler": "index.handler",
                "Role": { "Fn::GetAtt" : ["RootRole", "Arn"] },
                "Code": {
                    "ZipFile": {
                        "Fn::Join": [
                            "\n",
                            [
                                "exports.handler = function(event, context) {",
                                "  return { statusCode: 200, headers: { \"Content-Type\": \"text/html\" }, body: \"hello world!\" };",
                                "};"
                            ]
                        ]
                    }
                },
                "Runtime": "nodejs8.10",
                "Environment" : {
                    "Variables" : {
                        "DATABASE_REGION":{"Ref": "AWS::Region"}
                    }
                }
            }
        },
        "ApiGatewayCloudWatchLogsRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Effect": "Allow",
                        "Principal": { "Service": ["apigateway.amazonaws.com"] },
                        "Action": ["sts:AssumeRole"]
                    }]
                },
                "Policies": [{
                    "PolicyName": "ApiGatewayLogsPolicy",
                    "PolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [{
                            "Effect": "Allow",
                            "Action": [
                                "logs:CreateLogGroup",
                                "logs:CreateLogStream",
                                "logs:DescribeLogGroups",
                                "logs:DescribeLogStreams",
                                "logs:PutLogEvents",
                                "logs:GetLogEvents",
                                "logs:FilterLogEvents"
                            ],
                            "Resource": "*"
                        }]
                    }
                }]
            }
        },
        "ApiGatewayAccount": {
            "Type" : "AWS::ApiGateway::Account",
            "Properties" : {
                "CloudWatchRoleArn" : {"Fn::GetAtt" : ["ApiGatewayCloudWatchLogsRole", "Arn"] }
            }
        },
        "ApiDeployment": {
            "Type": "AWS::ApiGateway::Deployment",
            "DependsOn": ["FileGetMethod","FilePostMethod"],
            "Properties": {
                "RestApiId": {"Ref": "FilesRestApi"},
                "StageName": "cfstage"
            }
        },
        "ApiStage": {
            "DependsOn" : ["ApiGatewayAccount"],
            "Type": "AWS::ApiGateway::Stage",
            "Properties": {
                "DeploymentId": {"Ref": "ApiDeployment"},
                "MethodSettings": [{
                    "DataTraceEnabled": true,
                    "HttpMethod": "*",
                    "LoggingLevel": "INFO",
                    "ResourcePath": "/*"
                }],
                "RestApiId": {"Ref": "FilesRestApi"},
                "StageName": "v1"
            }
        }
    }
}
1

1 Answers

1
votes

The issue you are seeing comes from setting an integration role as well as the Lambda permission.

So if you remove the execution role from the method and just keep the permission object you will be fine:

"FilePostMethod": {
    "DependsOn": "FilePostPermission",
    "Type": "AWS::ApiGateway::Method",
    "Properties": {
        "AuthorizationType": "NONE",
        "HttpMethod": "POST",
        "Integration": {
            "Type": "AWS",
            "IntegrationHttpMethod": "POST",
            "Uri": {"Fn::Join" : ["",
                ["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["SaveFileLambda", "Arn"]}, "/invocations"]
            ]},
            "IntegrationResponses": [
                {
                    "StatusCode": 200
                }
            ]
        },
        "ResourceId": {"Ref": "RootFileResource"},
        "RestApiId": {"Ref": "FilesRestApi"},
        "MethodResponses": [
            {
                "StatusCode": 200
            },
            {
                "StatusCode": 400
            }
        ]
    }
},

Alternatively, you could remove the permission and add the lambda:InvokeFunction permission to the execution role.