I am using CloudFormation to attempt to set up "stage" and "prod" API Gateway stages with Lambda integration (one lambda per stage to simplify deployment) to URLs as follows:
- stage: https://<api_id>.execute-api.us-east-1.amazonaws.com/stage/mfl-scoring
- prod: https://<api_id>.execute-api.us-east-1.amazonaws.com/prod/mfl-scoring
The relevant parts of my CF template are as follows:
MflScoringStageFunction:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt MflScoringStagedFunctionIamRole.Arn
Code:
S3Bucket: !Sub ${BuildBucket}
S3Key: builds/bootstrap.zip
# S3ObjectVersion: TODO
Handler: bootstrap
Runtime: provided.al2
MemorySize: 128
Timeout: 5
MflScoringProdFunction:
Type: AWS::Lambda::Function
Properties:
Role: !GetAtt MflScoringStagedFunctionIamRole.Arn
Code:
S3Bucket: !Sub ${BuildBucket}
S3Key: builds/bootstrap.zip
# S3ObjectVersion: TODO
Handler: bootstrap
Runtime: provided.al2
MemorySize: 128
Timeout: 5
MflScoringFunctionStageAlias:
Type: AWS::Lambda::Alias
Properties:
FunctionName:
Ref: MflScoringStageFunction
FunctionVersion: "$LATEST"
Name: STAGE
MflScoringFunctionProdAlias:
Type: AWS::Lambda::Alias
Properties:
FunctionName:
Ref: MflScoringProdFunction
FunctionVersion: "$LATEST"
Name: PROD
MflScoringFunctionStagePermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !Ref MflScoringFunctionStageAlias
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:MflScoringApi/*"
MflScoringFunctionProdPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !Ref MflScoringFunctionProdAlias
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:MflScoringApi/*"
MflScoringStageFunctionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt MflScoringStageFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn:
!Sub
- "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${MflScoringApi}/*"
- MflScoringApi: !Ref MflScoringApi
MflScoringProdFunctionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt MflScoringProdFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn:
!Sub
- "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${MflScoringApi}/*"
- MflScoringApi: !Ref MflScoringApi
MflScoringApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: MFL Scoring API
ProtocolType: HTTP
MflScoringIntegration:
Type: AWS::ApiGatewayV2::Integration
DependsOn:
- MflScoringStageFunction
- MflScoringProdFunction
- MflScoringApiStageStage
- MflScoringApiStageProd
Properties:
ApiId: !Ref MflScoringApi
Description: Lambda proxy integration
IntegrationType: AWS_PROXY
IntegrationMethod: POST
PayloadFormatVersion: "2.0"
IntegrationUri: !Sub 'arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${!stageVariables.LambdaFunction}:${!stageVariables.LambdaAlias}/invocations'
MflScoringApiRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref MflScoringApi
RouteKey: "GET /mfl-scoring"
Target: !Join
- /
- - integrations
- !Ref MflScoringIntegration
MflScoringApiStageStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref MflScoringApi
DefaultRouteSettings:
DetailedMetricsEnabled: True
StageName: stage
StageVariables:
LambdaAlias: STAGE
LambdaFunction: !GetAtt MflScoringStageFunction.Arn
AutoDeploy: true
MflScoringApiStageProd:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref MflScoringApi
DefaultRouteSettings:
DetailedMetricsEnabled: True
StageName: prod
StageVariables:
LambdaAlias: PROD
LambdaFunction: !GetAtt MflScoringProdFunction.Arn
AutoDeploy: true
Stage variables appear in the console as follows:
Stage "stage":
- Key | Value
- LambdaAlias | STAGE
- LambdaFunction | arn:aws:lambda:us-east-1:123412341234:function:mfl-scoring-http-MflScoringStageFunction-cgdT1EMOrMbd
Stage "prod":
- Key | Value
- LambdaAlias | PROD
- LambdaFunction | arn:aws:lambda:us-east-1:123412341234:function:mfl-scoring-http-MflScoringProdFunction-vZajLTdguZE7
The CloudTrail record for the failure is as follows:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "xxxx",
"arn": "arn:aws:iam::xxxx:user/sam-user",
"accountId": "xxxx",
"accessKeyId": "xxxx",
"userName": "sam-user",
"sessionContext": {
"sessionIssuer": {},
"webIdFederationData": {},
"attributes": {
"creationDate": "2023-10-17T19:51:06Z",
"mfaAuthenticated": "false"
}
},
"invokedBy": "cloudformation.amazonaws.com"
},
"eventTime": "2023-10-17T19:51:11Z",
"eventSource": "apigateway.amazonaws.com",
"eventName": "UpdateIntegration",
"awsRegion": "us-east-1",
"sourceIPAddress": "cloudformation.amazonaws.com",
"userAgent": "cloudformation.amazonaws.com",
"errorCode": "BadRequestException",
"requestParameters": {
"integrationMethod": "POST",
"requestTemplates": {},
"integrationType": "AWS_PROXY",
"requestParameters": {},
"integrationId": "xxxx",
"description": "Lambda proxy integration",
"payloadFormatVersion": "2.0",
"integrationUri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${stageVariables.LambdaFunction}:${stageVariables.LambdaAlias}/invocations",
"apiId": "xxxx"
},
"responseElements": {
"message": "Invalid lambda function"
},
"requestID": "05af4104-df20-4916-a615-358084301c05",
"eventID": "61184f3d-c578-45f9-b4b7-2ac71d8b5f8f",
"readOnly": false,
"eventType": "AwsApiCall",
"managementEvent": true,
"recipientAccountId": "xxxx",
"eventCategory": "Management"
}
The error in the CloudFormation console is:
Invalid lambda function (Service: AmazonApiGatewayV2; Status Code: 400; Error Code: BadRequestException; Request ID: fa527627-91b7-4e0e-900a-2ca4ae50e9f7; Proxy: null)
What do I need to do to make ApiGatewayV2 recognize that IntegrationURI as legitimate per the AWS docs (https://docs.aws.amazon.com/apigateway/latest/developerguide/aws-api-gateway-stage-variables-reference.html#stage-variables-in-integration-lambda-functions)?