From dd014fc46e778d8b5fd1963ffbdaa64a9822737f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 16 Jan 2020 17:42:48 +0000 Subject: [PATCH] fix(apigateway): LambdaRestApi fails when a user defined Stage is attached LambdaRestApi, by default, sets up a proxy integration as part of its construction which in turn binds the specified lambda function handler to the proxy using the LambdaIntegration class. The LambdaIntegration class needs to set up the right IAM permissions so API Gateway is able to invoke the Lambda function. The policy for this permission requires the ARN of the 'ANY' Method that is set up by the Proxy. When the 'deploy' option is set, it indicates to the RestApi construct and its sub-constructs (of which LambdaRestApi is one) to create a default deployment and stage as part of its construction. The user can also unset the 'deploy' option and specify their own Stage by setting the 'deploymentStage' on the RestApi construct. However, when the 'deploy' option is unset, the LambdaIntegration class cannot compute the ARN for the 'ANY' Method since there's no Stage and Deployment created. This generates at synthesis, even though there may be a Stage that the user has configured against the RestApi later on. The fix here is straightforward. Computation of the Method ARN should occur at the time of synthesis rather than at the time of construction. fixes #5744 --- .../aws-apigateway/lib/integrations/lambda.ts | 3 +- ...pi.latebound-deploymentstage.expected.json | 379 ++++++++++++++++++ ...eg.lambda-api.latebound-deploymentstage.ts | 29 ++ 3 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json create mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 664a2c6e08b6d..058023d4c909f 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; +import { Lazy } from '@aws-cdk/core'; import { IntegrationOptions } from '../integration'; import { Method } from '../method'; import { AwsIntegration } from './aws'; @@ -60,7 +61,7 @@ export class LambdaIntegration extends AwsIntegration { this.handler.addPermission(`ApiPermission.${desc}`, { principal, scope: method, - sourceArn: method.methodArn, + sourceArn: Lazy.stringValue({ produce: () => method.methodArn }), }); // add permission to invoke from the console diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json new file mode 100644 index 0000000000000..9a58991757b82 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.expected.json @@ -0,0 +1,379 @@ +{ + "Resources": { + "myfnServiceRole7822DC24": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "myfn8C66D016": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "foo" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "myfnServiceRole7822DC24", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "myfnServiceRole7822DC24" + ] + }, + "lambdarestapiF559E4F2": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "lambdarestapi" + } + }, + "lambdarestapiCloudWatchRoleA142878F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "lambdarestapiAccount856938D8": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "lambdarestapiCloudWatchRoleA142878F", + "Arn" + ] + } + }, + "DependsOn": [ + "lambdarestapiF559E4F2" + ] + }, + "lambdarestapiproxyB0E963B7": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "lambdarestapiF559E4F2", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "lambdarestapiF559E4F2" + } + } + }, + "lambdarestapiproxyANYApiPermissionLateBoundDeploymentStageStacklambdarestapiCE6017F6ANYproxy2C5460ED": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "lambdarestapiF559E4F2" + }, + "/", + { + "Ref": "stage0661E4AC" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "lambdarestapiproxyANYApiPermissionTestLateBoundDeploymentStageStacklambdarestapiCE6017F6ANYproxyCC4F6BB2": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "lambdarestapiF559E4F2" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "lambdarestapiproxyANYC900233F": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "lambdarestapiproxyB0E963B7" + }, + "RestApiId": { + "Ref": "lambdarestapiF559E4F2" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "lambdarestapiANYApiPermissionLateBoundDeploymentStageStacklambdarestapiCE6017F6ANY35688E13": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "lambdarestapiF559E4F2" + }, + "/", + { + "Ref": "stage0661E4AC" + }, + "/*/" + ] + ] + } + } + }, + "lambdarestapiANYApiPermissionTestLateBoundDeploymentStageStacklambdarestapiCE6017F6ANY239CFD70": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "lambdarestapiF559E4F2" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "lambdarestapiANYB9BB3FB2": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "lambdarestapiF559E4F2", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "lambdarestapiF559E4F2" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "myfn8C66D016", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "deployment33381975": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "lambdarestapiF559E4F2" + } + } + }, + "stage0661E4AC": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "lambdarestapiF559E4F2" + }, + "DeploymentId": { + "Ref": "deployment33381975" + }, + "StageName": "prod" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts new file mode 100644 index 0000000000000..53e44bbafb33e --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.lambda-api.latebound-deploymentstage.ts @@ -0,0 +1,29 @@ +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import { App, Construct, Stack } from '@aws-cdk/core'; +import { Deployment, LambdaRestApi, Stage } from '../lib'; + +class LateBoundDeploymentStageStack extends Stack { + constructor(scope: Construct) { + super(scope, 'LateBoundDeploymentStageStack'); + + const fn = new Function(this, 'myfn', { + code: Code.fromInline('foo'), + runtime: Runtime.NODEJS_10_X, + handler: 'index.handler' + }); + + const api = new LambdaRestApi(this, 'lambdarestapi', { + deploy: false, + handler: fn, + }); + + api.deploymentStage = new Stage(this, 'stage', { + deployment: new Deployment(this, 'deployment', { + api + }) + }); + } +} + +const app = new App(); +new LateBoundDeploymentStageStack(app); \ No newline at end of file