Skip to content

Commit 1e352ca

Browse files
authored
fix(apigatewayv2-integrations): in case of multiple routes, only one execute permission is created (#18716)
When multiple routes are defined for a single lambda integration, only one of the routes gets permission to execute the function. This is because the permissions are added when the integration is bound to the route, which happens only once per integration. Split the `_bindToRoute` workflow into two parts: 1. The actual bind, followed by the creation of an `HttpIntegration`. We keep doing this only once per integration. 2. A post-bind step, that happens for every route. In the case of `HttpLambdaIntegration`, adding the permission has been moved to the post bind step. All other integrations remain the same. Fixes #18201. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 05f8b6f commit 1e352ca

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class HttpLambdaIntegration extends HttpRouteIntegration {
5050
this._id = id;
5151
}
5252

53-
public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {
53+
protected completeBind(options: HttpRouteIntegrationBindOptions) {
5454
const route = options.route;
5555
this.handler.addPermission(`${this._id}-Permission`, {
5656
scope: options.scope,
@@ -61,7 +61,9 @@ export class HttpLambdaIntegration extends HttpRouteIntegration {
6161
resourceName: `*/*${route.path ?? ''}`, // empty string in the case of the catch-all route $default
6262
}),
6363
});
64+
}
6465

66+
public bind(_: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {
6567
return {
6668
type: HttpIntegrationType.AWS_PROXY,
6769
uri: this.handler.functionArn,

packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Template } from '@aws-cdk/assertions';
1+
import { Match, Template } from '@aws-cdk/assertions';
22
import { HttpApi, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
33
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';
44
import { App, Stack } from '@aws-cdk/core';
@@ -71,6 +71,41 @@ describe('LambdaProxyIntegration', () => {
7171

7272
expect(() => app.synth()).not.toThrow();
7373
});
74+
75+
test('multiple routes for the same lambda integration', () => {
76+
const app = new App();
77+
const lambdaStack = new Stack(app, 'lambdaStack');
78+
const fooFn = fooFunction(lambdaStack, 'Fn');
79+
80+
const stack = new Stack(app, 'apigwStack');
81+
const api = new HttpApi(stack, 'httpApi');
82+
const integration = new HttpLambdaIntegration('Integration', fooFn);
83+
84+
api.addRoutes({
85+
path: '/foo',
86+
integration,
87+
});
88+
89+
api.addRoutes({
90+
path: '/bar',
91+
integration,
92+
});
93+
94+
// Make sure we have two permissions -- one for each method -- but a single integration
95+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', {
96+
SourceArn: {
97+
'Fn::Join': ['', Match.arrayWith([':execute-api:', '/*/*/foo'])],
98+
},
99+
});
100+
101+
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Permission', {
102+
SourceArn: {
103+
'Fn::Join': ['', Match.arrayWith([':execute-api:', '/*/*/bar'])],
104+
},
105+
});
106+
107+
Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::Integration', 1);
108+
});
74109
});
75110

76111
function fooFunction(stack: Stack, id: string) {

packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,22 @@ export abstract class HttpRouteIntegration {
332332
credentials: config.credentials,
333333
});
334334
}
335+
this.completeBind(options);
335336
return { integrationId: this.integration.integrationId };
336337
}
337338

339+
/**
340+
* Complete the binding of the integration to the route. In some cases, there is
341+
* some additional work to do, such as adding permissions for the API to access
342+
* the target. This work is necessary whether the integration has just been
343+
* created for this route or it is an existing one, previously created for other
344+
* routes. In most cases, however, concrete implementations do not need to
345+
* override this method.
346+
*/
347+
protected completeBind(_options: HttpRouteIntegrationBindOptions): void {
348+
// no-op by default
349+
}
350+
338351
/**
339352
* Bind this integration to the route.
340353
*/

0 commit comments

Comments
 (0)