Skip to content

Commit

Permalink
fix(apigateway): permission error in lambda integration when function…
Browse files Browse the repository at this point in the history
… name is modified

Changing function name triggers a resource replacement - the old
function is removed and replaced with a new function.
However, the RestApi deployment remains untouched and is still pointing
at the ARN of the, now stale, function.

The fix is to trigger a new deployment if the function name changes.

fixes #5306
  • Loading branch information
Niranjan Jayakar committed Jun 30, 2020
1 parent c085bd7 commit 604b344
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 24 deletions.
16 changes: 14 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/integration.ts
Expand Up @@ -134,6 +134,18 @@ export interface IntegrationProps {
readonly options?: IntegrationOptions;
}

/**
* Result of binding an Integration to the Method
*/
export interface IntegrationConfig {
/**
* Triggers a new Deployment if the value of this property changes.
* Use this property to trigger new deployments for changes to values in this integration.
* @default - deployments are not triggered for any change of this integration.
*/
readonly triggerDeployment?: any;
}

/**
* Base class for backend integrations for an API Gateway method.
*
Expand All @@ -156,8 +168,8 @@ export class Integration {
* Can be overridden by subclasses to allow the integration to interact with the method
* being integrated, access the REST API object, method ARNs, etc.
*/
public bind(_method: Method) {
return;
public bind(_method: Method): IntegrationConfig {
return {};
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts
@@ -1,5 +1,5 @@
import * as cdk from '@aws-cdk/core';
import { Integration, IntegrationOptions, IntegrationType } from '../integration';
import { Integration, IntegrationConfig, IntegrationOptions, IntegrationType } from '../integration';
import { Method } from '../method';
import { parseAwsApiCall } from '../util';

Expand Down Expand Up @@ -92,7 +92,8 @@ export class AwsIntegration extends Integration {
});
}

public bind(method: Method) {
public bind(method: Method): IntegrationConfig {
this.scope = method;
return {};
}
}
15 changes: 12 additions & 3 deletions packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts
@@ -1,7 +1,7 @@
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 { Lazy, Token } from '@aws-cdk/core';
import { IntegrationConfig, IntegrationOptions } from '../integration';
import { Method } from '../method';
import { AwsIntegration } from './aws';

Expand Down Expand Up @@ -52,7 +52,7 @@ export class LambdaIntegration extends AwsIntegration {
this.enableTest = options.allowTestInvoke === undefined ? true : false;
}

public bind(method: Method) {
public bind(method: Method): IntegrationConfig {
super.bind(method);
const principal = new iam.ServicePrincipal('apigateway.amazonaws.com');

Expand All @@ -72,5 +72,14 @@ export class LambdaIntegration extends AwsIntegration {
sourceArn: method.testMethodArn,
});
}

const cfnFunction = this.handler.node.defaultChild as lambda.CfnFunction;
let triggerDeployment;
if (!Token.isUnresolved(cfnFunction.functionName)) {
triggerDeployment = { functionName: cfnFunction.functionName };
}
return {
triggerDeployment,
};
}
}
26 changes: 11 additions & 15 deletions packages/@aws-cdk/aws-apigateway/lib/method.ts
Expand Up @@ -192,6 +192,9 @@ export class Method extends Resource {
authorizer._attachToApi(this.api);
}

const integration = props.integration ?? this.resource.defaultIntegration ?? new MockIntegration();
const bindResult = integration.bind(this);

const methodProps: CfnMethodProps = {
resourceId: props.resource.resourceId,
restApiId: this.api.restApiId,
Expand All @@ -201,7 +204,7 @@ export class Method extends Resource {
authorizationType,
authorizerId,
requestParameters: options.requestParameters || defaultMethodOptions.requestParameters,
integration: this.renderIntegration(props.integration),
integration: this.renderIntegration(integration),
methodResponses: this.renderMethodResponses(options.methodResponses),
requestModels: this.renderRequestModels(options.requestModels),
requestValidatorId: this.requestValidatorId(options),
Expand All @@ -219,7 +222,12 @@ export class Method extends Resource {
const deployment = props.resource.api.latestDeployment;
if (deployment) {
deployment.node.addDependency(resource);
deployment.addToLogicalId({ method: methodProps });
deployment.addToLogicalId({
method: {
...methodProps,
...bindResult?.triggerDeployment,
},
});
}
}

Expand Down Expand Up @@ -255,19 +263,7 @@ export class Method extends Resource {
return this.api.arnForExecuteApi(this.httpMethod, pathForArn(this.resource.path), 'test-invoke-stage');
}

private renderIntegration(integration?: Integration): CfnMethod.IntegrationProperty {
if (!integration) {
// use defaultIntegration from API if defined
if (this.resource.defaultIntegration) {
return this.renderIntegration(this.resource.defaultIntegration);
}

// fallback to mock
return this.renderIntegration(new MockIntegration());
}

integration.bind(this);

private renderIntegration(integration: Integration): CfnMethod.IntegrationProperty {
const options = integration._props.options || { };

let credentials;
Expand Down
Expand Up @@ -120,7 +120,7 @@
"BooksApi60AC975F"
]
},
"BooksApiDeployment86CA39AF7e6c771d47a1a3777eba99bffc037822": {
"BooksApiDeployment86CA39AFdfee6753c946e6cbf8b3d649e0cf1377": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
Expand All @@ -141,7 +141,7 @@
"Ref": "BooksApi60AC975F"
},
"DeploymentId": {
"Ref": "BooksApiDeployment86CA39AF7e6c771d47a1a3777eba99bffc037822"
"Ref": "BooksApiDeployment86CA39AFdfee6753c946e6cbf8b3d649e0cf1377"
},
"StageName": "prod"
}
Expand Down

0 comments on commit 604b344

Please sign in to comment.