@aws/nx-plugin 1.0.0-rc.44 → 1.0.0-rc.46
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/generators.json +13 -7
- package/package.json +1 -1
- package/src/agentcore-gateway/files/cedar/policies/README.md +9 -0
- package/src/agentcore-gateway/files/cedar/policies/permit-all.cedar +15 -0
- package/src/agentcore-gateway/generator.js +12 -5
- package/src/agentcore-gateway/generator.js.map +1 -1
- package/src/agentcore-gateway/schema.d.js.map +1 -1
- package/src/agentcore-gateway/schema.d.ts +1 -1
- package/src/agentcore-gateway/schema.json +3 -2
- package/src/mcp-server/tools/general-guidance.js +1 -1
- package/src/mcp-server/tools/general-guidance.js.map +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +5 -2
- package/src/py/agent/gateway-connection/generator.js +1 -1
- package/src/py/agent/gateway-connection/generator.js.map +1 -1
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +17 -0
- package/src/py/rdb/files/Dockerfile.create-db-user.template +4 -0
- package/src/py/rdb/files/Dockerfile.migration.template +4 -0
- package/src/py/rdb/generator.js +5 -0
- package/src/py/rdb/generator.js.map +1 -1
- package/src/sdk/ts.d.ts +2 -0
- package/src/sdk/ts.js +2 -0
- package/src/sdk/ts.js.map +1 -1
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +5 -1
- package/src/ts/agent/files/deploy/Dockerfile.template +5 -1
- package/src/ts/agent/gateway-connection/generator.js +1 -1
- package/src/ts/agent/gateway-connection/generator.js.map +1 -1
- package/src/ts/agent/generator.js +6 -0
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +982 -0
- package/src/ts/dcr-proxy/generator.d.ts +10 -0
- package/src/ts/dcr-proxy/generator.js +98 -0
- package/src/ts/dcr-proxy/generator.js.map +1 -0
- package/src/ts/dcr-proxy/schema.d.js +6 -0
- package/src/ts/dcr-proxy/schema.d.js.map +1 -0
- package/src/ts/dcr-proxy/schema.d.ts +13 -0
- package/src/ts/dcr-proxy/schema.json +44 -0
- package/src/ts/mcp-server/files/Dockerfile.template +5 -1
- package/src/ts/mcp-server/generator.js +1 -0
- package/src/ts/mcp-server/generator.js.map +1 -1
- package/src/ts/nx-plugin/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/nx-plugin/files/mcp-server/resources/GENERAL_GUIDANCE.md.template +1 -1
- package/src/ts/nx-plugin/files/mcp-server/utils.ts.template +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +7 -0
- package/src/ts/rdb/files/Dockerfile.template +7 -0
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +13 -4
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +11 -6
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +12 -2
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +1 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js +2 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +38 -3
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +13 -3
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +22 -1
- package/src/utils/commands.js +1 -1
- package/src/utils/commands.js.map +1 -1
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.d.ts +38 -0
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js +93 -0
- package/src/utils/dcr-proxy-constructs/dcr-proxy-constructs.js.map +1 -0
- package/src/utils/dcr-proxy-constructs/files/cdk/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.ts.template +227 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/authorization-server-metadata.ts.template +39 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/authorize.ts.template +26 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/mcp-proxy.ts.template +89 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/protected-resource-metadata.ts.template +13 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/register.ts.template +70 -0
- package/src/utils/dcr-proxy-constructs/files/project/src/handlers/token.ts.template +75 -0
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +410 -0
- package/src/utils/docker.d.ts +4 -3
- package/src/utils/docker.js +11 -7
- package/src/utils/docker.js.map +1 -1
- package/src/utils/format.d.ts +1 -1
- package/src/utils/format.js +3 -2
- package/src/utils/format.js.map +1 -1
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +11 -6
- package/src/utils/identity-constructs/identity-constructs.js +14 -3
- package/src/utils/identity-constructs/identity-constructs.js.map +1 -1
- package/src/utils/init.js +4 -0
- package/src/utils/init.js.map +1 -1
- package/src/utils/versions.d.ts +4 -1
- package/src/utils/versions.js +9 -1
- package/src/utils/versions.js.map +1 -1
- package/src/utils/website-constructs/files/cdk/core/static-website.ts.template +3 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# OAuth Dynamic Client Registration (DCR) proxy for Amazon Cognito.
|
|
2
|
+
#
|
|
3
|
+
# Lets MCP clients (Claude Code, Kiro CLI, MCP Inspector, ...) authenticate
|
|
4
|
+
# against an Amazon Cognito User Pool while keeping the Hosted UI flow intact,
|
|
5
|
+
# supporting RFC 7591 Dynamic Client Registration, and never exposing the
|
|
6
|
+
# Cognito App Client secret to the client.
|
|
7
|
+
|
|
8
|
+
terraform {
|
|
9
|
+
required_providers {
|
|
10
|
+
aws = {
|
|
11
|
+
source = "hashicorp/aws"
|
|
12
|
+
version = "<%- awsProviderVersion %>"
|
|
13
|
+
}
|
|
14
|
+
archive = {
|
|
15
|
+
source = "hashicorp/archive"
|
|
16
|
+
version = "<%- archiveProviderVersion %>"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Cognito configuration (injected by the caller)
|
|
22
|
+
variable "user_pool_id" {
|
|
23
|
+
description = "Cognito User Pool id (e.g. us-east-1_abc123)"
|
|
24
|
+
type = string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
variable "user_pool_client_id" {
|
|
28
|
+
description = "Cognito User Pool App Client id"
|
|
29
|
+
type = string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
variable "cognito_client_secret_arn" {
|
|
33
|
+
description = "ARN of the Secrets Manager secret holding the Cognito App Client secret. The token handler reads this at runtime; the secret is never exposed to the client."
|
|
34
|
+
type = string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
variable "cognito_hosted_ui_base" {
|
|
38
|
+
description = "Base URL of the Cognito Hosted UI domain, without a trailing slash (e.g. https://my-domain.auth.us-east-1.amazoncognito.com)"
|
|
39
|
+
type = string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
variable "upstream_url" {
|
|
43
|
+
description = "Full URL of the upstream MCP server. The proxy forwards /mcp requests to this URL and rewrites the upstream WWW-Authenticate header."
|
|
44
|
+
type = string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
variable "env" {
|
|
48
|
+
description = "Additional environment variables applied to all proxy Lambda functions"
|
|
49
|
+
type = map(string)
|
|
50
|
+
default = {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# CORS Configuration (passed to the core http-api module)
|
|
54
|
+
variable "cors_allow_headers" {
|
|
55
|
+
description = "List of allowed headers for CORS"
|
|
56
|
+
type = list(string)
|
|
57
|
+
default = ["authorization", "content-type", "mcp-protocol-version"]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
variable "cors_allow_methods" {
|
|
61
|
+
description = "List of allowed HTTP methods for CORS"
|
|
62
|
+
type = list(string)
|
|
63
|
+
default = ["GET", "POST", "DELETE", "OPTIONS"]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
variable "cors_allow_origins" {
|
|
67
|
+
description = "List of allowed origins for CORS"
|
|
68
|
+
type = list(string)
|
|
69
|
+
default = ["*"]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
variable "asset_bucket_name" {
|
|
73
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zips. Instantiate the `core/asset-bucket` module once per deployment and pass its `bucket_name` output here."
|
|
74
|
+
type = string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
variable "mcp_proxy_timeout" {
|
|
78
|
+
description = "Timeout (seconds) applied to the MCP proxy Lambda. Defaults to 29 (the API Gateway HTTP API integration limit)."
|
|
79
|
+
type = number
|
|
80
|
+
default = 29
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
variable "mcp_proxy_memory_size" {
|
|
84
|
+
description = "Memory size (MB) for the MCP proxy Lambda"
|
|
85
|
+
type = number
|
|
86
|
+
default = 512
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
variable "tags" {
|
|
90
|
+
description = "Tags to apply to all resources"
|
|
91
|
+
type = map(string)
|
|
92
|
+
default = {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
data "aws_region" "current" {}
|
|
96
|
+
data "aws_caller_identity" "current" {}
|
|
97
|
+
|
|
98
|
+
# The Cognito App Client secret, referenced by ARN so the token handler's role
|
|
99
|
+
# can be scoped to read exactly this secret.
|
|
100
|
+
data "aws_secretsmanager_secret" "cognito_client_secret" {
|
|
101
|
+
arn = var.cognito_client_secret_arn
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
resource "random_string" "suffix" {
|
|
105
|
+
length = 8
|
|
106
|
+
special = false
|
|
107
|
+
upper = false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
locals {
|
|
111
|
+
aws_account_id = data.aws_caller_identity.current.account_id
|
|
112
|
+
aws_region = data.aws_region.current.region
|
|
113
|
+
|
|
114
|
+
base_env = merge({
|
|
115
|
+
COGNITO_CLIENT_ID = var.user_pool_client_id
|
|
116
|
+
COGNITO_CLIENT_SECRET_ARN = var.cognito_client_secret_arn
|
|
117
|
+
COGNITO_ISSUER = "https://cognito-idp.${local.aws_region}.amazonaws.com/${var.user_pool_id}"
|
|
118
|
+
COGNITO_HOSTED_UI_BASE = var.cognito_hosted_ui_base
|
|
119
|
+
COGNITO_REGION = local.aws_region
|
|
120
|
+
}, var.env)
|
|
121
|
+
|
|
122
|
+
# Workspace-root-relative bundle output directory per handler
|
|
123
|
+
handler_bundle_dirs = {
|
|
124
|
+
"protected-resource-metadata" = "<%- bundlePathsFromRoot['protected-resource-metadata'] %>"
|
|
125
|
+
"authorization-server-metadata" = "<%- bundlePathsFromRoot['authorization-server-metadata'] %>"
|
|
126
|
+
"register" = "<%- bundlePathsFromRoot['register'] %>"
|
|
127
|
+
"authorize" = "<%- bundlePathsFromRoot['authorize'] %>"
|
|
128
|
+
"token" = "<%- bundlePathsFromRoot['token'] %>" #checkov:skip=CKV_SECRET_6:Value is a Lambda bundle output path, not a secret
|
|
129
|
+
"mcp-proxy" = "<%- bundlePathsFromRoot['mcp-proxy'] %>"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Handlers deployed with the standard configuration (timeout 10s, mem 256).
|
|
133
|
+
# mcp-proxy is deployed separately since its env/timeout/memory differ.
|
|
134
|
+
standard_handlers = {
|
|
135
|
+
for name, dir in local.handler_bundle_dirs : name => dir if name != "mcp-proxy"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Package each handler's rolldown bundle into a deployment zip
|
|
140
|
+
data "archive_file" "handler_zip" {
|
|
141
|
+
for_each = local.handler_bundle_dirs
|
|
142
|
+
|
|
143
|
+
type = "zip"
|
|
144
|
+
source_dir = "${path.module}/../../../../../../../${each.value}"
|
|
145
|
+
output_path = "${path.module}/../../../../../../../dist/packages/common/terraform/dcr-proxies/<%- nameKebabCase %>/${each.key}/lambda.zip"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
resource "aws_s3_object" "handler_zip" {
|
|
149
|
+
for_each = local.handler_bundle_dirs
|
|
150
|
+
|
|
151
|
+
bucket = var.asset_bucket_name
|
|
152
|
+
key = "dcr-proxies/<%- nameKebabCase %>/${each.key}/${data.archive_file.handler_zip[each.key].output_sha256}.zip"
|
|
153
|
+
source = data.archive_file.handler_zip[each.key].output_path
|
|
154
|
+
source_hash = data.archive_file.handler_zip[each.key].output_base64sha256
|
|
155
|
+
etag = data.archive_file.handler_zip[each.key].output_md5
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Shared execution role for handlers that do not read the client secret
|
|
159
|
+
resource "aws_iam_role" "proxy" {
|
|
160
|
+
name = "<%- nameKebabCase %>-proxy-${random_string.suffix.result}"
|
|
161
|
+
|
|
162
|
+
assume_role_policy = jsonencode({
|
|
163
|
+
Version = "2012-10-17"
|
|
164
|
+
Statement = [{
|
|
165
|
+
Effect = "Allow"
|
|
166
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
167
|
+
Action = "sts:AssumeRole"
|
|
168
|
+
}]
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
tags = var.tags
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Dedicated role for the token handler, additionally granted read access to the
|
|
175
|
+
# Cognito App Client secret only.
|
|
176
|
+
resource "aws_iam_role" "token" {
|
|
177
|
+
name = "<%- nameKebabCase %>-token-${random_string.suffix.result}"
|
|
178
|
+
|
|
179
|
+
assume_role_policy = jsonencode({
|
|
180
|
+
Version = "2012-10-17"
|
|
181
|
+
Statement = [{
|
|
182
|
+
Effect = "Allow"
|
|
183
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
184
|
+
Action = "sts:AssumeRole"
|
|
185
|
+
}]
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
tags = var.tags
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
resource "aws_iam_role_policy_attachment" "proxy_basic" {
|
|
192
|
+
role = aws_iam_role.proxy.name
|
|
193
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
resource "aws_iam_role_policy_attachment" "proxy_xray" {
|
|
197
|
+
role = aws_iam_role.proxy.name
|
|
198
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
resource "aws_iam_role_policy_attachment" "token_basic" {
|
|
202
|
+
role = aws_iam_role.token.name
|
|
203
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
resource "aws_iam_role_policy_attachment" "token_xray" {
|
|
207
|
+
role = aws_iam_role.token.name
|
|
208
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
resource "aws_iam_role_policy" "token_secret_read" {
|
|
212
|
+
name = "<%- nameKebabCase %>-token-secret-read-${random_string.suffix.result}"
|
|
213
|
+
role = aws_iam_role.token.id
|
|
214
|
+
|
|
215
|
+
policy = jsonencode({
|
|
216
|
+
Version = "2012-10-17"
|
|
217
|
+
Statement = [{
|
|
218
|
+
Effect = "Allow"
|
|
219
|
+
Action = ["secretsmanager:GetSecretValue"]
|
|
220
|
+
Resource = [data.aws_secretsmanager_secret.cognito_client_secret.arn]
|
|
221
|
+
}]
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Standard handler Lambda functions
|
|
226
|
+
resource "aws_lambda_function" "standard" {
|
|
227
|
+
#checkov:skip=CKV_AWS_117:Lambda does not need to be in a VPC for this use case
|
|
228
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this use case
|
|
229
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
230
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
231
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
232
|
+
for_each = local.standard_handlers
|
|
233
|
+
|
|
234
|
+
s3_bucket = aws_s3_object.handler_zip[each.key].bucket
|
|
235
|
+
s3_key = aws_s3_object.handler_zip[each.key].key
|
|
236
|
+
function_name = "<%- nameKebabCase %>-${each.key}-${random_string.suffix.result}"
|
|
237
|
+
role = each.key == "token" ? aws_iam_role.token.arn : aws_iam_role.proxy.arn
|
|
238
|
+
handler = "index.handler"
|
|
239
|
+
runtime = "nodejs22.x"
|
|
240
|
+
architectures = ["arm64"]
|
|
241
|
+
source_code_hash = data.archive_file.handler_zip[each.key].output_base64sha256
|
|
242
|
+
timeout = 10
|
|
243
|
+
memory_size = 256
|
|
244
|
+
|
|
245
|
+
tracing_config {
|
|
246
|
+
mode = "Active"
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
environment {
|
|
250
|
+
variables = local.base_env
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
tags = var.tags
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# MCP proxy Lambda function (extended env, timeout and memory)
|
|
257
|
+
resource "aws_lambda_function" "mcp_proxy" {
|
|
258
|
+
#checkov:skip=CKV_AWS_117:Lambda does not need to be in a VPC for this use case
|
|
259
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this use case
|
|
260
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
261
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
262
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
263
|
+
s3_bucket = aws_s3_object.handler_zip["mcp-proxy"].bucket
|
|
264
|
+
s3_key = aws_s3_object.handler_zip["mcp-proxy"].key
|
|
265
|
+
function_name = "<%- nameKebabCase %>-mcp-proxy-${random_string.suffix.result}"
|
|
266
|
+
role = aws_iam_role.proxy.arn
|
|
267
|
+
handler = "index.handler"
|
|
268
|
+
runtime = "nodejs22.x"
|
|
269
|
+
architectures = ["arm64"]
|
|
270
|
+
source_code_hash = data.archive_file.handler_zip["mcp-proxy"].output_base64sha256
|
|
271
|
+
timeout = var.mcp_proxy_timeout
|
|
272
|
+
memory_size = var.mcp_proxy_memory_size
|
|
273
|
+
|
|
274
|
+
tracing_config {
|
|
275
|
+
mode = "Active"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
environment {
|
|
279
|
+
variables = merge(local.base_env, {
|
|
280
|
+
UPSTREAM_URL = var.upstream_url
|
|
281
|
+
PROXY_METADATA_URL = "${module.http_api.api_endpoint}/.well-known/oauth-protected-resource"
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
tags = var.tags
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# HTTP API Gateway (shared core module)
|
|
289
|
+
module "http_api" {
|
|
290
|
+
source = "../../../core/api/http-api"
|
|
291
|
+
|
|
292
|
+
api_name = "<%- nameKebabCase %>-mcp-auth-proxy-${random_string.suffix.result}"
|
|
293
|
+
api_description = "MCP-compatible OAuth proxy for Amazon Cognito"
|
|
294
|
+
stage_name = "$default"
|
|
295
|
+
stage_auto_deploy = true
|
|
296
|
+
cors_allow_headers = var.cors_allow_headers
|
|
297
|
+
cors_allow_methods = var.cors_allow_methods
|
|
298
|
+
cors_allow_origins = var.cors_allow_origins
|
|
299
|
+
|
|
300
|
+
tags = var.tags
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Integrations
|
|
304
|
+
resource "aws_apigatewayv2_integration" "standard" {
|
|
305
|
+
for_each = local.standard_handlers
|
|
306
|
+
|
|
307
|
+
api_id = module.http_api.api_id
|
|
308
|
+
integration_type = "AWS_PROXY"
|
|
309
|
+
integration_uri = aws_lambda_function.standard[each.key].invoke_arn
|
|
310
|
+
payload_format_version = "2.0"
|
|
311
|
+
timeout_milliseconds = 30000
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
resource "aws_apigatewayv2_integration" "mcp_proxy" {
|
|
315
|
+
api_id = module.http_api.api_id
|
|
316
|
+
integration_type = "AWS_PROXY"
|
|
317
|
+
integration_uri = aws_lambda_function.mcp_proxy.invoke_arn
|
|
318
|
+
payload_format_version = "2.0"
|
|
319
|
+
timeout_milliseconds = 29000
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Routes. jwks_uri/issuer are handled by Cognito; the proxy hosts the OAuth
|
|
323
|
+
# metadata, authorize, token and registration endpoints plus the /mcp proxy.
|
|
324
|
+
locals {
|
|
325
|
+
routes = {
|
|
326
|
+
"GET /.well-known/oauth-protected-resource" = "protected-resource-metadata"
|
|
327
|
+
"GET /.well-known/oauth-authorization-server" = "authorization-server-metadata"
|
|
328
|
+
"GET /.well-known/openid-configuration" = "authorization-server-metadata"
|
|
329
|
+
"POST /register" = "register"
|
|
330
|
+
"GET /authorize" = "authorize"
|
|
331
|
+
"POST /oauth/token" = "token"
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# OAuth discovery, registration and authorization endpoints are part of the
|
|
336
|
+
# public DCR/OAuth flow and must be reachable without prior authorization.
|
|
337
|
+
resource "aws_apigatewayv2_route" "standard" {
|
|
338
|
+
#checkov:skip=CKV_AWS_309:Public OAuth/DCR endpoints must be accessible without authorization
|
|
339
|
+
for_each = local.routes
|
|
340
|
+
|
|
341
|
+
api_id = module.http_api.api_id
|
|
342
|
+
route_key = each.key
|
|
343
|
+
target = "integrations/${aws_apigatewayv2_integration.standard[each.value].id}"
|
|
344
|
+
authorization_type = "NONE"
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# The /mcp route enforces authorization at the upstream MCP server, so the
|
|
348
|
+
# proxy route itself is intentionally left open.
|
|
349
|
+
resource "aws_apigatewayv2_route" "mcp_proxy" {
|
|
350
|
+
#checkov:skip=CKV_AWS_309:Authorization is enforced by the upstream MCP server, not this proxy route
|
|
351
|
+
api_id = module.http_api.api_id
|
|
352
|
+
route_key = "ANY /mcp"
|
|
353
|
+
target = "integrations/${aws_apigatewayv2_integration.mcp_proxy.id}"
|
|
354
|
+
authorization_type = "NONE"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# Permissions for API Gateway to invoke each Lambda
|
|
358
|
+
resource "aws_lambda_permission" "standard" {
|
|
359
|
+
for_each = local.standard_handlers
|
|
360
|
+
|
|
361
|
+
statement_id = "AllowAPIGatewayInvoke"
|
|
362
|
+
action = "lambda:InvokeFunction"
|
|
363
|
+
function_name = aws_lambda_function.standard[each.key].function_name
|
|
364
|
+
principal = "apigateway.amazonaws.com"
|
|
365
|
+
source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
resource "aws_lambda_permission" "mcp_proxy" {
|
|
369
|
+
statement_id = "AllowAPIGatewayInvoke"
|
|
370
|
+
action = "lambda:InvokeFunction"
|
|
371
|
+
function_name = aws_lambda_function.mcp_proxy.function_name
|
|
372
|
+
principal = "apigateway.amazonaws.com"
|
|
373
|
+
source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
# Outputs
|
|
377
|
+
output "proxy_url" {
|
|
378
|
+
description = "Base URL of the DCR proxy"
|
|
379
|
+
value = module.http_api.api_endpoint
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
output "mcp_url" {
|
|
383
|
+
description = "MCP endpoint proxied to the upstream server"
|
|
384
|
+
value = "${module.http_api.api_endpoint}/mcp"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
output "metadata_url" {
|
|
388
|
+
description = "OAuth protected-resource metadata URL"
|
|
389
|
+
value = "${module.http_api.api_endpoint}/.well-known/oauth-protected-resource"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
output "auth_server_metadata_url" {
|
|
393
|
+
description = "OAuth authorization-server metadata URL"
|
|
394
|
+
value = "${module.http_api.api_endpoint}/.well-known/oauth-authorization-server"
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
output "token_endpoint" {
|
|
398
|
+
description = "OAuth token endpoint"
|
|
399
|
+
value = "${module.http_api.api_endpoint}/oauth/token"
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
output "registration_endpoint" {
|
|
403
|
+
description = "Dynamic Client Registration endpoint"
|
|
404
|
+
value = "${module.http_api.api_endpoint}/register"
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
output "api_id" {
|
|
408
|
+
description = "ID of the HTTP API Gateway"
|
|
409
|
+
value = module.http_api.api_id
|
|
410
|
+
}
|
package/src/utils/docker.d.ts
CHANGED
|
@@ -27,8 +27,9 @@ export interface DockerScanTargetOptions {
|
|
|
27
27
|
readonly imageTags: string[];
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* Add a cacheable Trivy scan target for the images built by a docker target
|
|
31
|
-
* wire it under the aggregate \`trivy\` target
|
|
30
|
+
* Add a cacheable Trivy scan target for the images built by a docker target
|
|
31
|
+
* and wire it under the aggregate \`trivy\` target. The scan is not part of
|
|
32
|
+
* \`build\` — run \`nx run-many --target trivy\` (the root \`trivy\` script) in CI.
|
|
32
33
|
*
|
|
33
34
|
* The scan target depends on the docker target and declares the project source
|
|
34
35
|
* as its inputs, so an unchanged image is never re-scanned (a cache hit skips
|
|
@@ -36,7 +37,7 @@ export interface DockerScanTargetOptions {
|
|
|
36
37
|
* \`dist/<projectRoot>/trivy/<scan-key>\` (kept out of any Docker build context)
|
|
37
38
|
* and scanned with the pinned ECR-hosted Trivy image via a workspace-relative
|
|
38
39
|
* bind mount, so the same commands work under both docker and finch. The scan
|
|
39
|
-
*
|
|
40
|
+
* exits non-zero (exit code 1) on HIGH/CRITICAL vulnerabilities.
|
|
40
41
|
*
|
|
41
42
|
* A \`.trivyignore\` is vended at the project root (kept if it already exists)
|
|
42
43
|
* for suppressing findings.
|
package/src/utils/docker.js
CHANGED
|
@@ -7,12 +7,13 @@ import { addDependencyToTargetIfNotPresent } from "./nx.js";
|
|
|
7
7
|
import { CONTAINER_VERSIONS } from "./versions.js";
|
|
8
8
|
const TRIVY_IGNORE_FILE = '.trivyignore';
|
|
9
9
|
const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to
|
|
10
|
-
# suppress it during the image scan run
|
|
10
|
+
# suppress it during the image scan (\`nx run-many --target trivy\`).
|
|
11
11
|
# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids
|
|
12
12
|
`;
|
|
13
13
|
/**
|
|
14
|
-
* Add a cacheable Trivy scan target for the images built by a docker target
|
|
15
|
-
* wire it under the aggregate \`trivy\` target
|
|
14
|
+
* Add a cacheable Trivy scan target for the images built by a docker target
|
|
15
|
+
* and wire it under the aggregate \`trivy\` target. The scan is not part of
|
|
16
|
+
* \`build\` — run \`nx run-many --target trivy\` (the root \`trivy\` script) in CI.
|
|
16
17
|
*
|
|
17
18
|
* The scan target depends on the docker target and declares the project source
|
|
18
19
|
* as its inputs, so an unchanged image is never re-scanned (a cache hit skips
|
|
@@ -20,7 +21,7 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
|
|
|
20
21
|
* \`dist/<projectRoot>/trivy/<scan-key>\` (kept out of any Docker build context)
|
|
21
22
|
* and scanned with the pinned ECR-hosted Trivy image via a workspace-relative
|
|
22
23
|
* bind mount, so the same commands work under both docker and finch. The scan
|
|
23
|
-
*
|
|
24
|
+
* exits non-zero (exit code 1) on HIGH/CRITICAL vulnerabilities.
|
|
24
25
|
*
|
|
25
26
|
* A \`.trivyignore\` is vended at the project root (kept if it already exists)
|
|
26
27
|
* for suppressing findings.
|
|
@@ -70,12 +71,15 @@ const TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.
|
|
|
70
71
|
dockerTargetName
|
|
71
72
|
]
|
|
72
73
|
};
|
|
73
|
-
// Aggregate per-component scan targets under a single `trivy` target
|
|
74
|
-
//
|
|
74
|
+
// Aggregate per-component scan targets under a single `trivy` target. The
|
|
75
|
+
// scan is intentionally NOT wired into `build`: image scanning is slow and
|
|
76
|
+
// its result depends on the ever-changing vulnerability database (a scan
|
|
77
|
+
// that passes today can fail tomorrow when a new CVE is published), so
|
|
78
|
+
// coupling it to `build` would make local and CI builds non-deterministic.
|
|
79
|
+
// Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.
|
|
75
80
|
if (trivyTargetName !== 'trivy') {
|
|
76
81
|
addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);
|
|
77
82
|
}
|
|
78
|
-
addDependencyToTargetIfNotPresent(project, 'build', 'trivy');
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
//# sourceMappingURL=docker.js.map
|
package/src/utils/docker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport { FsCommands } from './fs';\nimport { addDependencyToTargetIfNotPresent } from './nx';\nimport { CONTAINER_VERSIONS } from './versions';\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan run
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/utils/docker.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type ProjectConfiguration,\n type Tree,\n} from '@nx/devkit';\nimport { FsCommands } from './fs';\nimport { addDependencyToTargetIfNotPresent } from './nx';\nimport { CONTAINER_VERSIONS } from './versions';\n\nconst TRIVY_IGNORE_FILE = '.trivyignore';\n\nconst TRIVY_IGNORE_CONTENTS = `# Trivy ignore file. Add one vulnerability ID (e.g. CVE-2021-12345) per line to\n# suppress it during the image scan (\\`nx run-many --target trivy\\`).\n# https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids\n`;\n\nexport interface DockerScanTargetOptions {\n /**\n * Project configuration to add the scan target to (mutated in place).\n */\n readonly project: ProjectConfiguration;\n /**\n * Container engine command to invoke (\\`docker\\` or \\`finch\\`).\n */\n readonly containerEngine: string;\n /**\n * Name of the scan target to add, e.g. \\`my-agent-trivy\\`.\n */\n readonly trivyTargetName: string;\n /**\n * Name of the docker target that builds the image(s) to scan. The scan\n * target depends on it.\n */\n readonly dockerTargetName: string;\n /**\n * Tags of the images built by the docker target which should be scanned.\n */\n readonly imageTags: string[];\n}\n\n/**\n * Add a cacheable Trivy scan target for the images built by a docker target\n * and wire it under the aggregate \\`trivy\\` target. The scan is not part of\n * \\`build\\` — run \\`nx run-many --target trivy\\` (the root \\`trivy\\` script) in CI.\n *\n * The scan target depends on the docker target and declares the project source\n * as its inputs, so an unchanged image is never re-scanned (a cache hit skips\n * the scan entirely). Each image is saved to a tarball under\n * \\`dist/<projectRoot>/trivy/<scan-key>\\` (kept out of any Docker build context)\n * and scanned with the pinned ECR-hosted Trivy image via a workspace-relative\n * bind mount, so the same commands work under both docker and finch. The scan\n * exits non-zero (exit code 1) on HIGH/CRITICAL vulnerabilities.\n *\n * A \\`.trivyignore\\` is vended at the project root (kept if it already exists)\n * for suppressing findings.\n */\nexport const addDockerScanTarget = (\n tree: Tree,\n options: DockerScanTargetOptions,\n): void => {\n const { project, containerEngine, trivyTargetName, dockerTargetName } =\n options;\n const { imageTags } = options;\n const projectRoot = project.root;\n\n const ignoreFilePath = joinPathFragments(projectRoot, TRIVY_IGNORE_FILE);\n if (!tree.exists(ignoreFilePath)) {\n tree.write(ignoreFilePath, TRIVY_IGNORE_CONTENTS);\n }\n\n // Stage scan artifacts in a directory unique to this scan target so that\n // sibling targets in the same project (e.g. multiple agents) don't clobber\n // each other's tarballs when run in parallel. The first image tag is unique\n // per target, so it makes a stable, collision-free key.\n const scanKey = imageTags[0].replace(/[^a-zA-Z0-9-]/g, '-');\n const scanDir = joinPathFragments('dist', projectRoot, 'trivy', scanKey);\n const trivyImage = `public.ecr.aws/aquasecurity/trivy:${CONTAINER_VERSIONS.trivy}`;\n\n const fs = new FsCommands(tree);\n const commands = [\n fs.rm(scanDir),\n fs.mkdir(scanDir),\n fs.cp(ignoreFilePath, joinPathFragments(scanDir, TRIVY_IGNORE_FILE)),\n ];\n\n imageTags.forEach((imageTag, index) => {\n const tarName = `image-${index}.tar`;\n commands.push(\n `${containerEngine} save -o ${joinPathFragments(scanDir, tarName)} ${imageTag}`,\n `${containerEngine} run --rm -v \"./${scanDir}\":/scan ${trivyImage} image --input /scan/${tarName} --ignorefile /scan/${TRIVY_IGNORE_FILE} --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q`,\n );\n });\n\n project.targets ??= {};\n project.targets[trivyTargetName] = {\n cache: true,\n // The scanned image is fully determined by the project source, so caching\n // on the source ensures an unchanged image is never re-scanned.\n inputs: ['default', '^production'],\n outputs: [`{workspaceRoot}/${scanDir}`],\n executor: 'nx:run-commands',\n options: {\n commands,\n parallel: false,\n },\n dependsOn: [dockerTargetName],\n };\n\n // Aggregate per-component scan targets under a single `trivy` target. The\n // scan is intentionally NOT wired into `build`: image scanning is slow and\n // its result depends on the ever-changing vulnerability database (a scan\n // that passes today can fail tomorrow when a new CVE is published), so\n // coupling it to `build` would make local and CI builds non-deterministic.\n // Run `nx run-many --target trivy` (the root `trivy` script) in CI instead.\n if (trivyTargetName !== 'trivy') {\n addDependencyToTargetIfNotPresent(project, 'trivy', trivyTargetName);\n }\n};\n"],"names":["joinPathFragments","FsCommands","addDependencyToTargetIfNotPresent","CONTAINER_VERSIONS","TRIVY_IGNORE_FILE","TRIVY_IGNORE_CONTENTS","addDockerScanTarget","tree","options","project","containerEngine","trivyTargetName","dockerTargetName","imageTags","projectRoot","root","ignoreFilePath","exists","write","scanKey","replace","scanDir","trivyImage","trivy","fs","commands","rm","mkdir","cp","forEach","imageTag","index","tarName","push","targets","cache","inputs","outputs","executor","parallel","dependsOn"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SAASC,UAAU,QAAQ,UAAO;AAClC,SAASC,iCAAiC,QAAQ,UAAO;AACzD,SAASC,kBAAkB,QAAQ,gBAAa;AAEhD,MAAMC,oBAAoB;AAE1B,MAAMC,wBAAwB,CAAC;;;AAG/B,CAAC;AA0BD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,sBAAsB,CACjCC,MACAC;IAEA,MAAM,EAAEC,OAAO,EAAEC,eAAe,EAAEC,eAAe,EAAEC,gBAAgB,EAAE,GACnEJ;IACF,MAAM,EAAEK,SAAS,EAAE,GAAGL;IACtB,MAAMM,cAAcL,QAAQM,IAAI;IAEhC,MAAMC,iBAAiBhB,kBAAkBc,aAAaV;IACtD,IAAI,CAACG,KAAKU,MAAM,CAACD,iBAAiB;QAChCT,KAAKW,KAAK,CAACF,gBAAgBX;IAC7B;IAEA,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,wDAAwD;IACxD,MAAMc,UAAUN,SAAS,CAAC,EAAE,CAACO,OAAO,CAAC,kBAAkB;IACvD,MAAMC,UAAUrB,kBAAkB,QAAQc,aAAa,SAASK;IAChE,MAAMG,aAAa,CAAC,kCAAkC,EAAEnB,mBAAmBoB,KAAK,EAAE;IAElF,MAAMC,KAAK,IAAIvB,WAAWM;IAC1B,MAAMkB,WAAW;QACfD,GAAGE,EAAE,CAACL;QACNG,GAAGG,KAAK,CAACN;QACTG,GAAGI,EAAE,CAACZ,gBAAgBhB,kBAAkBqB,SAASjB;KAClD;IAEDS,UAAUgB,OAAO,CAAC,CAACC,UAAUC;QAC3B,MAAMC,UAAU,CAAC,MAAM,EAAED,MAAM,IAAI,CAAC;QACpCN,SAASQ,IAAI,CACX,GAAGvB,gBAAgB,SAAS,EAAEV,kBAAkBqB,SAASW,SAAS,CAAC,EAAEF,UAAU,EAC/E,GAAGpB,gBAAgB,gBAAgB,EAAEW,QAAQ,QAAQ,EAAEC,WAAW,qBAAqB,EAAEU,QAAQ,oBAAoB,EAAE5B,kBAAkB,yFAAyF,CAAC;IAEvO;IAEAK,QAAQyB,OAAO,KAAK,CAAC;IACrBzB,QAAQyB,OAAO,CAACvB,gBAAgB,GAAG;QACjCwB,OAAO;QACP,0EAA0E;QAC1E,gEAAgE;QAChEC,QAAQ;YAAC;YAAW;SAAc;QAClCC,SAAS;YAAC,CAAC,gBAAgB,EAAEhB,SAAS;SAAC;QACvCiB,UAAU;QACV9B,SAAS;YACPiB;YACAc,UAAU;QACZ;QACAC,WAAW;YAAC5B;SAAiB;IAC/B;IAEA,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAID,oBAAoB,SAAS;QAC/BT,kCAAkCO,SAAS,SAASE;IACtD;AACF,EAAE"}
|
package/src/utils/format.d.ts
CHANGED
package/src/utils/format.js
CHANGED
|
@@ -9,9 +9,10 @@ import { createRequire } from "module";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { uvxCommand } from "./py.js";
|
|
11
11
|
import { readToml } from "./toml.js";
|
|
12
|
+
import { TS_VERSIONS } from "./versions.js";
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
export const DEFAULT_BIOME_CONFIG = {
|
|
14
|
-
$schema:
|
|
15
|
+
$schema: `https://biomejs.dev/schemas/${TS_VERSIONS['@biomejs/biome']}/schema.json`,
|
|
15
16
|
root: true,
|
|
16
17
|
formatter: {
|
|
17
18
|
enabled: true,
|
|
@@ -36,7 +37,7 @@ export const DEFAULT_BIOME_CONFIG = {
|
|
|
36
37
|
linter: {
|
|
37
38
|
enabled: true,
|
|
38
39
|
rules: {
|
|
39
|
-
|
|
40
|
+
preset: 'none',
|
|
40
41
|
correctness: {
|
|
41
42
|
noUndeclaredDependencies: 'warn'
|
|
42
43
|
}
|