@aws/nx-plugin-mcp 0.120.0 → 0.121.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aws-nx-mcp.js +23 -35
- package/docs/guides/astro-docs.mdx +142 -0
- package/docs/guides/connection/py-agent-a2a.mdx +105 -0
- package/docs/guides/connection/py-agent-mcp.mdx +147 -0
- package/docs/guides/connection/react-agui.mdx +240 -0
- package/docs/guides/connection/react-fastapi.mdx +1476 -0
- package/docs/guides/connection/react-py-agent.mdx +198 -0
- package/docs/guides/connection/react-smithy.mdx +1355 -0
- package/docs/guides/connection/react-trpc.mdx +447 -0
- package/docs/guides/connection/react-ts-agent.mdx +198 -0
- package/docs/guides/connection/smithy-rdb.mdx +161 -0
- package/docs/guides/connection/trpc-rdb.mdx +127 -0
- package/docs/guides/connection/ts-agent-a2a.mdx +106 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +144 -0
- package/docs/guides/connection/ts-agent-rdb.mdx +141 -0
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +135 -0
- package/docs/guides/connection.mdx +126 -0
- package/docs/guides/docker-bundling.mdx +403 -0
- package/docs/guides/fastapi.mdx +509 -0
- package/docs/guides/license.mdx +293 -0
- package/docs/guides/nx-generator.mdx +588 -0
- package/docs/guides/py-agent.mdx +483 -0
- package/docs/guides/py-mcp-server.mdx +161 -0
- package/docs/guides/python-lambda-function.mdx +207 -0
- package/docs/guides/python-project.mdx +228 -0
- package/docs/guides/react-website-auth.mdx +216 -0
- package/docs/guides/react-website.mdx +421 -0
- package/docs/guides/runtime-config.mdx +312 -0
- package/docs/guides/terraform-project.mdx +317 -0
- package/docs/guides/trpc.mdx +816 -0
- package/docs/guides/ts-agent.mdx +436 -0
- package/docs/guides/ts-lambda-function.mdx +218 -0
- package/docs/guides/ts-mcp-server.mdx +169 -0
- package/docs/guides/ts-nx-plugin.mdx +159 -0
- package/docs/guides/ts-rdb.mdx +759 -0
- package/docs/guides/ts-smithy-api.mdx +661 -0
- package/docs/guides/typescript-infrastructure.mdx +408 -0
- package/docs/guides/typescript-project.mdx +312 -0
- package/docs/guides/workspace.mdx +181 -0
- package/docs/snippets/agent/architecture.mdx +72 -0
- package/docs/snippets/agent/bedrock-deployment.mdx +172 -0
- package/docs/snippets/agent/runtime-arn.mdx +64 -0
- package/docs/snippets/api/api-architecture.mdx +93 -0
- package/docs/snippets/api/api-choice-note.mdx +6 -0
- package/docs/snippets/api/cors-configuration-cdk-note.mdx +25 -0
- package/docs/snippets/api/cors-configuration-terraform-note.mdx +28 -0
- package/docs/snippets/api/shared-constructs.mdx +38 -0
- package/docs/snippets/api/type-safe-api-integrations.mdx +643 -0
- package/docs/snippets/api/waf-configuration.mdx +37 -0
- package/docs/snippets/connection/a2a-infrastructure.mdx +63 -0
- package/docs/snippets/connection/lambda-rdb-ssl-requirements.mdx +40 -0
- package/docs/snippets/connection/mcp-server-rdb-ssl-requirements.mdx +35 -0
- package/docs/snippets/connection/rdb-api-infrastructure.mdx +72 -0
- package/docs/snippets/connection/react-agent-infrastructure.mdx +61 -0
- package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +35 -0
- package/docs/snippets/lambda-function/architecture.mdx +36 -0
- package/docs/snippets/lambda-function/deploying-your-function.mdx +118 -0
- package/docs/snippets/mcp/architecture.mdx +58 -0
- package/docs/snippets/mcp/assistant-docs.mdx +10 -0
- package/docs/snippets/mcp/bedrock-deployment.mdx +167 -0
- package/docs/snippets/mcp/config.mdx +13 -0
- package/docs/snippets/mcp/configuration-py.mdx +42 -0
- package/docs/snippets/mcp/configuration-ts.mdx +53 -0
- package/docs/snippets/mcp/observability.mdx +8 -0
- package/docs/snippets/mcp/shared-constructs.mdx +32 -0
- package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +602 -0
- package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +915 -0
- package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +161 -0
- package/docs/snippets/pdk-migration/example/04-deploy.mdx +229 -0
- package/docs/snippets/pdk-migration/faq/aws-arch.mdx +17 -0
- package/docs/snippets/pdk-migration/faq/cdk-graph.mdx +29 -0
- package/docs/snippets/pdk-migration/faq/infrastructure-python-java.mdx +19 -0
- package/docs/snippets/pdk-migration/faq/pdk-nag.mdx +15 -0
- package/docs/snippets/pdk-migration/faq/pipeline.mdx +15 -0
- package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +310 -0
- package/docs/snippets/pdk-migration/faq/use-of-projen.mdx +15 -0
- package/docs/snippets/prerequisites.mdx +20 -0
- package/docs/snippets/required-prerequisites.mdx +12 -0
- package/docs/snippets/shared-constructs.mdx +40 -0
- package/docs/snippets/tools/acurl.mdx +73 -0
- package/docs/snippets/ts-bundle.mdx +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Type-Safe API Integrations
|
|
3
|
+
---
|
|
4
|
+
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
|
5
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
6
|
+
|
|
7
|
+
The REST/HTTP API CDK constructs are configured to provide a type-safe interface for defining integrations for each of your operations.
|
|
8
|
+
|
|
9
|
+
<Infrastructure>
|
|
10
|
+
<Fragment slot="cdk">
|
|
11
|
+
The CDK constructs provide full type-safe integration support as described below.
|
|
12
|
+
</Fragment>
|
|
13
|
+
<Fragment slot="terraform">
|
|
14
|
+
:::note[Router Pattern]
|
|
15
|
+
Terraform modules use the "router pattern" with a single Lambda function serving all operations. Type-safe integrations are not supported - the module creates one Lambda function that handles all API requests.
|
|
16
|
+
|
|
17
|
+
For explicit per-operation integrations with Terraform, you would need to manually create individual Lambda functions and API Gateway routes. See the [Explicit Integrations](#explicit-integrations) section for examples.
|
|
18
|
+
:::
|
|
19
|
+
</Fragment>
|
|
20
|
+
</Infrastructure>
|
|
21
|
+
|
|
22
|
+
#### Default Integrations
|
|
23
|
+
|
|
24
|
+
<Infrastructure>
|
|
25
|
+
<Fragment slot="cdk">
|
|
26
|
+
You can use the static `defaultIntegrations` to make use of the default pattern, which defines an individual AWS Lambda function for each operation:
|
|
27
|
+
|
|
28
|
+
```ts {2}
|
|
29
|
+
new MyApi(this, 'MyApi', {
|
|
30
|
+
integrations: MyApi.defaultIntegrations(this).build(),
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
</Fragment>
|
|
34
|
+
<Fragment slot="terraform">
|
|
35
|
+
Terraform modules automatically use the router pattern with a single Lambda function. No additional configuration is needed:
|
|
36
|
+
|
|
37
|
+
```hcl
|
|
38
|
+
module "my_api" {
|
|
39
|
+
source = "../../common/terraform/src/app/apis/my-api"
|
|
40
|
+
|
|
41
|
+
asset_bucket_name = module.asset_bucket.bucket_name
|
|
42
|
+
|
|
43
|
+
# The module automatically creates a single Lambda function
|
|
44
|
+
# that handles all API operations
|
|
45
|
+
tags = local.common_tags
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
</Fragment>
|
|
49
|
+
</Infrastructure>
|
|
50
|
+
|
|
51
|
+
#### Accessing Integrations
|
|
52
|
+
|
|
53
|
+
<Infrastructure>
|
|
54
|
+
<Fragment slot="cdk">
|
|
55
|
+
You can access the underlying AWS Lambda functions via the API construct's `integrations` property, in a type-safe manner. For example, if your API defines an operation named `sayHello` and you need to add some permissions to this function, you can do so as follows:
|
|
56
|
+
|
|
57
|
+
```ts {6}
|
|
58
|
+
const api = new MyApi(this, 'MyApi', {
|
|
59
|
+
integrations: MyApi.defaultIntegrations(this).build(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// sayHello is typed to the operations defined in your API
|
|
63
|
+
api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({
|
|
64
|
+
effect: Effect.ALLOW,
|
|
65
|
+
actions: [...],
|
|
66
|
+
resources: [...],
|
|
67
|
+
}));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If your API uses the `shared` pattern, the shared router Lambda is exposed as `api.integrations.$router`:
|
|
71
|
+
|
|
72
|
+
```ts {6}
|
|
73
|
+
const api = new MyApi(this, 'MyApi', {
|
|
74
|
+
integrations: MyApi.defaultIntegrations(this).build(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');
|
|
78
|
+
```
|
|
79
|
+
</Fragment>
|
|
80
|
+
<Fragment slot="terraform">
|
|
81
|
+
With Terraform's router pattern, there's only one Lambda function. You can access it via the module outputs:
|
|
82
|
+
|
|
83
|
+
```hcl
|
|
84
|
+
# Grant additional permissions to the single Lambda function
|
|
85
|
+
resource "aws_iam_role_policy" "additional_permissions" {
|
|
86
|
+
name = "additional-api-permissions"
|
|
87
|
+
role = module.my_api.lambda_execution_role_name
|
|
88
|
+
|
|
89
|
+
policy = jsonencode({
|
|
90
|
+
Version = "2012-10-17"
|
|
91
|
+
Statement = [
|
|
92
|
+
{
|
|
93
|
+
Effect = "Allow"
|
|
94
|
+
Action = [
|
|
95
|
+
"s3:GetObject",
|
|
96
|
+
"s3:PutObject"
|
|
97
|
+
]
|
|
98
|
+
Resource = "arn:aws:s3:::my-bucket/*"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
</Fragment>
|
|
105
|
+
</Infrastructure>
|
|
106
|
+
|
|
107
|
+
#### Customising Default Options
|
|
108
|
+
|
|
109
|
+
<Infrastructure>
|
|
110
|
+
<Fragment slot="cdk">
|
|
111
|
+
If you would like to customise the options used when creating the Lambda function for each default integration, you can use the `withDefaultOptions` method. For example, if you would like all of your Lambda functions to reside in a Vpc:
|
|
112
|
+
|
|
113
|
+
```ts {5-7}
|
|
114
|
+
const vpc = new Vpc(this, 'Vpc', ...);
|
|
115
|
+
|
|
116
|
+
new MyApi(this, 'MyApi', {
|
|
117
|
+
integrations: MyApi.defaultIntegrations(this)
|
|
118
|
+
.withDefaultOptions({
|
|
119
|
+
vpc,
|
|
120
|
+
})
|
|
121
|
+
.build(),
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
</Fragment>
|
|
127
|
+
<Fragment slot="terraform">
|
|
128
|
+
To customize options like VPC configuration, you need to edit the generated Terraform module. For example, to add VPC support to all Lambda functions:
|
|
129
|
+
|
|
130
|
+
```hcl
|
|
131
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
132
|
+
# Add VPC variables
|
|
133
|
+
variable "vpc_subnet_ids" {
|
|
134
|
+
description = "List of VPC subnet IDs for Lambda function"
|
|
135
|
+
type = list(string)
|
|
136
|
+
default = []
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
variable "vpc_security_group_ids" {
|
|
140
|
+
description = "List of VPC security group IDs for Lambda function"
|
|
141
|
+
type = list(string)
|
|
142
|
+
default = []
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Update the Lambda function resource
|
|
146
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
147
|
+
# ... existing configuration ...
|
|
148
|
+
|
|
149
|
+
# Add VPC configuration
|
|
150
|
+
vpc_config {
|
|
151
|
+
subnet_ids = var.vpc_subnet_ids
|
|
152
|
+
security_group_ids = var.vpc_security_group_ids
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Then use the module with VPC configuration:
|
|
158
|
+
|
|
159
|
+
```hcl
|
|
160
|
+
module "my_api" {
|
|
161
|
+
source = "../../common/terraform/src/app/apis/my-api"
|
|
162
|
+
|
|
163
|
+
asset_bucket_name = module.asset_bucket.bucket_name
|
|
164
|
+
|
|
165
|
+
# VPC configuration
|
|
166
|
+
vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
|
|
167
|
+
vpc_security_group_ids = [aws_security_group.lambda_sg.id]
|
|
168
|
+
|
|
169
|
+
tags = local.common_tags
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
</Fragment>
|
|
173
|
+
</Infrastructure>
|
|
174
|
+
|
|
175
|
+
#### Overriding Integrations
|
|
176
|
+
|
|
177
|
+
<Infrastructure>
|
|
178
|
+
<Fragment slot="cdk">
|
|
179
|
+
You can also override integrations for specific operations using the `withOverrides` method. Each override must specify an `integration` property which is typed to the appropriate CDK integration construct for the HTTP or REST API. The `withOverrides` method is also type-safe. For example, if you would like to override a `getDocumentation` API to point to documentation hosted by some external website you could achieve this as follows:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
new MyApi(this, 'MyApi', {
|
|
183
|
+
integrations: MyApi.defaultIntegrations(this)
|
|
184
|
+
.withOverrides({
|
|
185
|
+
getDocumentation: {
|
|
186
|
+
integration: new HttpIntegration('https://example.com/documentation'),
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
.build(),
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
You will also notice that the overridden integration no longer has a `handler` property when accessing it via `api.integrations.getDocumentation`.
|
|
194
|
+
|
|
195
|
+
You can add additional properties to an integration which will also be typed accordingly, allowing for other types of integration to be abstracted but remain type-safe, for example if you have created an S3 integration for a REST API and later wish to reference the bucket for a particular operation, you can do so as follows:
|
|
196
|
+
|
|
197
|
+
```ts {13, 41}
|
|
198
|
+
const storageBucket = new Bucket(this, 'Bucket', { ... });
|
|
199
|
+
|
|
200
|
+
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', {
|
|
201
|
+
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
storageBucket.grantRead(apiGatewayRole);
|
|
205
|
+
|
|
206
|
+
const api = new MyApi(this, 'MyApi', {
|
|
207
|
+
integrations: MyApi.defaultIntegrations(this)
|
|
208
|
+
.withOverrides({
|
|
209
|
+
getFile: {
|
|
210
|
+
bucket: storageBucket,
|
|
211
|
+
integration: new AwsIntegration({
|
|
212
|
+
service: 's3',
|
|
213
|
+
integrationHttpMethod: 'GET',
|
|
214
|
+
path: `${storageBucket.bucketName}/{fileName}`,
|
|
215
|
+
options: {
|
|
216
|
+
credentialsRole: apiGatewayRole,
|
|
217
|
+
requestParameters: {
|
|
218
|
+
'integration.request.path.fileName': 'method.request.querystring.fileName',
|
|
219
|
+
},
|
|
220
|
+
integrationResponses: [{ statusCode: '200' }],
|
|
221
|
+
},
|
|
222
|
+
}),
|
|
223
|
+
options: {
|
|
224
|
+
requestParameters: {
|
|
225
|
+
'method.request.querystring.fileName': true,
|
|
226
|
+
},
|
|
227
|
+
methodResponses: [{
|
|
228
|
+
statusCode: '200',
|
|
229
|
+
}],
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
.build(),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Later, perhaps in another file, you can access the bucket property we defined
|
|
237
|
+
// in a type-safe manner
|
|
238
|
+
api.integrations.getFile.bucket.grantRead(...);
|
|
239
|
+
```
|
|
240
|
+
</Fragment>
|
|
241
|
+
<Fragment slot="terraform">
|
|
242
|
+
:::note[Integration Overrides]
|
|
243
|
+
Overriding specific integrations is not supported with Terraform modules since they use the router pattern. All operations are handled by the single Lambda function.
|
|
244
|
+
|
|
245
|
+
For different integration types per operation, you would need to implement explicit integrations manually (see the [Explicit Integrations](#explicit-integrations) section below).
|
|
246
|
+
:::
|
|
247
|
+
</Fragment>
|
|
248
|
+
</Infrastructure>
|
|
249
|
+
|
|
250
|
+
#### Overriding Authorizers
|
|
251
|
+
|
|
252
|
+
<Infrastructure>
|
|
253
|
+
<Fragment slot="cdk">
|
|
254
|
+
You can also supply `options` in your integration to override particular method options such as authorizers, for example if you wished to use Cognito authentication for your `getDocumentation` operation:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
new MyApi(this, 'MyApi', {
|
|
258
|
+
integrations: MyApi.defaultIntegrations(this)
|
|
259
|
+
.withOverrides({
|
|
260
|
+
getDocumentation: {
|
|
261
|
+
integration: new HttpIntegration('https://example.com/documentation'),
|
|
262
|
+
options: {
|
|
263
|
+
authorizer: new CognitoUserPoolsAuthorizer(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
.build(),
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
</Fragment>
|
|
271
|
+
<Fragment slot="terraform">
|
|
272
|
+
:::note[Authorizer Overrides]
|
|
273
|
+
Per-operation authorizer overrides are not supported with Terraform modules. The entire API uses the authentication method specified when generating the API (IAM, Cognito, or Custom).
|
|
274
|
+
|
|
275
|
+
For per-operation authorization, you would need to implement explicit integrations manually as below.
|
|
276
|
+
:::
|
|
277
|
+
</Fragment>
|
|
278
|
+
</Infrastructure>
|
|
279
|
+
|
|
280
|
+
#### Explicit Integrations
|
|
281
|
+
|
|
282
|
+
<Infrastructure>
|
|
283
|
+
<Fragment slot="cdk">
|
|
284
|
+
If you prefer, you can choose not to use the default integrations and instead directly supply one for each operation. This is useful if, for example, each operation needs to use a different type of integration or you would like to receive a type error when adding new operations:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
new MyApi(this, 'MyApi', {
|
|
288
|
+
integrations: {
|
|
289
|
+
sayHello: {
|
|
290
|
+
integration: new LambdaIntegration(...),
|
|
291
|
+
},
|
|
292
|
+
getDocumentation: {
|
|
293
|
+
integration: new HttpIntegration(...),
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
</Fragment>
|
|
299
|
+
<Fragment slot="terraform">
|
|
300
|
+
For explicit per-operation integrations with Terraform, you should modify the generated app-specific module to replace the default proxy integration with specific integrations for each operation.
|
|
301
|
+
|
|
302
|
+
Edit `packages/common/terraform/src/app/apis/my-api/my-api.tf`:
|
|
303
|
+
|
|
304
|
+
1. **Remove the default proxy routes** (e.g., `resource "aws_apigatewayv2_route" "proxy_routes"`)
|
|
305
|
+
2. **Replace the single Lambda function** with individual functions for each operation
|
|
306
|
+
3. **Create specific integrations and routes** for each operation, reusing the same ZIP bundle:
|
|
307
|
+
|
|
308
|
+
<Tabs syncKey="http-rest">
|
|
309
|
+
<TabItem label="HTTP API" _filter={{ computeType: 'ServerlessApiGatewayHttpApi' }}>
|
|
310
|
+
|
|
311
|
+
```diff
|
|
312
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
313
|
+
|
|
314
|
+
# Remove the default single Lambda function
|
|
315
|
+
- resource "aws_lambda_function" "api_lambda" {
|
|
316
|
+
- filename = data.archive_file.lambda_zip.output_path
|
|
317
|
+
- function_name = "MyApiHandler"
|
|
318
|
+
- role = aws_iam_role.lambda_execution_role.arn
|
|
319
|
+
- handler = "index.handler"
|
|
320
|
+
- runtime = "nodejs22.x"
|
|
321
|
+
- timeout = 30
|
|
322
|
+
- # ... rest of configuration
|
|
323
|
+
- }
|
|
324
|
+
|
|
325
|
+
# Remove the default proxy integration
|
|
326
|
+
- resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
327
|
+
- api_id = module.http_api.api_id
|
|
328
|
+
- integration_type = "AWS_PROXY"
|
|
329
|
+
- integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
330
|
+
- # ... rest of configuration
|
|
331
|
+
- }
|
|
332
|
+
|
|
333
|
+
# Remove the default proxy routes
|
|
334
|
+
- resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
335
|
+
- for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
336
|
+
- api_id = module.http_api.api_id
|
|
337
|
+
- route_key = "${each.key} /{proxy+}"
|
|
338
|
+
- target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
339
|
+
- # ... rest of configuration
|
|
340
|
+
- }
|
|
341
|
+
|
|
342
|
+
# Add individual Lambda functions for each operation using the same bundle
|
|
343
|
+
+ resource "aws_lambda_function" "say_hello_handler" {
|
|
344
|
+
+ filename = data.archive_file.lambda_zip.output_path
|
|
345
|
+
+ function_name = "MyApi-SayHello"
|
|
346
|
+
+ role = aws_iam_role.lambda_execution_role.arn
|
|
347
|
+
+ handler = "sayHello.handler" # Specific handler for this operation
|
|
348
|
+
+ runtime = "nodejs22.x"
|
|
349
|
+
+ timeout = 30
|
|
350
|
+
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
351
|
+
+
|
|
352
|
+
+ tracing_config {
|
|
353
|
+
+ mode = "Active"
|
|
354
|
+
+ }
|
|
355
|
+
+
|
|
356
|
+
+ environment {
|
|
357
|
+
+ variables = var.env
|
|
358
|
+
+ }
|
|
359
|
+
+
|
|
360
|
+
+ tags = var.tags
|
|
361
|
+
+ }
|
|
362
|
+
|
|
363
|
+
+ resource "aws_lambda_function" "get_documentation_handler" {
|
|
364
|
+
+ filename = data.archive_file.lambda_zip.output_path
|
|
365
|
+
+ function_name = "MyApi-GetDocumentation"
|
|
366
|
+
+ role = aws_iam_role.lambda_execution_role.arn
|
|
367
|
+
+ handler = "getDocumentation.handler" # Specific handler for this operation
|
|
368
|
+
+ runtime = "nodejs22.x"
|
|
369
|
+
+ timeout = 30
|
|
370
|
+
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
371
|
+
+
|
|
372
|
+
+ tracing_config {
|
|
373
|
+
+ mode = "Active"
|
|
374
|
+
+ }
|
|
375
|
+
+
|
|
376
|
+
+ environment {
|
|
377
|
+
+ variables = var.env
|
|
378
|
+
+ }
|
|
379
|
+
+
|
|
380
|
+
+ tags = var.tags
|
|
381
|
+
+ }
|
|
382
|
+
|
|
383
|
+
# Add specific integrations for each operation
|
|
384
|
+
+ resource "aws_apigatewayv2_integration" "say_hello_integration" {
|
|
385
|
+
+ api_id = module.http_api.api_id
|
|
386
|
+
+ integration_type = "AWS_PROXY"
|
|
387
|
+
+ integration_uri = aws_lambda_function.say_hello_handler.invoke_arn
|
|
388
|
+
+ payload_format_version = "2.0"
|
|
389
|
+
+ timeout_milliseconds = 30000
|
|
390
|
+
+ }
|
|
391
|
+
|
|
392
|
+
+ resource "aws_apigatewayv2_integration" "get_documentation_integration" {
|
|
393
|
+
+ api_id = module.http_api.api_id
|
|
394
|
+
+ integration_type = "HTTP_PROXY"
|
|
395
|
+
+ integration_uri = "https://example.com/documentation"
|
|
396
|
+
+ integration_method = "GET"
|
|
397
|
+
+ }
|
|
398
|
+
|
|
399
|
+
# Add specific routes for each operation
|
|
400
|
+
+ resource "aws_apigatewayv2_route" "say_hello_route" {
|
|
401
|
+
+ api_id = module.http_api.api_id
|
|
402
|
+
+ route_key = "POST /sayHello"
|
|
403
|
+
+ target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}"
|
|
404
|
+
+ authorization_type = "AWS_IAM"
|
|
405
|
+
+ }
|
|
406
|
+
|
|
407
|
+
+ resource "aws_apigatewayv2_route" "get_documentation_route" {
|
|
408
|
+
+ api_id = module.http_api.api_id
|
|
409
|
+
+ route_key = "GET /documentation"
|
|
410
|
+
+ target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}"
|
|
411
|
+
+ authorization_type = "NONE"
|
|
412
|
+
+ }
|
|
413
|
+
|
|
414
|
+
# Add Lambda permissions for each function
|
|
415
|
+
+ resource "aws_lambda_permission" "say_hello_permission" {
|
|
416
|
+
+ statement_id = "AllowExecutionFromAPIGateway-SayHello"
|
|
417
|
+
+ action = "lambda:InvokeFunction"
|
|
418
|
+
+ function_name = aws_lambda_function.say_hello_handler.function_name
|
|
419
|
+
+ principal = "apigateway.amazonaws.com"
|
|
420
|
+
+ source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
421
|
+
+ }
|
|
422
|
+
|
|
423
|
+
+ resource "aws_lambda_permission" "get_documentation_permission" {
|
|
424
|
+
+ statement_id = "AllowExecutionFromAPIGateway-GetDocumentation"
|
|
425
|
+
+ action = "lambda:InvokeFunction"
|
|
426
|
+
+ function_name = aws_lambda_function.get_documentation_handler.function_name
|
|
427
|
+
+ principal = "apigateway.amazonaws.com"
|
|
428
|
+
+ source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
429
|
+
+ }
|
|
430
|
+
```
|
|
431
|
+
</TabItem>
|
|
432
|
+
<TabItem label="REST API" _filter={{ computeType: 'ServerlessApiGatewayRestApi' }}>
|
|
433
|
+
|
|
434
|
+
```diff
|
|
435
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
436
|
+
|
|
437
|
+
# Remove the default single Lambda function
|
|
438
|
+
- resource "aws_lambda_function" "api_lambda" {
|
|
439
|
+
- filename = data.archive_file.lambda_zip.output_path
|
|
440
|
+
- function_name = "MyApiHandler"
|
|
441
|
+
- role = aws_iam_role.lambda_execution_role.arn
|
|
442
|
+
- handler = "index.handler"
|
|
443
|
+
- runtime = "nodejs22.x"
|
|
444
|
+
- timeout = 30
|
|
445
|
+
- # ... rest of configuration
|
|
446
|
+
- }
|
|
447
|
+
|
|
448
|
+
# Remove the default proxy integration
|
|
449
|
+
- resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
450
|
+
- api_id = module.http_api.api_id
|
|
451
|
+
- integration_type = "AWS_PROXY"
|
|
452
|
+
- integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
453
|
+
- # ... rest of configuration
|
|
454
|
+
- }
|
|
455
|
+
|
|
456
|
+
# Remove the default proxy routes
|
|
457
|
+
- resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
458
|
+
- for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
459
|
+
- api_id = module.http_api.api_id
|
|
460
|
+
- route_key = "${each.key} /{proxy+}"
|
|
461
|
+
- target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
462
|
+
- # ... rest of configuration
|
|
463
|
+
- }
|
|
464
|
+
|
|
465
|
+
# Add individual Lambda functions for each operation using the same bundle
|
|
466
|
+
+ resource "aws_lambda_function" "say_hello_handler" {
|
|
467
|
+
+ filename = data.archive_file.lambda_zip.output_path
|
|
468
|
+
+ function_name = "MyApi-SayHello"
|
|
469
|
+
+ role = aws_iam_role.lambda_execution_role.arn
|
|
470
|
+
+ handler = "sayHello.handler" # Specific handler for this operation
|
|
471
|
+
+ runtime = "nodejs22.x"
|
|
472
|
+
+ timeout = 30
|
|
473
|
+
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
474
|
+
+
|
|
475
|
+
+ tracing_config {
|
|
476
|
+
+ mode = "Active"
|
|
477
|
+
+ }
|
|
478
|
+
+
|
|
479
|
+
+ environment {
|
|
480
|
+
+ variables = var.env
|
|
481
|
+
+ }
|
|
482
|
+
+
|
|
483
|
+
+ tags = var.tags
|
|
484
|
+
+ }
|
|
485
|
+
|
|
486
|
+
+ resource "aws_lambda_function" "get_documentation_handler" {
|
|
487
|
+
+ filename = data.archive_file.lambda_zip.output_path
|
|
488
|
+
+ function_name = "MyApi-GetDocumentation"
|
|
489
|
+
+ role = aws_iam_role.lambda_execution_role.arn
|
|
490
|
+
+ handler = "getDocumentation.handler" # Specific handler for this operation
|
|
491
|
+
+ runtime = "nodejs22.x"
|
|
492
|
+
+ timeout = 30
|
|
493
|
+
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
494
|
+
+
|
|
495
|
+
+ tracing_config {
|
|
496
|
+
+ mode = "Active"
|
|
497
|
+
+ }
|
|
498
|
+
+
|
|
499
|
+
+ environment {
|
|
500
|
+
+ variables = var.env
|
|
501
|
+
+ }
|
|
502
|
+
+
|
|
503
|
+
+ tags = var.tags
|
|
504
|
+
+ }
|
|
505
|
+
|
|
506
|
+
# Add specific resources and methods for each operation
|
|
507
|
+
+ resource "aws_api_gateway_resource" "say_hello_resource" {
|
|
508
|
+
+ rest_api_id = module.rest_api.api_id
|
|
509
|
+
+ parent_id = module.rest_api.api_root_resource_id
|
|
510
|
+
+ path_part = "sayHello"
|
|
511
|
+
+ }
|
|
512
|
+
|
|
513
|
+
+ resource "aws_api_gateway_method" "say_hello_method" {
|
|
514
|
+
+ rest_api_id = module.rest_api.api_id
|
|
515
|
+
+ resource_id = aws_api_gateway_resource.say_hello_resource.id
|
|
516
|
+
+ http_method = "POST"
|
|
517
|
+
+ authorization = "AWS_IAM"
|
|
518
|
+
+ }
|
|
519
|
+
|
|
520
|
+
+ resource "aws_api_gateway_integration" "say_hello_integration" {
|
|
521
|
+
+ rest_api_id = module.rest_api.api_id
|
|
522
|
+
+ resource_id = aws_api_gateway_resource.say_hello_resource.id
|
|
523
|
+
+ http_method = aws_api_gateway_method.say_hello_method.http_method
|
|
524
|
+
+
|
|
525
|
+
+ integration_http_method = "POST"
|
|
526
|
+
+ type = "AWS_PROXY"
|
|
527
|
+
+ uri = aws_lambda_function.say_hello_handler.invoke_arn
|
|
528
|
+
+ }
|
|
529
|
+
|
|
530
|
+
+ resource "aws_api_gateway_resource" "get_documentation_resource" {
|
|
531
|
+
+ rest_api_id = module.rest_api.api_id
|
|
532
|
+
+ parent_id = module.rest_api.api_root_resource_id
|
|
533
|
+
+ path_part = "documentation"
|
|
534
|
+
+ }
|
|
535
|
+
|
|
536
|
+
+ resource "aws_api_gateway_method" "get_documentation_method" {
|
|
537
|
+
+ rest_api_id = module.rest_api.api_id
|
|
538
|
+
+ resource_id = aws_api_gateway_resource.get_documentation_resource.id
|
|
539
|
+
+ http_method = "GET"
|
|
540
|
+
+ authorization = "NONE"
|
|
541
|
+
+ }
|
|
542
|
+
|
|
543
|
+
+ resource "aws_api_gateway_integration" "get_documentation_integration" {
|
|
544
|
+
+ rest_api_id = module.rest_api.api_id
|
|
545
|
+
+ resource_id = aws_api_gateway_resource.get_documentation_resource.id
|
|
546
|
+
+ http_method = aws_api_gateway_method.get_documentation_method.http_method
|
|
547
|
+
+
|
|
548
|
+
+ integration_http_method = "GET"
|
|
549
|
+
+ type = "HTTP"
|
|
550
|
+
+ uri = "https://example.com/documentation"
|
|
551
|
+
+ }
|
|
552
|
+
|
|
553
|
+
# Update deployment to depend on new integrations
|
|
554
|
+
~ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
555
|
+
rest_api_id = module.rest_api.api_id
|
|
556
|
+
|
|
557
|
+
depends_on = [
|
|
558
|
+
- aws_api_gateway_integration.lambda_integration,
|
|
559
|
+
+ aws_api_gateway_integration.say_hello_integration,
|
|
560
|
+
+ aws_api_gateway_integration.get_documentation_integration,
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
lifecycle {
|
|
564
|
+
create_before_destroy = true
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
+ triggers = {
|
|
568
|
+
+ redeployment = sha1(jsonencode([
|
|
569
|
+
+ aws_api_gateway_integration.say_hello_integration,
|
|
570
|
+
+ aws_api_gateway_integration.get_documentation_integration,
|
|
571
|
+
+ ]))
|
|
572
|
+
+ }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# Add Lambda permissions for each function
|
|
576
|
+
+ resource "aws_lambda_permission" "say_hello_permission" {
|
|
577
|
+
+ statement_id = "AllowExecutionFromAPIGateway-SayHello"
|
|
578
|
+
+ action = "lambda:InvokeFunction"
|
|
579
|
+
+ function_name = aws_lambda_function.say_hello_handler.function_name
|
|
580
|
+
+ principal = "apigateway.amazonaws.com"
|
|
581
|
+
+ source_arn = "${module.rest_api.api_execution_arn}/*/*"
|
|
582
|
+
+ }
|
|
583
|
+
|
|
584
|
+
+ resource "aws_lambda_permission" "get_documentation_permission" {
|
|
585
|
+
+ statement_id = "AllowExecutionFromAPIGateway-GetDocumentation"
|
|
586
|
+
+ action = "lambda:InvokeFunction"
|
|
587
|
+
+ function_name = aws_lambda_function.get_documentation_handler.function_name
|
|
588
|
+
+ principal = "apigateway.amazonaws.com"
|
|
589
|
+
+ source_arn = "${module.rest_api.api_execution_arn}/*/*"
|
|
590
|
+
+ }
|
|
591
|
+
```
|
|
592
|
+
</TabItem>
|
|
593
|
+
</Tabs>
|
|
594
|
+
|
|
595
|
+
</Fragment>
|
|
596
|
+
</Infrastructure>
|
|
597
|
+
|
|
598
|
+
#### Integration Pattern
|
|
599
|
+
|
|
600
|
+
<Infrastructure>
|
|
601
|
+
<Fragment slot="cdk">
|
|
602
|
+
|
|
603
|
+
Generated CDK API constructs support two integration patterns:
|
|
604
|
+
|
|
605
|
+
- `isolated` creates one Lambda function per operation. This is the default for generated APIs.
|
|
606
|
+
- `shared` creates a single default router Lambda and reuses it for every operation unless you override specific integrations.
|
|
607
|
+
|
|
608
|
+
`isolated` gives you finer-grained permissions and configuration per operation. `shared` reduces Lambda and API Gateway integration sprawl while still allowing selective overrides.
|
|
609
|
+
|
|
610
|
+
For example, setting `pattern` to `'shared'` creates a single function instead of one per integration:
|
|
611
|
+
|
|
612
|
+
```ts {5,11-12}
|
|
613
|
+
// packages/common/constructs/src/app/apis/my-api.ts
|
|
614
|
+
export class MyApi<...> extends ... {
|
|
615
|
+
|
|
616
|
+
public static defaultIntegrations = (scope: Construct) => {
|
|
617
|
+
...
|
|
618
|
+
return IntegrationBuilder.rest({
|
|
619
|
+
pattern: 'shared',
|
|
620
|
+
...
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
</Fragment>
|
|
626
|
+
<Fragment slot="terraform">
|
|
627
|
+
Terraform modules automatically use the router pattern - this is the default and only supported approach. The generated module creates a single Lambda function that handles all API operations.
|
|
628
|
+
|
|
629
|
+
You can simply instantiate the default module to get the router pattern:
|
|
630
|
+
|
|
631
|
+
```hcl
|
|
632
|
+
# Default router pattern - single Lambda function for all operations
|
|
633
|
+
module "my_api" {
|
|
634
|
+
source = "../../common/terraform/src/app/apis/my-api"
|
|
635
|
+
|
|
636
|
+
asset_bucket_name = module.asset_bucket.bucket_name
|
|
637
|
+
|
|
638
|
+
# Single Lambda function handles all operations automatically
|
|
639
|
+
tags = local.common_tags
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
</Fragment>
|
|
643
|
+
</Infrastructure>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: WAF configuration
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
|
|
6
|
+
For REST APIs, the generated construct associates an [AWS WAFv2](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) Web ACL with the API Gateway stage by default. The Web ACL uses the AWS managed default ruleset ([`AWSManagedRulesCommonRuleSet`](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-crs) and [`AWSManagedRulesKnownBadInputsRuleSet`](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html#aws-managed-rule-groups-baseline-known-bad-inputs)), providing protection against common web exploits including the OWASP Top 10. WAF request logs are written to a CloudWatch Logs group.
|
|
7
|
+
|
|
8
|
+
:::caution[SizeRestrictions_BODY deviation from defaults]
|
|
9
|
+
The `SizeRestrictions_BODY` rule from `AWSManagedRulesCommonRuleSet` is overridden to `Count` rather than `Block`, since the rule's 8 KB limit is too restrictive for most APIs. Oversized requests will still be recorded as metrics so you can monitor them. See the [AWS WAF body size limits](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-oversize-handling.html) guide for more details.
|
|
10
|
+
:::
|
|
11
|
+
|
|
12
|
+
You can edit the generated rest-api construct to add, remove, or adjust rules (for example, to add [rate-based rules](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based.html) or additional managed rule groups).
|
|
13
|
+
|
|
14
|
+
<Infrastructure>
|
|
15
|
+
<Fragment slot="cdk">
|
|
16
|
+
To opt out (for example, to attach your own Web ACL), set `enableWaf` to `false`:
|
|
17
|
+
|
|
18
|
+
```ts {4}
|
|
19
|
+
const api = new MyApi(this, 'MyApi', {
|
|
20
|
+
integrations: MyApi.defaultIntegrations(this).build(),
|
|
21
|
+
enableWaf: false,
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
</Fragment>
|
|
25
|
+
<Fragment slot="terraform">
|
|
26
|
+
To opt out (for example, to attach your own Web ACL), set `enable_waf` to `false`:
|
|
27
|
+
|
|
28
|
+
```hcl {4-5}
|
|
29
|
+
module "my_api" {
|
|
30
|
+
source = "../../common/terraform/src/app/apis/my-api"
|
|
31
|
+
|
|
32
|
+
asset_bucket_name = module.asset_bucket.bucket_name
|
|
33
|
+
enable_waf = false
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
</Fragment>
|
|
37
|
+
</Infrastructure>
|