'ApiGatewayV2 how to set the "Execution Role" via CloudFormation
I'm trying to setup websockets via API Gateway. When I try to make a connection I get the error Execution failed due to configuration error: Invalid permissions on Lambda function in my CloudWatch logs.
I have created AWS::IAM::Role resource in my CloudFormation and attached it as the Role in my AWS::Lambda::Function resource. But I think I need to attach it to the AWS::ApiGatewayV2::Integration but I don't see how.
When I look in the AWS Console, I see there is no Execution Role selected:
But I can't see how to add the role in CloudFormation. What am I missing?
Full CloudFormation:
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Website S3 Hosted, API Gateway Backend
Parameters:
DomainName:
Type: String
Description: The DNS name of an Amazon Route 53 hosted zone e.g. server.com
AllowedPattern: '(?!-)[a-zA-Z0-9-.]{1,63}(?<!-)'
ConstraintDescription: must be a valid DNS zone name.
DiscordToken:
Type: String
DiscordChannelID:
Type: String
HostedZoneId:
Type: String
SSLARN:
Description: Cloudfront SSL ARN (us-east-1)
Type: String
Mappings:
APIRegionMap:
us-east-1:
APIHostedZoneId: Z1UJRXOUMOOFQ8
us-east-2:
APIHostedZoneId: ZOJJZC49E0EPZ
us-west-1:
APIHostedZoneId: Z2MUQ32089INYE
us-west-2:
APIHostedZoneId: Z2OJLYMUO9EFXC
Resources:
# SSL
SSL:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
DomainValidationOptions:
- DomainName: !Join ['.', ['api', !Ref DomainName]]
HostedZoneId: !Ref HostedZoneId
SubjectAlternativeNames:
- !Join ['.', ['*', !Ref DomainName]]
ValidationMethod: DNS
# DynamoDB Tables
SocketTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
-
AttributeName: connectionId
AttributeType: S
KeySchema:
-
AttributeName: connectionId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
TableName: clients
# Lambda Items
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: '/'
Policies:
- PolicyName: execution
PolicyDocument:
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:ListBucket
Resource: '*'
Resource: '*'
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: '*'
- Effect: Allow
Action:
- execute-api:Invoke
- execute-api:ManageConnections
Resource: '*'
LambdaFunctionSocketServer:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: exports.handler = function (event, context, callback) { callback(null, event); };
Handler: index.handler
MemorySize: 1024
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: nodejs12.x
Timeout: 900
Environment:
Variables:
DBTable: !Ref SocketTable
DiscordToken: !Ref DiscordToken
DiscordChannelID: !Ref DiscordChannelID
# API Gateway Items
WebSocketLoggingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action:
- sts:AssumeRole
Path: '/'
Policies:
- PolicyName: execution
PolicyDocument:
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:DescribeLogGroups
- logs:DescribeLogStreams
- logs:PutLogEvents
- logs:GetLogEvents
- logs:FilterLogEvents
Resource: '*'
WebSocketAPI:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: !Join ['-', !Split ['.', !Join ['.', ['ws', !Ref DomainName]]]]
ProtocolType: WEBSOCKET
RouteSelectionExpression: "$request.body.action"
WebSocketInteg:
Type: AWS::ApiGatewayV2::Integration
DependsOn:
- LambdaFunctionSocketServer
Properties:
ApiId: !Ref WebSocketAPI
Description: WebSocket Integration
IntegrationType: AWS_PROXY
IntegrationUri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunctionSocketServer.Arn}/invocations'
WebSocketConnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketAPI
RouteKey: $connect
AuthorizationType: NONE
OperationName: ConnectRoute
Target: !Join ['/', ['integrations', !Ref WebSocketInteg]]
WebSocketDisconnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketAPI
RouteKey: $disconnect
AuthorizationType: NONE
OperationName: DisconnectRoute
Target: !Join ['/', ['integrations', !Ref WebSocketInteg]]
WebSocketSendRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref WebSocketAPI
RouteKey: sendmessage
AuthorizationType: NONE
OperationName: SendRoute
Target: !Join ['/', ['integrations', !Ref WebSocketInteg]]
WebSocketDeployment:
Type: AWS::ApiGatewayV2::Deployment
DependsOn:
- WebSocketConnectRoute
- WebSocketSendRoute
- WebSocketDisconnectRoute
Properties:
ApiId: !Ref WebSocketAPI
WebSocketLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Join ['.', ['ws', !Ref DomainName]]
RetentionInDays: 3
WebSocketStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: Prod
Description: Prod Stage
DeploymentId: !Ref WebSocketDeployment
ApiId: !Ref WebSocketAPI
WebSocketDomain:
Type: 'AWS::ApiGatewayV2::DomainName'
Properties:
DomainName: !Join ['.', ['ws', !Ref DomainName]]
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: !Ref SSL
WebSocketMapping:
Type: 'AWS::ApiGatewayV2::ApiMapping'
DependsOn:
- WebSocketDeployment
Properties:
DomainName: !Ref WebSocketDomain
ApiId: !Ref WebSocketAPI
Stage: !Ref WebSocketStage
# Website items
WebsiteBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
DependsOn:
- WebSocketDeployment
Properties:
BucketName:
Ref: DomainName
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: 404.html
Tags:
- Key: Name
Value: !Join ['_', ['WebsiteBucket', !Ref 'AWS::StackName']]
- Key: Domain
Value: !Ref DomainName
DeletionPolicy: Retain
WWWBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
DependsOn:
- WebSocketDeployment
Properties:
BucketName: !Join ['.', ['www', !Ref DomainName]]
AccessControl: PublicRead
WebsiteConfiguration:
RedirectAllRequestsTo:
HostName: !Ref WebsiteBucket
Tags:
- Key: Name
Value: !Join ['_', ['WWWBucket', !Ref 'AWS::StackName']]
- Key: Domain
Value: !Ref DomainName
ImageBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
DependsOn:
- WebSocketDeployment
Properties:
BucketName: !Join ['.', ['images', !Ref DomainName]]
AccessControl: Private
Tags:
- Key: Name
Value: !Join ['_', ['ImageBucket', !Ref 'AWS::StackName']]
- Key: Domain
Value: !Ref DomainName
DeletionPolicy: Retain
WebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebsiteBucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Join ['', ['arn:aws:s3:::', !Ref WebsiteBucket, '/*']]
Principal: '*'
WWWBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WWWBucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Join ['', ['arn:aws:s3:::', !Ref WWWBucket, '/*']]
Principal: '*'
WebsiteCloudfront:
Type: AWS::CloudFront::Distribution
DependsOn:
- WebsiteBucket
Properties:
DistributionConfig:
Comment: !Ref DomainName
Origins:
- DomainName: !Select [2, !Split ["/", !GetAtt WebsiteBucket.WebsiteURL]]
Id: S3Origin
CustomOriginConfig:
HTTPPort: '80'
HTTPSPort: '443'
OriginProtocolPolicy: http-only
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: index.html
Aliases:
- !Ref DomainName
- !Join ['.', ['www', !Ref DomainName]]
DefaultCacheBehavior:
DefaultTTL: 60
MaxTTL: 60
MinTTL: 60
AllowedMethods:
- GET
- HEAD
Compress: true
TargetOriginId: S3Origin
ForwardedValues:
QueryString: true
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_All
ViewerCertificate:
AcmCertificateArn: !Ref SSLARN
SslSupportMethod: sni-only
CustomErrorResponses:
- ErrorCode: 404
ResponseCode: 200
ResponsePagePath: /index.html
CloudfrontDNSRecord:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName:
Fn::Join: ['', [!Ref DomainName, '.']]
Comment: Cloudfront zone records.
RecordSets:
- Name: !Ref DomainName
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2
DNSName: !GetAtt [WebsiteCloudfront, DomainName]
- Name: !Join ['.', ['www', !Ref DomainName]]
Type: A
AliasTarget:
HostedZoneId: Z2FDTNDATAQYW2
DNSName: !GetAtt [WebsiteCloudfront, DomainName]
WebsocketDNSRecord:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName:
Fn::Join: ['', [!Ref DomainName, '.']]
Comment: Websocket zone records.
RecordSets:
- Name: !Join ['.', ['ws', !Ref DomainName]]
Type: A
AliasTarget:
HostedZoneId:
Fn::FindInMap:
- APIRegionMap
- Ref: AWS::Region
- APIHostedZoneId
DNSName: !GetAtt [WebSocketDomain, RegionalDomainName]
Outputs:
S3WebsiteURL:
Value: !GetAtt WebsiteBucket.WebsiteURL
Description: URL for website hosted on S3
Solution 1:[1]
I think you just need to add an AWS::Lambda::Permission to allow the gateway to invoke the function.
OnConnectPermission:
Type: AWS::Lambda::Permission
DependsOn:
- WebSocketAPI
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LambdaFunctionSocketServer
Principal: apigateway.amazonaws.com
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | b3n |

