Add SNS trigger to a specific lambda version using cloudformation stack - aws-lambda

I am able to add SNS as an event source for my lambda function.But I am unable to add SNS trigger to a specific lambda version ie to a qualified function name.
Here's my cloud formation snippet code:
LambdaCore:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Description: Lambda Core with X-ray
FunctionName: !Ref LambdaCore
Handler: !Ref LambdaCoreHandler
MemorySize: '512'
DeadLetterConfig:
TargetArn: !GetAtt
- DeadLetterQueue
- Arn
Role: !GetAtt
- LambdaRoleForCore
- Arn
Runtime: java8
Timeout: '300'
TracingConfig:
Mode: Active
VpcConfig:
SecurityGroupIds: !Ref SecurityGroups
SubnetIds: !Ref Subnets
Version:
Type: AWS::Lambda::Version
DependsOn:
- LambdaCore
Properties:
FunctionName: !Ref LambdaCore
Description: v1
InputEventLambdaSubscription:
Type: 'AWS::SNS::Subscription'
DependsOn:
- SNSTopic
- LambdaCore
Properties:
Endpoint: !Ref
- LambdaCore
- Arn
Protocol: lambda
TopicArn: !Ref SNSTopic
PermissionForSNSToInvokeCore:
Type: 'AWS::Lambda::Permission'
DependsOn:
- LambdaCore
- SNSTopic
Properties:
FunctionName: !GetAtt
- LambdaCore
- Arn
Action: 'lambda:InvokeFunction'
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopic

If you want to have the subscription point to the version you need to use the version ARN in the subscription. The ARN of AWS::Lambda::Version will look just like a lambda ARN except that it includes the :<version> at the end.
A couple of other things.
You don't need to do DependsOn when the thing you are including in the dependency is a Ref or GetAtt in the resource.
The short form of GetAtt is a lot easier to look at an understand.
LambdaCore:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Description: Lambda Core with X-ray
FunctionName: !Ref LambdaCore
Handler: !Ref LambdaCoreHandler
MemorySize: '512'
DeadLetterConfig:
TargetArn: !GetAtt DeadLetterQueue.Arn
Role: !GetAtt LambdaRoleForCore.Arn
Runtime: java8
Timeout: '300'
TracingConfig:
Mode: Active
VpcConfig:
SecurityGroupIds: !Ref SecurityGroups
SubnetIds: !Ref Subnets
Version:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref LambdaCore
Description: v1
InputEventLambdaSubscription:
Type: 'AWS::SNS::Subscription'
Properties:
# this will return the ARN of the lambda with the version on it
Endpoint: !Ref Version
Protocol: lambda
TopicArn: !Ref SNSTopic
PermissionForSNSToInvokeCore:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !GetAtt LambdaCore.Arn
Action: 'lambda:InvokeFunction'
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopic

Related

Lambda(Serverless Framework) + DynamoDB Accelerator(DAX): App can not connect

i created my app using AWS(API Gateway/Lambda/DynamoDB).
but loading speed of the app is too late.
so, i want to improve the loading speed using DynamoDB DAX(cache).
serverless.yml
service: myapp
frameworkVersion: '2.31'
provider:
name: aws
runtime: nodejs14.x
lambdaHashingVersion: 20201221
stage: $opt:stage
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- dax:*
Resource: '*'
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: '*'
vpc:
securityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
subnetIds:
- !Ref daxSubnet
functions:
graphql:
handler: ./build/src/app.handler
events:
- http:
path: graphql
method: ANY
environment:
DAX_ENDPOINT: !GetAtt daxCluster.ClusterDiscoveryEndpoint
vpc:
securityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
subnetIds:
- !Ref daxSubnet
resources:
Resources:
daxCluster:
Type: AWS::DAX::Cluster
Properties:
ClusterName: dax-cluster
IAMRoleARN: !GetAtt daxRole.Arn
NodeType: dax.t2.small
ReplicationFactor: 1
SecurityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
SubnetGroupName: !Ref daxSubnetGroup
daxRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- dax.amazonaws.com
Version: '2012-10-17'
RoleName: dax-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
daxSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for Dax
GroupName: dax-security-group
VpcId: !Ref daxVpc
daxSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: daxSecurityGroup
Properties:
GroupId: !GetAtt daxSecurityGroup.GroupId
IpProtocol: tcp
FromPort: 8111
ToPort: 8111
SourceSecurityGroupId: !GetAtt daxSecurityGroup.GroupId
daxVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: dax-cluster
daxSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ''
CidrBlock: 10.0.0.0/20
Tags:
- Key: Name
Value: dax-cluster
VpcId: !Ref daxVpc
daxSubnetGroup:
Type: AWS::DAX::SubnetGroup
Properties:
Description: Subnet group for DAX
SubnetGroupName: dax-subnet-group
SubnetIds:
- !Ref daxSubnet
MyTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.stage}_table
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
app.ts
import { DynamoDB } from "aws-sdk";
import AmazonDaxClient from "amazon-dax-client";
export default async() {
...
const endpoint = process.env.DAX_ENDPOINT as string;
const config = { ... }
const dax: any = new AmazonDaxClient({endpoints: [endpoint], region: 'us-east-1'});
const dynamodb = new DynamoDB.DocumentClient({...config, service: dax});
await dynamodb.transactWrite({
TransactItems: [
{
Delete: {
TableName: 'development_table',
Key: {
id: args.id,
createdAt: createdAt
}
}
},
],
}).promise();
return ( ... )
}
My App can not connect to API Gateway.(Timeout)
Please help me, That's very kind of you.
i tried...
serverless.yml
...
functions:
graphql:
handler: ./build/src/app.handler
events:
- httpApi:
path: /{id+}
method: GET
- httpApi:
path: /
method: POST
...
But, App can not connect.
i referred to AWS blog
package
"#types/amazon-dax-client": "^1.2.3",
"amazon-dax-client": "^1.2.9",

Cannot setup aws SNS trigger Lambda function

I used this template to create a cloudformation stack
Description: App Store Env
Parameters:
SourceAccountId:
Description: source S3 bucket owner account ID
Type: String
SourceS3Bucket:
Description: source S3 bucket
Type: String
AipEnv:
Description: AIP environment
Type: String
Default: preprod
ServiceName:
Description: Service Name
Type: String
ServiceVersion:
Description: Service Version
Type: String
DescriptionTag:
Description: Service description
Type: String
Default: ""
LambdaRoleName:
Description: Lambda role name
Type: String
Default: Nucleus-Renegade-Lambda
Mappings:
EnvironmentMap:
dev:
ApplicationId: {some_value}
BillingTag: AppStore-Services
CostCenterTag:
TeamBucket: {some_value}
TeamDataBucket: {some_value}
AccountId: {some_value}
SecurityGroups:
- {some_value}
- {some_value}
Subnets:
- {some_value}
- {some_value}
preprod:
ApplicationId: {some_value}
BillingTag: AppStore-Services
CostCenterTag: {some_value}
TeamBucket: {some_value}
TeamDataBucket: {some_value}
AccountId: {some_value}
SecurityGroups:
- {some_value}
Subnets:
- {some_value}
- {some_value}
prod:
ApplicationId: {some_value}
BillingTag: AppStore-Services
CostCenterTag: {some_value}
TeamBucket: {some_value}
TeamDataBucket: {some_value}
AccountId: {some_value}
Resources:
S3SyncLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: app.lambda_handler
MemorySize: 512
Role: !Sub "arn:aws:iam::${AWS::AccountId}:role/${LambdaRoleName}"
Code:
S3Bucket: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- TeamBucket
S3Key: !Sub >-
${ServiceName}/lambda/s3sync/${ServiceVersion}/lambda-function.zip
FunctionName: !Sub >-
aip-s3sync-${AipEnv}-${ServiceName}-${ServiceVersion}
Runtime: python3.9
Timeout: 300
Environment:
Variables:
TEAM_BUCKET: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- TeamBucket
Tags:
- Key: Billing
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- BillingTag
- Key: COST_CENTER
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- CostCenterTag
- Key: APP_ID
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- ApplicationId
- Key: Description
Value: !Ref DescriptionTag
AdtTopic:
Type: 'AWS::SNS::Topic'
Properties:
TopicName: !Sub "aip-${AipEnv}-${ServiceName}-NewAdtSNS"
KmsMasterKeyId: alias/SNS
Tags:
- Key: Billing
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- BillingTag
- Key: COST_CENTER
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- CostCenterTag
- Key: APP_ID
Value: !FindInMap
- EnvironmentMap
- !Ref AipEnv
- ApplicationId
- Key: Description
Value: !Ref DescriptionTag
AdtTopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Id: AdtTopicPolicy
Version: '2012-10-17'
Statement:
- Sid: Allow-S3-id
Effect: Allow
Principal:
Service: s3.amazonaws.com
Action: sns:Publish
Resource: !Ref AdtTopic
Condition:
StringEquals:
aws:SourceAccount: !Ref SourceAccountId
ArnLike:
aws:SourceArn: !Sub "arn:aws:s3:*:*:${SourceS3Bucket}"
Topics:
- !Ref AdtTopic
InvokeLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref S3SyncLambdaFunction
Principal: sns.amazonaws.com
SourceArn: !Ref AdtTopic
However, after the stack is created, I checked lambda function trigger and found:
SNS:
A subscription for {lambda_function_arn} on the topic {sns_topic} could not be found.
Do not know which part in template is wrong. I use InvokeLambdaPermission to configure sns trigger to lambda function. Can anyone help me on this issue?
Regards,
Arthur
I think you should use this resource type in order to set sns topic to trigger your lambda:
Type: AWS::Lambda::EventSourceMapping
On your case add something like this:
LambdaFunctionEventSourceMapping:
Type: AWS::Lambda::EventSourceMapping
Properties:
EventSourceArn: !GetAtt AdtTopic.Arn
FunctionName: !GetAtt LambdaFunction.Arn
You can read more about the resource type here:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html
Also in this article:
https://aws.amazon.com/premiumsupport/knowledge-center/lambda-subscribe-push-cloudformation/
it says:
"
The AWS::Lambda::EventSourceMapping resource is designed for pull-based event sources, such as Amazon DynamoDB event streams and Amazon Kinesis. With push-based event sources, such as Amazon S3 event notifications or Amazon SNS messages, the event source is responsible for invoking the Lambda function. For a push event source to invoke a Lambda function, the function’s resource policy must authorize a supported event source.
"
Good luck!
You have to define AWS::SNS::Subscription which is going to subscribe your lambda to a given sns topic.

AWS CloudFormation setting SNS trigger to Lambda

I want to add SNS as a trigger for Lambda in CloudFormation template but I it is not working for me. Below is the code I applied and I get lambda, sns and lambda subscription to SNS but I can't add trigger to lambda. Does anyone have any idea how to do it?
LambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: lambda_policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: '*'
Resource: '*'
Lambdafunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt LambdaRole.Arn
# Role: !GettAtt [LambdaRole, Arn]
Code:
S3Bucket: lambda-s3
S3Key: lambda.zip
Runtime: python3.9
Timeout: 30
PermissionSNStoLambda:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Ref Lambdafunction
Principal: sns.amazonaws.com
test2Topic:
Type: 'AWS::SNS::Topic'
Properties:
DisplayName: Scale of Test Web group
Subscription:
- Protocol: lambda
Endpoint: !GetAtt Lambdafunction.Arn
SNSPolicy:
Type: 'AWS::SNS::TopicPolicy'
Properties:
Topics:
- !Ref test2Topic
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: AWS: '*'
Action:
- 'SNS:GetTopicAttributes'
- 'SNS:SetTopicAttributes'
- 'SNS:AddPermission'
- 'SNS:RemovePermission'
- 'SNS:DeleteTopic'
- 'SNS:Subscribe'
- 'SNS:ListSubscriptionsByTopic'
- 'SNS:Publish'
- 'SNS:Receive'
Resource: !Ref test2Topic
This is the most logical for me to use to add the trigger, but no success:
SNSTriggersLambda:
Type: AWS::Lambda::EventInvokeConfig
Properties:
DestinationConfig:
OnFailure:
Destination: !Ref test2Topic
OnSuccess:
Destination: !Ref test2Topic
FunctionName: !Ref Lambdafunction
MaximumEventAgeInSeconds: 70
MaximumRetryAttempts: 1
Qualifier: $LATEST
I just managed to resolve the issue. I needed one extra line in permission section stating source ARN for SNS Topic:
PermissionSNStoLambda:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Ref Lambdafunction
Principal: sns.amazonaws.com
SourceArn: !Ref test2Topic
It works now!

Cloudformation: Invalid permissions on Lambda function found with API GW Integration

This is my cloudformation template, which deploys successfully:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Deploy Da Blog
Resources:
ArticleTable:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: id
KeyType: HASH
AttributeDefinitions:
- AttributeName: id
AttributeType: S
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: !Sub ${AWS::StackName}-Article
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
############################### BLOG API ############################
BlogRestApiLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/apigateway/${AWS::StackName}'
BlogRestApiLoggingRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'BlogRestApiLoggingRole-${AWS::StackName}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
## specifies the IAM role that Amazon API Gateway uses to write API logs to Amazon CloudWatch Logs
BlogRestApiAccount:
Type: AWS::ApiGateway::Account
Properties:
CloudWatchRoleArn: !GetAtt [ BlogRestApiLoggingRole, Arn ]
BlogRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub '${AWS::StackName}'
Description: Blog API
EndpointConfiguration:
Types:
- EDGE
BlogRestApiDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId: !Ref BlogRestApi
Description: Automatically created by the RestApi construct
DependsOn:
- BlogRestApi
- BlogApiResource
- BlogApiIdResource
- ListArticlesMethod
- PostArticleMethod
- GetArticleMethod
BlogRestApiStageProd:
Type: AWS::ApiGateway::Stage
DependsOn:
- BlogRestApiDeployment
Properties:
RestApiId: !Ref BlogRestApi
DeploymentId: !Ref BlogRestApiDeployment
StageName: prod
MethodSettings:
- LoggingLevel: INFO
ResourcePath: '/*'
HttpMethod: '*'
MetricsEnabled: true
BlogApiRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'BlogApiRole-${AWS::StackName}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action:
- sts:AssumeRole
BlogApiPolicy:
Type: AWS::IAM::Policy
DependsOn:
- BlogApiRole
- ListArticlesFunction
- PostArticleFunction
- GetArticleFunction
Properties:
PolicyName: !Sub 'BlogApiPolicy-${AWS::StackName}'
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: 'Allow'
Action: 'lambda:InvokeFunction'
Resource:
- !GetAtt ListArticlesFunction.Arn
- !GetAtt PostArticleFunction.Arn
- !GetAtt GetArticleFunction.Arn
Roles:
- !Ref BlogApiRole
############################### END OF Blog API ############################
############################### START OF Blog Functions ####################
BlogApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt [ BlogRestApi, RootResourceId ]
PathPart: article
RestApiId: !Ref BlogRestApi
BlogApiIdResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !Ref BlogApiResource
PathPart: "{id}"
RestApiId: !Ref BlogRestApi
BlogApiOptionsMethod:
Type: AWS::ApiGateway::Method
DependsOn:
- BlogApiResource
Properties:
ApiKeyRequired: true
AuthorizationType: NONE
HttpMethod: OPTIONS
Integration:
Type: MOCK
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': '{"statusCode": 200}'
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PATCH,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
ResponseTemplates:
'application/json': '{}'
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: false
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
ListArticlesFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-list-articles-function'
Handler: com.zenithwebfoundry.blog.api.ListArticlesHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:Query
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
ListArticlesMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListArticlesFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: ListArticles
PostArticleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-post-article-function'
Handler: com.zenithwebfoundry.blog.api.SaveArticleHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
PostArticleMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: POST
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostArticleFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: PostArticle
GetArticleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-get-article-function'
Handler: com.zenithwebfoundry.blog.api.GetArticleHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
GetArticleMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiIdResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetArticleFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: GetArticle
############################### END OF Blog Functions ######################
BlogAPIDomainName:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: !Join [ ".", ['blogapi', !Ref DomainName]]
EndpointConfiguration:
Types:
- EDGE
CertificateArn: !Ref DomainCert
SecurityPolicy: TLS_1_0
BlogAPIHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref BlogAPIDomainName
BlogAPIBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Ref BlogAPIDomainName
RestApiId: !Ref BlogRestApi
Stage: 'prod'
Route53RecordSetGroup:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId: !Ref DomainHostedZoneId
RecordSets:
- Name: !Join [ "", ['blog', '.', !Ref DomainName, '.']]
Type: A
TTL: '300'
ResourceRecords:
- 52.64.238.177
- Name: !Join [ ".", ['blogapi', !Ref DomainName]]
Type: A
AliasTarget:
# HostedZoneId: !Ref BlogAPIHostedZone # distributionHostedZoneId - alias target name does not lie in the target zone
HostedZoneId: Z2FDTNDATAQYW2
# https://j97h8bvml9.execute-api.ap-southeast-2.amazonaws.com/prod/articles
DNSName: !GetAtt [BlogAPIDomainName, DistributionDomainName]
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'assets' ] ]
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [GET,PUT,POST,DELETE,HEAD]
AllowedOrigins: ['http://localhost*']
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
WebBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'web' ] ]
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
WebsiteConfiguration:
IndexDocument: 'index.html'
ErrorDocument: 'index.html'
Parameters:
ParamCodePackage:
Type: String
ParamCodeBucket:
Type: String
DomainName:
Description: "Public DNS Zone Name"
Type: String
DomainHostedZoneId:
Type: String
Description: 'The AWS HostedZoneId of the above domain name'
DomainCert:
Type: String
Description: 'The arn reference to the certificate used with the domain.'
ParamRequestMappingTemplate:
Type: String
Description: 'Read from resources/templates'
SaveArticleHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.SaveArticleHandler'
GetArticleHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.GetArticleHandler'
ListArticlesHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.ListArticlesHandler'
ParamAPIGatewayRequestEventMappingTemplate:
Type: String
Default: '{
"resource" : "$context.resourceId",
"path" : "$context.path",
"httpMethod" : "$context.httpMethod",
"headers": {
#foreach($header in $input.params().header.keySet())
"$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end
#end
},
"method": "$context.httpMethod",
"pathParameters": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
},
"queryStringParameters": {
#foreach($queryParam in $input.params().querystring.keySet())
"$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
#end
},
"body" : $input.json("$"),
"isBase64Encoded": false
}'
ParamAPIGatewayResponseEventMappingTemplate:
Type: String
Default: '#set($statusCode = $input.path("$.statusCode"))
#set($context.responseOverride.status = $statusCode)
#set($headers = $input.path("$.headers"))
#foreach($key in $headers.keySet())
#set($context.responseOverride.header[$key] = $headers[$key])
#end
#set($context.responseOverride.header.Access-Control-Allow-Headers = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token")
#set($context.responseOverride.header.Access-Control-Allow-Methods = "*")
#set($context.responseOverride.header.Access-Control-Allow-Origin = "*")
{
"body": $input.json("$.body")
}
'
Outputs:
ArticleEndpoint:
Value: !Join ["", ['https://', !Ref BlogRestApi, '.execute-api.ap-southeast-2.', !Ref 'AWS::URLSuffix', '/', !Ref BlogRestApiStageProd, '/'] ]
The problem is that when I try to call the ListArticlesFunction using the following curl expression:
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET https://blogapi.zenithwebfoundry.com/article
I get the following:
HTTP/2 500
content-type: application/json
content-length: 36
date: Sun, 30 Aug 2020 09:09:50 GMT
x-amzn-requestid: c1948ed5-18c5-4807-8904-3c1c2af06c25
x-amzn-errortype: InternalServerErrorException
x-amz-apigw-id: SE3y1G71ywMF5fw=
x-cache: Error from cloudfront
via: 1.1 647846f53eba457a8e4ba1d1d42a6336.cloudfront.net (CloudFront)
x-amz-cf-pop: SYD1-C1
x-amz-cf-id: 6bnMhNRUdf1znTmD0vQn86UZcMF_j9JCzFb-JvhwwT9j6ch4P8t20g==
{"message": "Internal server error"}
Checking cloudwatch, I see the slightly cryptic error:
Execution failed due to configuration error: Invalid permissions on Lambda function
The same thing happens if I go into the APIGateway service console and go to the article GET resource. So at least its consistent.
I have tested the handler in isolation and there's no compilation problems anywhere, so I'm pretty sure its stack-related.
I gather that the problem must either be in the BlogApiPolicy Policy or the Lambda Policy, but nothing I set seems to work. Does anyone know what cloudformation shenanigans is needed to get this right?
The answer is that I needed the role added as a Credentials: entry in the method of each Lambda, for example:
ListArticlesMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListArticlesFunction.Arn}/invocations'
Credentials: !GetAtt [BlogApiRole, Arn]
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: ListArticles
Why this is suddenly needed, when this was never needed before, is quite beyond me, but I must thank #Marcin for his/her patient assistance and convincing my doubting mind.
Based on the comments.
The issue was that BlogApiRole, although being created, it was not used in any of the API Gateway methods. This is required, because API Gateway needs to have explicit permissions to invoke a lambda functions. To enable this, Credentials in AWS::ApiGateway::Method Integration should be set.
The alternative is to use AWS::Lambda::Permission. The use of the AWS::Lambda::Permission is especially useful when there are many methods in the API gateway which require invoking the lambda. The reason is that you can create one such AWS::Lambda::Permission for the lambda function which can allow API gateway access to the lambda as a whole. This saves us from defining Credentials for each method independently.

How to have a public APIG access a lambda inside a VPC?

I have a small web app deployed as an aws lambda that correctly operates when tested via the lambda management console. However running a test via the apig console yeilds this error:
Sun Jan 26 21:43:30 UTC 2020 : Execution failed due to configuration error: Invalid permissions on Lambda function
However the apig is provisioned with the execute permission:
apiGatewayRole:
DependsOn:
- squashApp
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "apigateway.amazonaws.com"
Action: "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
Policies:
- PolicyName: "API_Service_Role_Policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action: "lambda:InvokeFunction"
Resource: !GetAtt "squashApp.Arn"
Effect: "Allow"
lambdaApiGatewayInvoke:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt "squashApp.Arn"
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/*/"
Previously the permission was sufficient (The IAM policy is recently added) to permit the lambda invocation. The error started occuring after I moved the lambda inside a VPC.
squashApp:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
GO_ENV: production
DB_HOST: !GetAtt "squashRDS.Endpoint.Address"
DB_PORT: !GetAtt "squashRDS.Endpoint.Port"
DB_USER_NAME: !Sub "${rdsUser}"
DB_USER_PASS: !Sub "${rdsPassword}"
Code:
S3Bucket: '${lambdaHandoffBucket}'
S3Key: !Sub 'functions/${lambdaVersion}/${lambdaHash}' # TODO make parameter
Handler: 'app'
Role: !GetAtt "squashLambdaRole.Arn"
Runtime: 'go1.x'
Timeout: 2
FunctionName: !Ref "lambdaFunctionName"
VpcConfig:
SecurityGroupIds:
- !Ref "lambdaSecurtiyGroup"
SubnetIds:
- !Ref "privateSubnet1"
- !Ref "privateSubnet2"
vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.192.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
privateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref vpc
CidrBlock: 10.192.21.0/24
MapPublicIpOnLaunch: false
AvailabilityZone: !Ref "PrimaryAZ"
Tags:
- Key: Name
Value: !Sub ${apiGatewayStageName} Private Subnet (AZ1)
privateSubnet2:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref vpc
CidrBlock: 10.192.30.0/24
MapPublicIpOnLaunch: false
AvailabilityZone: !Ref "SecondaryAZ"
privateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
privateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref privateRouteTable1
SubnetId: !Ref privateSubnet1
privateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
privateSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref privateRouteTable1
SubnetId: !Ref privateSubnet1
noIngressSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "no-ingress-sg"
GroupDescription: "Security group with no ingress rule"
VpcId: !Ref vpc
The full cloud formation resource section:
Resources:
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "SquashAPI"
Description: "Squash Box Ladder"
apiGatewayRootMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
# This can be ANY | GET| POST | etc and references the api http method as seen by a client
HttpMethod: "ANY"
Integration:
# This must be post or the integration between gateway and lambda fails
IntegrationHttpMethod: "POST"
Type: "AWS_PROXY"
Uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
- lambdaArn: !GetAtt "squashApp.Arn"
ResourceId: !GetAtt "apiGateway.RootResourceId"
RestApiId: !Ref "apiGateway"
apiGatewayCatchAllProxy:
Type: AWS::ApiGateway::Resource
Properties:
PathPart: "{proxy+}"
RestApiId: !Ref "apiGateway"
ParentId: !GetAtt "apiGateway.RootResourceId"
apiGatewayCatchAllMethod:
Type: "AWS::ApiGateway::Method"
Properties:
AuthorizationType: "NONE"
HttpMethod: "ANY"
Integration:
# This must be post or the integration between gateway and lambda fails
IntegrationHttpMethod: "POST"
Type: "AWS_PROXY"
Uri: !Sub
- "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
- lambdaArn: !GetAtt "squashApp.Arn"
ResourceId: !Ref "apiGatewayCatchAllProxy"
RestApiId: !Ref "apiGateway"
apiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
DependsOn:
- "apiGatewayRootMethod"
- "apiGatewayCatchAllMethod"
Properties:
RestApiId: !Ref "apiGateway"
StageName: !Ref "apiGatewayStageName"
squashApp:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
GO_ENV: production
DB_HOST: !GetAtt "squashRDS.Endpoint.Address"
DB_PORT: !GetAtt "squashRDS.Endpoint.Port"
DB_USER_NAME: !Sub "${rdsUser}"
DB_USER_PASS: !Sub "${rdsPassword}"
Code:
S3Bucket: '${lambdaHandoffBucket}'
S3Key: !Sub 'functions/${lambdaVersion}/${lambdaHash}' # TODO make parameter
Handler: 'app'
Role: !GetAtt "squashLambdaRole.Arn"
Runtime: 'go1.x'
Timeout: 2
FunctionName: !Ref "lambdaFunctionName"
VpcConfig:
SecurityGroupIds:
- !Ref "lambdaSecurtiyGroup"
SubnetIds:
- !Ref "privateSubnet1"
- !Ref "privateSubnet2"
lambdaSecurtiyGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: For Lambda Application
VpcId: !Ref vpc
lambdaEgres:
Type: AWS::EC2::SecurityGroupEgress
Properties:
GroupId: !Ref lambdaSecurtiyGroup
IpProtocol: tcp
FromPort: !GetAtt "squashRDS.Endpoint.Port"
ToPort: !GetAtt "squashRDS.Endpoint.Port"
CidrIp: !GetAtt 'vpc.CidrBlock'
lambdaApiGatewayInvoke:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt "squashApp.Arn"
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/*/"
squashLambdaRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "sts:AssumeRole"
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
Policies:
- PolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Effect: "Allow"
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${lambdaFunctionName}:*"
PolicyName: "lambda"
lambdaLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/aws/lambda/${lambdaFunctionName}"
RetentionInDays: 14
apiGatewayRole:
DependsOn:
- squashApp
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "apigateway.amazonaws.com"
Action: "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
Policies:
- PolicyName: "API_Service_Role_Policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action: "lambda:InvokeFunction"
Resource: !GetAtt "squashApp.Arn"
Effect: "Allow"
apiGwAccountConfig:
Type: "AWS::ApiGateway::Account"
Properties:
CloudWatchRoleArn: !GetAtt "apiGatewayRole.Arn"
squashRDS:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: db.t2.micro
AllocatedStorage: 20
StorageType: "gp2"
Engine: "postgres"
MasterUsername: !Sub "${rdsUser}"
MasterUserPassword: !Sub "${rdsPassword}"
DBSubnetGroupName: !Ref "rdsSubnetGroup"
VPCSecurityGroups:
- !Ref rdsSecurityGroup
DeletionPolicy: Snapshot
rdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: For RDS Instance
VpcId: !Ref vpc
Tags:
- Key: Name
Value: RDS-SecurityGroup
rdsSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: "RDS Subnet Group"
DBSubnetGroupDescription: Subnet including the RDS into the VPC
SubnetIds:
- !Ref "privateSubnet1"
- !Ref "privateSubnet2"
rdsIngres:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref rdsSecurityGroup
IpProtocol: tcp
FromPort: !GetAtt "squashRDS.Endpoint.Port"
ToPort: !GetAtt "squashRDS.Endpoint.Port"
CidrIp: !GetAtt 'vpc.CidrBlock'
vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.192.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
privateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref vpc
CidrBlock: 10.192.21.0/24
MapPublicIpOnLaunch: false
AvailabilityZone: !Ref "PrimaryAZ"
privateSubnet2:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref vpc
CidrBlock: 10.192.30.0/24
MapPublicIpOnLaunch: false
AvailabilityZone: !Ref "SecondaryAZ"
privateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
privateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref privateRouteTable1
SubnetId: !Ref privateSubnet1
privateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
privateSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref privateRouteTable1
SubnetId: !Ref privateSubnet1
noIngressSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "no-ingress-sg"
GroupDescription: "Security group with no ingress rule"
VpcId: !Ref vpc
Outputs:
apiGatewayInvokeURL:
Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}"
lambdaArn:
Value: !GetAtt "squashApp.Arn"
In "squashLambdaRole", try adding "apigateway.amazonaws.com" in service principal list + attach a AWS managed policy named "AWSLambdaRole".
It solved my problem but I did not understand why :/
The only thing I can think of is, the API gateway source Arn should end with a resource path. In your case, it ends with a /.
I would add an * at the end to allow any resource path to invoke the lambda. You can change it to a specific path once you get it working.
lambdaApiGatewayInvoke:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt "squashApp.Arn"
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/*/*"
hope this helps. good luck.

Resources