@aws/nx-plugin 1.0.0-rc.3 → 1.0.0-rc.31
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/LICENSE-THIRD-PARTY +6746 -6133
- package/README.md +7 -7
- package/executors.json +9 -0
- package/generators.json +100 -1
- package/package.json +35 -17
- package/src/agentcore-gateway/attach-upstream.d.ts +33 -0
- package/src/agentcore-gateway/attach-upstream.js +38 -0
- package/src/agentcore-gateway/attach-upstream.js.map +1 -0
- package/src/agentcore-gateway/files/cedar/policies/README.md +52 -0
- package/src/agentcore-gateway/files/cedar/policies/permit-all.cedar +11 -0
- package/src/agentcore-gateway/files/project/local-dev.ts.template +134 -0
- package/src/agentcore-gateway/gateway-connection/generator.d.ts +21 -0
- package/src/agentcore-gateway/gateway-connection/generator.js +101 -0
- package/src/agentcore-gateway/gateway-connection/generator.js.map +1 -0
- package/src/agentcore-gateway/gateway-connection/schema.d.js +6 -0
- package/src/agentcore-gateway/gateway-connection/schema.d.js.map +1 -0
- package/src/agentcore-gateway/gateway-connection/schema.d.ts +13 -0
- package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
- package/src/agentcore-gateway/generator.d.ts +25 -0
- package/src/agentcore-gateway/generator.js +149 -0
- package/src/agentcore-gateway/generator.js.map +1 -0
- package/src/agentcore-gateway/mcp-connection/generator.d.ts +19 -0
- package/src/agentcore-gateway/mcp-connection/generator.js +59 -0
- package/src/agentcore-gateway/mcp-connection/generator.js.map +1 -0
- package/src/agentcore-gateway/mcp-connection/schema.d.js +6 -0
- package/src/agentcore-gateway/mcp-connection/schema.d.js.map +1 -0
- package/src/agentcore-gateway/mcp-connection/schema.d.ts +13 -0
- package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
- package/src/agentcore-gateway/schema.d.js +6 -0
- package/src/agentcore-gateway/schema.d.js.map +1 -0
- package/src/agentcore-gateway/schema.d.ts +17 -0
- package/src/agentcore-gateway/schema.json +70 -0
- package/src/bin/aws-nx-mcp.js +14 -0
- package/src/bin/aws-nx-mcp.js.map +1 -0
- package/src/connection/{agent-serve-local.d.ts → agent-local-dev.d.ts} +7 -7
- package/src/connection/agent-local-dev.js +42 -0
- package/src/connection/agent-local-dev.js.map +1 -0
- package/src/connection/agent-runtime-config.d.ts +1 -1
- package/src/connection/agent-runtime-config.js +9 -14
- package/src/connection/agent-runtime-config.js.map +1 -1
- package/src/connection/generator.d.ts +7 -14
- package/src/connection/generator.js +158 -177
- package/src/connection/generator.js.map +1 -1
- package/src/connection/local-dev.d.ts +15 -0
- package/src/connection/local-dev.js +37 -0
- package/src/connection/local-dev.js.map +1 -0
- package/src/connection/schema.d.js +6 -0
- package/src/connection/schema.d.js.map +1 -0
- package/src/connection/schema.d.ts +1 -0
- package/src/connection/schema.json +5 -0
- package/src/connection/supported-connections.d.ts +98 -0
- package/src/connection/supported-connections.js +138 -0
- package/src/connection/supported-connections.js.map +1 -0
- package/src/index.js +2 -5
- package/src/index.js.map +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +51 -81
- package/src/infra/app/generator.d.ts +3 -3
- package/src/infra/app/generator.js +141 -123
- package/src/infra/app/generator.js.map +1 -1
- package/src/infra/app/schema.d.js +6 -0
- package/src/infra/app/schema.d.js.map +1 -0
- package/src/infra/app/schema.d.ts +1 -1
- package/src/infra/app/schema.json +5 -0
- package/src/license/__snapshots__/config.spec.ts.snap +23 -0
- package/src/license/config-types.d.ts +18 -1
- package/src/license/config-types.js +8 -2
- package/src/license/config-types.js.map +1 -1
- package/src/license/config.d.ts +65 -7
- package/src/license/config.js +442 -109
- package/src/license/config.js.map +1 -1
- package/src/license/dependency-check/check.d.ts +16 -0
- package/src/license/dependency-check/check.js +116 -0
- package/src/license/dependency-check/check.js.map +1 -0
- package/src/license/dependency-check/collectors/collector.d.ts +21 -0
- package/src/license/dependency-check/collectors/collector.js +46 -0
- package/src/license/dependency-check/collectors/collector.js.map +1 -0
- package/src/license/dependency-check/collectors/npm-collector.d.ts +10 -0
- package/src/license/dependency-check/collectors/npm-collector.js +68 -0
- package/src/license/dependency-check/collectors/npm-collector.js.map +1 -0
- package/src/license/dependency-check/collectors/python-collector.d.ts +23 -0
- package/src/license/dependency-check/collectors/python-collector.js +114 -0
- package/src/license/dependency-check/collectors/python-collector.js.map +1 -0
- package/src/license/dependency-check/evaluator.d.ts +9 -0
- package/src/license/dependency-check/evaluator.js +94 -0
- package/src/license/dependency-check/evaluator.js.map +1 -0
- package/src/license/dependency-check/executor-schema.json +6 -0
- package/src/license/dependency-check/executor.d.ts +9 -0
- package/src/license/dependency-check/executor.js +51 -0
- package/src/license/dependency-check/executor.js.map +1 -0
- package/src/license/dependency-check/pre-approved.d.ts +6 -0
- package/src/license/dependency-check/pre-approved.js +1893 -0
- package/src/license/dependency-check/pre-approved.js.map +1 -0
- package/src/license/dependency-check/types.d.ts +45 -0
- package/src/license/dependency-check/types.js +6 -0
- package/src/license/dependency-check/types.js.map +1 -0
- package/src/license/generator.d.ts +3 -3
- package/src/license/generator.js +127 -30
- package/src/license/generator.js.map +1 -1
- package/src/license/index.d.ts +8 -0
- package/src/license/index.js +7 -0
- package/src/license/index.js.map +1 -0
- package/src/license/known-exceptions.d.ts +7 -0
- package/src/license/known-exceptions.js +39 -0
- package/src/license/known-exceptions.js.map +1 -0
- package/src/license/schema.d.js +6 -0
- package/src/license/schema.d.js.map +1 -0
- package/src/license/schema.d.ts +7 -0
- package/src/license/schema.json +11 -0
- package/src/license/sync/generator.d.ts +2 -2
- package/src/license/sync/generator.js +100 -125
- package/src/license/sync/generator.js.map +1 -1
- package/src/license/sync/project-file-sync.d.ts +3 -3
- package/src/license/sync/project-file-sync.js +28 -35
- package/src/license/sync/project-file-sync.js.map +1 -1
- package/src/mcp-server/generator-info.d.ts +5 -1
- package/src/mcp-server/generator-info.js +130 -160
- package/src/mcp-server/generator-info.js.map +1 -1
- package/src/mcp-server/guide-pipeline.d.ts +1 -1
- package/src/mcp-server/guide-pipeline.js +184 -206
- package/src/mcp-server/guide-pipeline.js.map +1 -1
- package/src/mcp-server/guide-render.js +10 -25
- package/src/mcp-server/guide-render.js.map +1 -1
- package/src/mcp-server/index.js +6 -10
- package/src/mcp-server/index.js.map +1 -1
- package/src/mcp-server/mdx-ast.js +42 -56
- package/src/mcp-server/mdx-ast.js.map +1 -1
- package/src/mcp-server/option-filter.js +33 -47
- package/src/mcp-server/option-filter.js.map +1 -1
- package/src/mcp-server/schema-registry.d.ts +9 -3
- package/src/mcp-server/schema-registry.js +41 -38
- package/src/mcp-server/schema-registry.js.map +1 -1
- package/src/mcp-server/schema.js +9 -7
- package/src/mcp-server/schema.js.map +1 -1
- package/src/mcp-server/server.js +20 -24
- package/src/mcp-server/server.js.map +1 -1
- package/src/mcp-server/tools/create-workspace-command.d.ts +1 -1
- package/src/mcp-server/tools/create-workspace-command.js +23 -24
- package/src/mcp-server/tools/create-workspace-command.js.map +1 -1
- package/src/mcp-server/tools/general-guidance.d.ts +2 -2
- package/src/mcp-server/tools/general-guidance.js +46 -25
- package/src/mcp-server/tools/general-guidance.js.map +1 -1
- package/src/mcp-server/tools/generator-guide.d.ts +2 -2
- package/src/mcp-server/tools/generator-guide.js +29 -37
- package/src/mcp-server/tools/generator-guide.js.map +1 -1
- package/src/mcp-server/tools/list-generators.d.ts +2 -2
- package/src/mcp-server/tools/list-generators.js +19 -25
- package/src/mcp-server/tools/list-generators.js.map +1 -1
- package/src/open-api/ts-client/__snapshots__/generator.additional-properties.spec.ts.snap +147 -33
- package/src/open-api/ts-client/__snapshots__/generator.arrays.spec.ts.snap +308 -75
- package/src/open-api/ts-client/__snapshots__/generator.complex-types.spec.ts.snap +286 -62
- package/src/open-api/ts-client/__snapshots__/generator.composite-types.spec.ts.snap +674 -93
- package/src/open-api/ts-client/__snapshots__/generator.content-type.spec.ts.snap +147 -33
- package/src/open-api/ts-client/__snapshots__/generator.duplicate-types.spec.ts.snap +196 -44
- package/src/open-api/ts-client/__snapshots__/generator.edge-cases.spec.ts.snap +5507 -0
- package/src/open-api/ts-client/__snapshots__/generator.fast-api.spec.ts.snap +219 -60
- package/src/open-api/ts-client/__snapshots__/generator.primitive-types.spec.ts.snap +351 -80
- package/src/open-api/ts-client/__snapshots__/generator.request.spec.ts.snap +170 -49
- package/src/open-api/ts-client/__snapshots__/generator.reserved-keywords.spec.ts.snap +99 -23
- package/src/open-api/ts-client/__snapshots__/generator.response.spec.ts.snap +147 -33
- package/src/open-api/ts-client/__snapshots__/generator.streaming.spec.ts.snap +392 -88
- package/src/open-api/ts-client/__snapshots__/generator.tags.spec.ts.snap +196 -44
- package/src/open-api/ts-client/files/client.gen.ts.template +130 -18
- package/src/open-api/ts-client/files/types.gen.ts.template +4 -4
- package/src/open-api/ts-client/generator.d.ts +3 -3
- package/src/open-api/ts-client/generator.js +16 -26
- package/src/open-api/ts-client/generator.js.map +1 -1
- package/src/open-api/ts-client/schema.d.js +6 -0
- package/src/open-api/ts-client/schema.d.js.map +1 -0
- package/src/open-api/ts-hooks/files/options-proxy.gen.ts.template +5 -0
- package/src/open-api/ts-hooks/generator.d.ts +3 -3
- package/src/open-api/ts-hooks/generator.js +13 -21
- package/src/open-api/ts-hooks/generator.js.map +1 -1
- package/src/open-api/ts-hooks/generator.spec.tsx +84 -14
- package/src/open-api/ts-hooks/schema.d.js +6 -0
- package/src/open-api/ts-hooks/schema.d.js.map +1 -0
- package/src/open-api/ts-metadata/generator.d.ts +3 -3
- package/src/open-api/ts-metadata/generator.js +12 -20
- package/src/open-api/ts-metadata/generator.js.map +1 -1
- package/src/open-api/ts-metadata/schema.d.js +6 -0
- package/src/open-api/ts-metadata/schema.d.js.map +1 -0
- package/src/open-api/utils/codegen-data/languages.d.ts +3 -5
- package/src/open-api/utils/codegen-data/languages.js +53 -66
- package/src/open-api/utils/codegen-data/languages.js.map +1 -1
- package/src/open-api/utils/codegen-data/types.d.ts +240 -10
- package/src/open-api/utils/codegen-data/types.js +54 -30
- package/src/open-api/utils/codegen-data/types.js.map +1 -1
- package/src/open-api/utils/codegen-data.d.ts +8 -4
- package/src/open-api/utils/codegen-data.js +398 -674
- package/src/open-api/utils/codegen-data.js.map +1 -1
- package/src/open-api/utils/normalise.d.ts +5 -1
- package/src/open-api/utils/normalise.js +386 -247
- package/src/open-api/utils/normalise.js.map +1 -1
- package/src/open-api/utils/parse.d.ts +2 -2
- package/src/open-api/utils/parse.js +11 -13
- package/src/open-api/utils/parse.js.map +1 -1
- package/src/open-api/utils/parser.d.ts +55 -0
- package/src/open-api/utils/parser.js +743 -0
- package/src/open-api/utils/parser.js.map +1 -0
- package/src/open-api/utils/refs.d.ts +1 -1
- package/src/open-api/utils/refs.js +12 -22
- package/src/open-api/utils/refs.js.map +1 -1
- package/src/open-api/utils/types.d.ts +15 -0
- package/src/open-api/utils/types.js +5 -2
- package/src/open-api/utils/types.js.map +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +160 -17
- package/src/preset/generator.d.ts +4 -4
- package/src/preset/generator.js +140 -112
- package/src/preset/generator.js.map +1 -1
- package/src/preset/schema.d.js +6 -0
- package/src/preset/schema.d.js.map +1 -0
- package/src/preset/schema.d.ts +4 -2
- package/src/preset/schema.json +10 -5
- package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +304 -0
- package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +734 -0
- package/src/py/agent/__snapshots__/{generator.spec.ts.snap → generator.protocols.spec.ts.snap} +80 -314
- package/src/py/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +189 -0
- package/src/py/agent/a2a-connection/files/agent-connection/app/__targetAgentSnakeCase___client_strands.py.template +30 -0
- package/src/py/agent/a2a-connection/files/langchain/agent-connection/app/__targetAgentSnakeCase___client_langchain.py.template +28 -0
- package/src/py/agent/a2a-connection/generator.d.ts +3 -3
- package/src/py/agent/a2a-connection/generator.js +113 -85
- package/src/py/agent/a2a-connection/generator.js.map +1 -1
- package/src/py/agent/a2a-connection/schema.d.js +8 -0
- package/src/py/agent/a2a-connection/schema.d.js.map +1 -0
- package/src/py/agent/a2a-connection/schema.d.ts +1 -0
- package/src/py/agent/a2a-connection/schema.json +5 -0
- package/src/py/agent/files/a2a-langchain/main.py.template +89 -0
- package/src/py/agent/files/ag-ui-langchain/main.py.template +83 -0
- package/src/py/agent/files/common/agent.py.template +2 -0
- package/src/py/agent/files/common-langchain/agent.py.template +30 -0
- package/src/py/agent/files/http/init.py.template +7 -25
- package/src/py/agent/files/http/main.py.template +2 -3
- package/src/py/agent/files/http-langchain/main.py.template +84 -0
- package/src/py/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +184 -0
- package/src/py/agent/gateway-connection/files/agent-connection/app/__gatewaySnakeCase___client_strands.py.template +35 -0
- package/src/py/agent/gateway-connection/files/langchain/agent-connection/app/__gatewaySnakeCase___client_langchain.py.template +34 -0
- package/src/py/agent/gateway-connection/generator.d.ts +10 -0
- package/src/py/agent/gateway-connection/generator.js +119 -0
- package/src/py/agent/gateway-connection/generator.js.map +1 -0
- package/src/py/agent/gateway-connection/schema.d.js +6 -0
- package/src/py/agent/gateway-connection/schema.d.js.map +1 -0
- package/src/py/agent/gateway-connection/schema.d.ts +13 -0
- package/src/py/agent/gateway-connection/schema.json +31 -0
- package/src/py/agent/generator.d.ts +3 -3
- package/src/py/agent/generator.js +280 -180
- package/src/py/agent/generator.js.map +1 -1
- package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +153 -89
- package/src/py/agent/mcp-connection/files/agent-connection/app/__mcpServerSnakeCase___client_strands.py.template +30 -0
- package/src/py/agent/mcp-connection/files/langchain/agent-connection/app/__mcpServerSnakeCase___client_langchain.py.template +30 -0
- package/src/py/agent/mcp-connection/generator.d.ts +3 -3
- package/src/py/agent/mcp-connection/generator.js +65 -126
- package/src/py/agent/mcp-connection/generator.js.map +1 -1
- package/src/py/agent/mcp-connection/schema.d.js +8 -0
- package/src/py/agent/mcp-connection/schema.d.js.map +1 -0
- package/src/py/agent/mcp-connection/schema.d.ts +1 -0
- package/src/py/agent/mcp-connection/schema.json +5 -0
- package/src/py/agent/react-connection/__snapshots__/generator.spec.ts.snap +11 -19
- package/src/py/agent/react-connection/generator.d.ts +4 -4
- package/src/py/agent/react-connection/generator.js +55 -59
- package/src/py/agent/react-connection/generator.js.map +1 -1
- package/src/py/agent/react-connection/{serve-local.d.ts → local-dev.d.ts} +6 -6
- package/src/py/agent/react-connection/local-dev.js +36 -0
- package/src/py/agent/react-connection/local-dev.js.map +1 -0
- package/src/py/agent/react-connection/schema.d.js +8 -0
- package/src/py/agent/react-connection/schema.d.js.map +1 -0
- package/src/py/agent/react-connection/schema.d.ts +1 -0
- package/src/py/agent/react-connection/schema.json +5 -0
- package/src/py/agent/schema.d.js +6 -0
- package/src/py/agent/schema.d.js.map +1 -0
- package/src/py/agent/schema.d.ts +2 -1
- package/src/py/agent/schema.json +6 -1
- package/src/py/api/generator.d.ts +3 -3
- package/src/py/api/generator.js +12 -11
- package/src/py/api/generator.js.map +1 -1
- package/src/py/api/schema.d.js +6 -0
- package/src/py/api/schema.d.js.map +1 -0
- package/src/py/api/schema.d.ts +1 -0
- package/src/py/api/schema.json +5 -0
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +1526 -0
- package/src/py/dynamodb/agent-connection/__snapshots__/generator.spec.ts.snap +45 -0
- package/src/py/dynamodb/agent-connection/generator.d.ts +10 -0
- package/src/py/dynamodb/agent-connection/generator.js +39 -0
- package/src/py/dynamodb/agent-connection/generator.js.map +1 -0
- package/src/py/dynamodb/agent-connection/schema.d.js +8 -0
- package/src/py/dynamodb/agent-connection/schema.d.js.map +1 -0
- package/src/py/dynamodb/agent-connection/schema.d.ts +15 -0
- package/src/py/dynamodb/agent-connection/schema.json +27 -0
- package/src/py/dynamodb/fast-api-connection/__snapshots__/generator.spec.ts.snap +23 -0
- package/src/py/dynamodb/fast-api-connection/generator.d.ts +10 -0
- package/src/py/dynamodb/fast-api-connection/generator.js +37 -0
- package/src/py/dynamodb/fast-api-connection/generator.js.map +1 -0
- package/src/py/dynamodb/fast-api-connection/schema.d.js +8 -0
- package/src/py/dynamodb/fast-api-connection/schema.d.js.map +1 -0
- package/src/py/dynamodb/fast-api-connection/schema.d.ts +13 -0
- package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
- package/src/py/dynamodb/files/__name__/__init__.py.template +6 -0
- package/src/py/dynamodb/files/__name__/client.py.template +39 -0
- package/src/py/dynamodb/files/__name__/entities/__init__.py.template +4 -0
- package/src/py/dynamodb/files/__name__/entities/base.py.template +63 -0
- package/src/py/dynamodb/files/__name__/entities/example.py.template +64 -0
- package/src/py/dynamodb/files/config.json.template +24 -0
- package/src/py/dynamodb/generator.d.ts +10 -0
- package/src/py/dynamodb/generator.js +110 -0
- package/src/py/dynamodb/generator.js.map +1 -0
- package/src/py/dynamodb/mcp-server-connection/__snapshots__/generator.spec.ts.snap +45 -0
- package/src/py/dynamodb/mcp-server-connection/generator.d.ts +10 -0
- package/src/py/dynamodb/mcp-server-connection/generator.js +39 -0
- package/src/py/dynamodb/mcp-server-connection/generator.js.map +1 -0
- package/src/py/dynamodb/mcp-server-connection/schema.d.js +8 -0
- package/src/py/dynamodb/mcp-server-connection/schema.d.js.map +1 -0
- package/src/py/dynamodb/mcp-server-connection/schema.d.ts +15 -0
- package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
- package/src/py/dynamodb/schema.d.js +6 -0
- package/src/py/dynamodb/schema.d.js.map +1 -0
- package/src/py/dynamodb/schema.d.ts +16 -0
- package/src/py/dynamodb/schema.json +75 -0
- package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +212 -4678
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +4833 -0
- package/src/py/fast-api/files/app/__name__/init.py.template +5 -22
- package/src/py/fast-api/files/app/__name__/main.py.template +4 -1
- package/src/py/fast-api/generator.d.ts +3 -3
- package/src/py/fast-api/generator.js +90 -87
- package/src/py/fast-api/generator.js.map +1 -1
- package/src/py/fast-api/react/__snapshots__/generator.spec.ts.snap +4 -3
- package/src/py/fast-api/react/generator.d.ts +4 -4
- package/src/py/fast-api/react/generator.js +25 -25
- package/src/py/fast-api/react/generator.js.map +1 -1
- package/src/py/fast-api/react/open-api.d.ts +1 -1
- package/src/py/fast-api/react/open-api.js +31 -29
- package/src/py/fast-api/react/open-api.js.map +1 -1
- package/src/py/fast-api/react/schema.d.js +6 -0
- package/src/py/fast-api/react/schema.d.js.map +1 -0
- package/src/py/fast-api/react/schema.d.ts +1 -0
- package/src/py/fast-api/react/schema.json +5 -0
- package/src/py/fast-api/schema.d.js +6 -0
- package/src/py/fast-api/schema.d.js.map +1 -0
- package/src/py/fast-api/schema.d.ts +1 -0
- package/src/py/fast-api/schema.json +5 -0
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/py/lambda-function/generator.d.ts +3 -3
- package/src/py/lambda-function/generator.js +60 -59
- package/src/py/lambda-function/generator.js.map +1 -1
- package/src/py/lambda-function/schema.d.js +6 -0
- package/src/py/lambda-function/schema.d.js.map +1 -0
- package/src/py/lambda-function/schema.d.ts +1 -0
- package/src/py/lambda-function/schema.json +5 -0
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +87 -17
- package/src/py/mcp-server/generator.d.ts +3 -3
- package/src/py/mcp-server/generator.js +152 -125
- package/src/py/mcp-server/generator.js.map +1 -1
- package/src/py/mcp-server/schema.d.js +6 -0
- package/src/py/mcp-server/schema.d.js.map +1 -0
- package/src/py/mcp-server/schema.d.ts +1 -0
- package/src/py/mcp-server/schema.json +5 -0
- package/src/py/project/generator.d.ts +17 -4
- package/src/py/project/generator.js +205 -127
- package/src/py/project/generator.js.map +1 -1
- package/src/py/project/schema.d.js +6 -0
- package/src/py/project/schema.d.js.map +1 -0
- package/src/py/project/schema.d.ts +1 -0
- package/src/py/project/schema.json +5 -0
- package/src/sdk/agentcore-gateway.d.ts +6 -0
- package/src/sdk/agentcore-gateway.js +7 -0
- package/src/sdk/agentcore-gateway.js.map +1 -0
- package/src/sdk/connection.d.ts +6 -0
- package/src/sdk/connection.js +6 -0
- package/src/sdk/connection.js.map +1 -0
- package/src/sdk/license.d.ts +8 -0
- package/src/sdk/license.js +7 -0
- package/src/sdk/license.js.map +1 -0
- package/src/sdk/open-api.d.ts +10 -0
- package/src/sdk/open-api.js +8 -0
- package/src/sdk/open-api.js.map +1 -0
- package/src/sdk/py.d.ts +14 -0
- package/src/sdk/py.js +10 -0
- package/src/sdk/py.js.map +1 -0
- package/src/sdk/terraform.d.ts +6 -0
- package/src/sdk/terraform.js +7 -0
- package/src/sdk/terraform.js.map +1 -0
- package/src/sdk/ts.d.ts +33 -0
- package/src/sdk/ts.js +35 -0
- package/src/sdk/ts.js.map +1 -0
- package/{sdk → src/sdk}/utils/ast.d.ts +1 -2
- package/src/sdk/utils/ast.js +6 -0
- package/src/sdk/utils/ast.js.map +1 -0
- package/{sdk → src/sdk}/utils/format.d.ts +1 -1
- package/src/sdk/utils/format.js +6 -0
- package/src/sdk/utils/format.js.map +1 -0
- package/{sdk → src/sdk}/utils/test.d.ts +1 -1
- package/src/sdk/utils/test.js +6 -0
- package/src/sdk/utils/test.js.map +1 -0
- package/src/smithy/project/__snapshots__/generator.spec.ts.snap +19 -24
- package/src/smithy/project/files/build.Dockerfile.template +1 -4
- package/src/smithy/project/files/src/operations/echo.smithy.template +1 -0
- package/src/smithy/project/generator.d.ts +3 -3
- package/src/smithy/project/generator.js +64 -57
- package/src/smithy/project/generator.js.map +1 -1
- package/src/smithy/project/schema.d.js +6 -0
- package/src/smithy/project/schema.d.js.map +1 -0
- package/src/smithy/project/schema.d.ts +1 -0
- package/src/smithy/project/schema.json +5 -0
- package/src/smithy/react-connection/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/smithy/react-connection/generator.d.ts +3 -3
- package/src/smithy/react-connection/generator.js +39 -47
- package/src/smithy/react-connection/generator.js.map +1 -1
- package/src/smithy/react-connection/schema.d.js +6 -0
- package/src/smithy/react-connection/schema.d.js.map +1 -0
- package/src/smithy/react-connection/schema.d.ts +1 -0
- package/src/smithy/react-connection/schema.json +5 -0
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +409 -106
- package/src/smithy/ts/api/generator.d.ts +3 -3
- package/src/smithy/ts/api/generator.js +127 -106
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/smithy/ts/api/schema.d.js +6 -0
- package/src/smithy/ts/api/schema.d.js.map +1 -0
- package/src/smithy/ts/api/schema.d.ts +1 -0
- package/src/smithy/ts/api/schema.json +5 -0
- package/src/terraform/project/files/application/src/main.tf.template +6 -1
- package/src/terraform/project/files/application/src/providers.tf.template +1 -1
- package/src/terraform/project/generator.d.ts +3 -3
- package/src/terraform/project/generator.js +156 -104
- package/src/terraform/project/generator.js.map +1 -1
- package/src/terraform/project/schema.d.js +9 -0
- package/src/terraform/project/schema.d.js.map +1 -0
- package/src/terraform/project/schema.d.ts +1 -0
- package/src/terraform/project/schema.json +5 -0
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +499 -110
- package/src/trpc/backend/files/src/procedures/echo.ts.template +1 -1
- package/src/trpc/backend/files/src/schema/echo.ts.template +2 -2
- package/src/trpc/backend/generator.d.ts +4 -4
- package/src/trpc/backend/generator.js +97 -85
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/trpc/backend/schema.d.js +6 -0
- package/src/trpc/backend/schema.d.js.map +1 -0
- package/src/trpc/backend/schema.d.ts +3 -2
- package/src/trpc/backend/schema.json +5 -0
- package/src/trpc/react/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/trpc/react/files/src/components/__apiNameClassName__ClientProvider.tsx.template +3 -3
- package/src/trpc/react/generator.d.ts +4 -4
- package/src/trpc/react/generator.js +63 -58
- package/src/trpc/react/generator.js.map +1 -1
- package/src/trpc/react/schema.d.js +6 -0
- package/src/trpc/react/schema.d.js.map +1 -0
- package/src/trpc/react/schema.d.ts +1 -0
- package/src/trpc/react/schema.json +5 -0
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +656 -18
- package/src/ts/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +256 -0
- package/src/ts/agent/a2a-connection/files/agent-connection/app/__targetAgentKebabCase__-client-strands.ts.template +23 -0
- package/src/ts/agent/a2a-connection/generator.d.ts +3 -3
- package/src/ts/agent/a2a-connection/generator.js +63 -56
- package/src/ts/agent/a2a-connection/generator.js.map +1 -1
- package/src/ts/agent/a2a-connection/schema.d.js +8 -0
- package/src/ts/agent/a2a-connection/schema.d.js.map +1 -0
- package/src/ts/agent/a2a-connection/schema.d.ts +1 -0
- package/src/ts/agent/a2a-connection/schema.json +5 -0
- package/src/ts/agent/files/common/agent.ts.template +6 -2
- package/src/ts/agent/files/http/router.ts.template +1 -1
- package/src/ts/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +220 -0
- package/src/ts/agent/gateway-connection/files/agent-connection/app/__gatewayKebabCase__-client-strands.ts.template +30 -0
- package/src/ts/agent/gateway-connection/generator.d.ts +10 -0
- package/src/ts/agent/gateway-connection/generator.js +114 -0
- package/src/ts/agent/gateway-connection/generator.js.map +1 -0
- package/src/ts/agent/gateway-connection/schema.d.js +6 -0
- package/src/ts/agent/gateway-connection/schema.d.js.map +1 -0
- package/src/ts/agent/gateway-connection/schema.d.ts +13 -0
- package/src/ts/agent/gateway-connection/schema.json +31 -0
- package/src/ts/agent/generator.d.ts +3 -3
- package/src/ts/agent/generator.js +188 -144
- package/src/ts/agent/generator.js.map +1 -1
- package/src/ts/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +192 -111
- package/src/ts/agent/mcp-connection/files/agent-connection/app/__mcpServerKebabCase__-client-strands.ts.template +22 -0
- package/src/ts/agent/mcp-connection/generator.d.ts +3 -3
- package/src/ts/agent/mcp-connection/generator.js +53 -60
- package/src/ts/agent/mcp-connection/generator.js.map +1 -1
- package/src/ts/agent/mcp-connection/schema.d.js +8 -0
- package/src/ts/agent/mcp-connection/schema.d.js.map +1 -0
- package/src/ts/agent/mcp-connection/schema.d.ts +1 -0
- package/src/ts/agent/mcp-connection/schema.json +5 -0
- package/src/ts/agent/react-connection/generator.d.ts +4 -4
- package/src/ts/agent/react-connection/generator.js +86 -80
- package/src/ts/agent/react-connection/generator.js.map +1 -1
- package/src/ts/agent/react-connection/local-dev.d.ts +13 -0
- package/src/ts/agent/react-connection/local-dev.js +17 -0
- package/src/ts/agent/react-connection/local-dev.js.map +1 -0
- package/src/ts/agent/react-connection/schema.d.js +8 -0
- package/src/ts/agent/react-connection/schema.d.js.map +1 -0
- package/src/ts/agent/react-connection/schema.d.ts +1 -0
- package/src/ts/agent/react-connection/schema.json +5 -0
- package/src/ts/agent/schema.d.js +6 -0
- package/src/ts/agent/schema.d.js.map +1 -0
- package/src/ts/agent/schema.d.ts +1 -0
- package/src/ts/agent/schema.json +5 -0
- package/src/ts/api/generator.d.ts +3 -3
- package/src/ts/api/generator.js +16 -16
- package/src/ts/api/generator.js.map +1 -1
- package/src/ts/api/schema.d.js +6 -0
- package/src/ts/api/schema.d.js.map +1 -0
- package/src/ts/api/schema.d.ts +3 -1
- package/src/ts/api/schema.json +5 -0
- package/src/ts/astro-docs/__snapshots__/generator.spec.ts.snap +15 -16
- package/src/ts/astro-docs/generator.d.ts +3 -3
- package/src/ts/astro-docs/generator.js +69 -54
- package/src/ts/astro-docs/generator.js.map +1 -1
- package/src/ts/astro-docs/schema.d.js +6 -0
- package/src/ts/astro-docs/schema.d.js.map +1 -0
- package/src/ts/astro-docs/schema.d.ts +2 -2
- package/src/ts/astro-docs/schema.json +3 -3
- package/src/ts/docs/generator.d.ts +3 -3
- package/src/ts/docs/generator.js +10 -10
- package/src/ts/docs/generator.js.map +1 -1
- package/src/ts/docs/schema.d.js +6 -0
- package/src/ts/docs/schema.d.js.map +1 -0
- package/src/ts/docs/schema.d.ts +1 -4
- package/src/ts/docs/schema.json +3 -3
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +1390 -0
- package/src/ts/dynamodb/agent-connection/__snapshots__/generator.spec.ts.snap +45 -0
- package/src/ts/dynamodb/agent-connection/generator.d.ts +10 -0
- package/src/ts/dynamodb/agent-connection/generator.js +30 -0
- package/src/ts/dynamodb/agent-connection/generator.js.map +1 -0
- package/src/ts/dynamodb/agent-connection/schema.d.js +8 -0
- package/src/ts/dynamodb/agent-connection/schema.d.js.map +1 -0
- package/src/ts/dynamodb/agent-connection/schema.d.ts +15 -0
- package/src/ts/dynamodb/agent-connection/schema.json +27 -0
- package/src/ts/dynamodb/files/config.json.template +24 -0
- package/src/ts/dynamodb/files/src/client.ts.template +61 -0
- package/src/ts/dynamodb/files/src/entities/example.ts.template +47 -0
- package/src/ts/dynamodb/files/src/entities/index.ts.template +1 -0
- package/src/ts/dynamodb/files/src/index.ts.template +2 -0
- package/src/ts/dynamodb/generator.d.ts +10 -0
- package/src/ts/dynamodb/generator.js +116 -0
- package/src/ts/dynamodb/generator.js.map +1 -0
- package/src/ts/dynamodb/mcp-server-connection/__snapshots__/generator.spec.ts.snap +45 -0
- package/src/ts/dynamodb/mcp-server-connection/generator.d.ts +10 -0
- package/src/ts/dynamodb/mcp-server-connection/generator.js +30 -0
- package/src/ts/dynamodb/mcp-server-connection/generator.js.map +1 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.d.js +8 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.d.js.map +1 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.d.ts +15 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.json +27 -0
- package/src/ts/dynamodb/schema.d.js +6 -0
- package/src/ts/dynamodb/schema.d.js.map +1 -0
- package/src/ts/dynamodb/schema.d.ts +16 -0
- package/src/ts/dynamodb/schema.json +75 -0
- package/src/ts/dynamodb/smithy-connection/__snapshots__/generator.spec.ts.snap +23 -0
- package/src/ts/dynamodb/smithy-connection/generator.d.ts +10 -0
- package/src/ts/dynamodb/smithy-connection/generator.js +28 -0
- package/src/ts/dynamodb/smithy-connection/generator.js.map +1 -0
- package/src/ts/dynamodb/smithy-connection/schema.d.js +8 -0
- package/src/ts/dynamodb/smithy-connection/schema.d.js.map +1 -0
- package/src/ts/dynamodb/smithy-connection/schema.d.ts +13 -0
- package/src/ts/dynamodb/smithy-connection/schema.json +23 -0
- package/src/ts/dynamodb/trpc-connection/__snapshots__/generator.spec.ts.snap +23 -0
- package/src/ts/dynamodb/trpc-connection/generator.d.ts +10 -0
- package/src/ts/dynamodb/trpc-connection/generator.js +28 -0
- package/src/ts/dynamodb/trpc-connection/generator.js.map +1 -0
- package/src/ts/dynamodb/trpc-connection/schema.d.js +8 -0
- package/src/ts/dynamodb/trpc-connection/schema.d.js.map +1 -0
- package/src/ts/dynamodb/trpc-connection/schema.d.ts +13 -0
- package/src/ts/dynamodb/trpc-connection/schema.json +23 -0
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +4 -4
- package/src/ts/lambda-function/generator.d.ts +3 -3
- package/src/ts/lambda-function/generator.js +59 -56
- package/src/ts/lambda-function/generator.js.map +1 -1
- package/src/ts/lambda-function/io.js +83 -52
- package/src/ts/lambda-function/io.js.map +1 -1
- package/src/ts/lambda-function/schema.d.js +6 -0
- package/src/ts/lambda-function/schema.d.js.map +1 -0
- package/src/ts/lambda-function/schema.d.ts +1 -0
- package/src/ts/lambda-function/schema.json +5 -0
- package/src/ts/lib/__snapshots__/generator.spec.ts.snap +53 -99
- package/src/ts/lib/__snapshots__/vitest.spec.ts.snap +24 -0
- package/src/ts/lib/biome.d.ts +7 -0
- package/src/ts/lib/biome.js +58 -0
- package/src/ts/lib/biome.js.map +1 -0
- package/src/ts/lib/generator.d.ts +3 -3
- package/src/ts/lib/generator.js +96 -96
- package/src/ts/lib/generator.js.map +1 -1
- package/src/ts/lib/grit/vitest-pass-with-no-tests.grit +1 -4
- package/src/ts/lib/schema.d.js +6 -0
- package/src/ts/lib/schema.d.js.map +1 -0
- package/src/ts/lib/schema.d.ts +1 -1
- package/src/ts/lib/schema.json +5 -0
- package/src/ts/lib/ts-project-utils.d.ts +12 -2
- package/src/ts/lib/ts-project-utils.js +80 -65
- package/src/ts/lib/ts-project-utils.js.map +1 -1
- package/src/ts/lib/types.js +5 -2
- package/src/ts/lib/types.js.map +1 -1
- package/src/ts/lib/vitest.d.ts +2 -2
- package/src/ts/lib/vitest.js +29 -26
- package/src/ts/lib/vitest.js.map +1 -1
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +83 -13
- package/src/ts/mcp-server/generator.d.ts +3 -3
- package/src/ts/mcp-server/generator.js +164 -120
- package/src/ts/mcp-server/generator.js.map +1 -1
- package/src/ts/mcp-server/schema.d.js +6 -0
- package/src/ts/mcp-server/schema.d.js.map +1 -0
- package/src/ts/mcp-server/schema.d.ts +1 -0
- package/src/ts/mcp-server/schema.json +5 -0
- package/src/ts/nx-generator/__snapshots__/generator.spec.ts.snap +46 -26
- package/src/ts/nx-generator/files/common/schema.d.ts.template +1 -0
- package/src/ts/nx-generator/files/common/schema.json.template +5 -0
- package/src/ts/nx-generator/files/local/generator.ts.template +3 -3
- package/src/ts/nx-generator/files/nx-plugin-for-aws/generator/generator.ts.template +6 -7
- package/src/ts/nx-generator/generator.d.ts +2 -2
- package/src/ts/nx-generator/generator.js +111 -74
- package/src/ts/nx-generator/generator.js.map +1 -1
- package/src/ts/nx-generator/schema.d.js +6 -0
- package/src/ts/nx-generator/schema.d.js.map +1 -0
- package/src/ts/nx-generator/schema.d.ts +1 -0
- package/src/ts/nx-generator/schema.json +5 -0
- package/src/ts/nx-plugin/__snapshots__/generator.spec.ts.snap +30 -18
- package/src/ts/nx-plugin/files/mcp-server/server.ts.template +5 -5
- package/src/ts/nx-plugin/files/mcp-server/tools/create-workspace-command.ts.template +2 -2
- package/src/ts/nx-plugin/files/mcp-server/tools/general-guidance.ts.template +1 -1
- package/src/ts/nx-plugin/files/mcp-server/tools/generator-guide.ts.template +2 -2
- package/src/ts/nx-plugin/files/mcp-server/tools/list-generators.ts.template +2 -2
- package/src/ts/nx-plugin/files/mcp-server/utils.ts.template +3 -3
- package/src/ts/nx-plugin/generator.d.ts +3 -3
- package/src/ts/nx-plugin/generator.js +60 -51
- package/src/ts/nx-plugin/generator.js.map +1 -1
- package/src/ts/nx-plugin/schema.d.js +9 -0
- package/src/ts/nx-plugin/schema.d.js.map +1 -0
- package/src/ts/nx-plugin/schema.json +5 -0
- package/src/ts/nx-plugin/utils.d.ts +1 -1
- package/src/ts/nx-plugin/utils.js +32 -38
- package/src/ts/nx-plugin/utils.js.map +1 -1
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +312 -482
- package/src/ts/rdb/agent-connection/__snapshots__/generator.spec.ts.snap +6 -6
- package/src/ts/rdb/agent-connection/generator.d.ts +3 -3
- package/src/ts/rdb/agent-connection/generator.js +38 -39
- package/src/ts/rdb/agent-connection/generator.js.map +1 -1
- package/src/ts/rdb/agent-connection/schema.d.js +8 -0
- package/src/ts/rdb/agent-connection/schema.d.js.map +1 -0
- package/src/ts/rdb/agent-connection/schema.d.ts +1 -0
- package/src/ts/rdb/agent-connection/schema.json +5 -0
- package/src/ts/rdb/files/config.json.template +14 -0
- package/src/ts/rdb/files/prisma.config.ts.template +6 -5
- package/src/ts/rdb/files/src/create-db-user-handler.ts.template +7 -9
- package/src/ts/rdb/files/src/index.ts.template +0 -1
- package/src/ts/rdb/files/src/migration-handler.ts.template +1 -28
- package/src/ts/rdb/files/src/prisma.ts.template +28 -23
- package/src/ts/rdb/files/src/utils.ts.template +3 -0
- package/src/ts/rdb/generator.d.ts +3 -7
- package/src/ts/rdb/generator.js +158 -130
- package/src/ts/rdb/generator.js.map +1 -1
- package/src/ts/rdb/mcp-server-connection/__snapshots__/generator.spec.ts.snap +6 -6
- package/src/ts/rdb/mcp-server-connection/generator.d.ts +3 -3
- package/src/ts/rdb/mcp-server-connection/generator.js +36 -37
- package/src/ts/rdb/mcp-server-connection/generator.js.map +1 -1
- package/src/ts/rdb/mcp-server-connection/schema.d.js +8 -0
- package/src/ts/rdb/mcp-server-connection/schema.d.js.map +1 -0
- package/src/ts/rdb/mcp-server-connection/schema.d.ts +1 -0
- package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
- package/src/ts/rdb/schema.d.js +6 -0
- package/src/ts/rdb/schema.d.js.map +1 -0
- package/src/ts/rdb/schema.d.ts +1 -0
- package/src/ts/rdb/schema.json +5 -0
- package/src/ts/rdb/smithy-connection/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/ts/rdb/smithy-connection/generator.d.ts +3 -3
- package/src/ts/rdb/smithy-connection/generator.js +46 -43
- package/src/ts/rdb/smithy-connection/generator.js.map +1 -1
- package/src/ts/rdb/smithy-connection/schema.d.js +8 -0
- package/src/ts/rdb/smithy-connection/schema.d.js.map +1 -0
- package/src/ts/rdb/smithy-connection/schema.d.ts +1 -0
- package/src/ts/rdb/smithy-connection/schema.json +5 -0
- package/src/ts/rdb/trpc-connection/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/ts/rdb/trpc-connection/generator.d.ts +3 -3
- package/src/ts/rdb/trpc-connection/generator.js +36 -31
- package/src/ts/rdb/trpc-connection/generator.js.map +1 -1
- package/src/ts/rdb/trpc-connection/schema.d.js +8 -0
- package/src/ts/rdb/trpc-connection/schema.d.js.map +1 -0
- package/src/ts/rdb/trpc-connection/schema.d.ts +1 -0
- package/src/ts/rdb/trpc-connection/schema.json +5 -0
- package/src/ts/rdb/utils.d.ts +1 -1
- package/src/ts/rdb/utils.js +9 -11
- package/src/ts/rdb/utils.js.map +1 -1
- package/src/ts/react-website/agui/files/common/src/hooks/useAgui__agentNameClassName__.tsx.template +1 -16
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.js +69 -62
- package/src/ts/react-website/agui/generator.js.map +1 -1
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +626 -612
- package/src/ts/react-website/app/generator.d.ts +4 -4
- package/src/ts/react-website/app/generator.js +217 -192
- package/src/ts/react-website/app/generator.js.map +1 -1
- package/src/ts/react-website/app/schema.d.js +6 -0
- package/src/ts/react-website/app/schema.d.js.map +1 -0
- package/src/ts/react-website/app/schema.d.ts +1 -1
- package/src/ts/react-website/app/schema.json +11 -6
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +129 -18
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +36 -10
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.ux-provider.spec.ts.snap +0 -20
- package/src/ts/react-website/cognito-auth/files/app/components/CognitoAuth/index.tsx.template +2 -2
- package/src/ts/react-website/cognito-auth/generator.d.ts +4 -4
- package/src/ts/react-website/cognito-auth/generator.js +64 -63
- package/src/ts/react-website/cognito-auth/generator.js.map +1 -1
- package/src/ts/react-website/cognito-auth/grit/cloudscape-auth-menu.grit +8 -6
- package/src/ts/react-website/cognito-auth/grit/cognito-auth-wrapper.grit +1 -1
- package/src/ts/react-website/cognito-auth/grit/none-auth-menu.grit +1 -0
- package/src/ts/react-website/cognito-auth/grit/shadcn-auth-menu.grit +3 -1
- package/src/ts/react-website/cognito-auth/grit/shadcn-state-declarations.grit +3 -1
- package/src/ts/react-website/cognito-auth/schema.d.js +6 -0
- package/src/ts/react-website/cognito-auth/schema.d.js.map +1 -0
- package/src/ts/react-website/cognito-auth/schema.d.ts +1 -0
- package/src/ts/react-website/cognito-auth/schema.json +5 -0
- package/src/ts/react-website/cognito-auth/utils.d.ts +1 -1
- package/src/ts/react-website/cognito-auth/utils.js +34 -32
- package/src/ts/react-website/cognito-auth/utils.js.map +1 -1
- package/src/ts/react-website/runtime-config/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/react-website/runtime-config/files/app/components/RuntimeConfig/index.tsx.template +2 -2
- package/src/ts/react-website/runtime-config/generator.d.ts +3 -3
- package/src/ts/react-website/runtime-config/generator.js +29 -31
- package/src/ts/react-website/runtime-config/generator.js.map +1 -1
- package/src/ts/react-website/runtime-config/schema.d.js +6 -0
- package/src/ts/react-website/runtime-config/schema.d.js.map +1 -0
- package/src/ts/react-website/runtime-config/schema.d.ts +1 -0
- package/src/ts/react-website/runtime-config/schema.json +5 -0
- package/src/ts/sync/generator.d.ts +3 -3
- package/src/ts/sync/generator.js +60 -59
- package/src/ts/sync/generator.js.map +1 -1
- package/src/ts/website/app/generator.d.ts +3 -3
- package/src/ts/website/app/generator.js +10 -10
- package/src/ts/website/app/generator.js.map +1 -1
- package/src/ts/website/app/schema.d.js +6 -0
- package/src/ts/website/app/schema.d.js.map +1 -0
- package/src/ts/website/app/schema.d.ts +1 -1
- package/src/ts/website/app/schema.json +11 -6
- package/src/ts/website/auth/generator.d.ts +4 -4
- package/src/ts/website/auth/generator.js +11 -10
- package/src/ts/website/auth/generator.js.map +1 -1
- package/src/ts/website/auth/schema.d.js +6 -0
- package/src/ts/website/auth/schema.d.js.map +1 -0
- package/src/ts/website/auth/schema.d.ts +1 -0
- package/src/ts/website/auth/schema.json +5 -0
- package/src/utils/agent-chat/agent-chat.d.ts +31 -0
- package/src/utils/agent-chat/agent-chat.js +29 -0
- package/src/utils/agent-chat/agent-chat.js.map +1 -0
- package/src/utils/agent-chat/files/a2a/chat.ts.template +33 -0
- package/src/utils/agent-chat/files/ag-ui/chat.ts.template +17 -0
- package/src/utils/agent-chat/files/common/agentcore.ts.template +81 -0
- package/src/utils/agent-chat/files/http-py/chat.ts.template +43 -0
- package/src/utils/agent-chat/files/http-ts/chat.ts.template +54 -0
- package/src/utils/agent-connection/agent-connection.d.ts +117 -25
- package/src/utils/agent-connection/agent-connection.js +411 -103
- package/src/utils/agent-connection/agent-connection.js.map +1 -1
- package/src/utils/agent-connection/files/core-a2a/agentcore-a2a-client-config.ts.template +93 -0
- package/src/utils/agent-connection/files/core-auth/agentcore-endpoints.ts.template +37 -0
- package/src/utils/agent-connection/files/core-auth/agentcore-fetch.ts.template +54 -0
- package/src/utils/agent-connection/files/core-gateway/agentcore-gateway-mcp-transport.ts.template +41 -0
- package/src/utils/agent-connection/files/core-mcp/agentcore-mcp-transport.ts.template +70 -0
- package/src/utils/agent-connection/files/core-runtime-config/runtime-config.ts.template +22 -32
- package/src/utils/agent-connection/files/core-shared/agentcore-transport.ts.template +39 -0
- package/src/utils/agent-connection/files/core-strands/a2a/agentcore-a2a-client-strands.ts.template +61 -0
- package/src/utils/agent-connection/files/core-strands/base/model-errors-strands.ts.template +36 -0
- package/src/utils/agent-connection/files/core-strands/gateway/agentcore-gateway-mcp-client-strands.ts.template +26 -0
- package/src/utils/agent-connection/files/core-strands/mcp/agentcore-mcp-client-strands.ts.template +27 -0
- package/src/utils/agent-connection/files/py-core-a2a/agentcore_a2a_client_config.py.template +43 -0
- package/src/utils/agent-connection/files/py-core-auth/agentcore_endpoints.py.template +47 -0
- package/src/utils/agent-connection/files/py-core-auth/auth/session.py.template +51 -0
- package/src/utils/agent-connection/files/py-core-gateway/agentcore_gateway_mcp_transport.py.template +28 -0
- package/src/utils/agent-connection/files/py-core-langchain/a2a/agentcore_a2a_client_langchain.py.template +99 -0
- package/src/utils/agent-connection/files/py-core-langchain/gateway/agentcore_gateway_mcp_client_langchain.py.template +57 -0
- package/src/utils/agent-connection/files/py-core-langchain/mcp/agentcore_mcp_client_langchain.py.template +64 -0
- package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_transport.py.template +34 -0
- package/src/utils/agent-connection/files/py-core-runtime-config/runtime_config.py.template +5 -18
- package/src/utils/agent-connection/files/py-core-shared/agentcore_transport.py.template +40 -0
- package/src/utils/agent-connection/files/py-core-strands/a2a/agentcore_a2a_client_strands.py.template +66 -0
- package/src/utils/agent-connection/files/py-core-strands/base/model_errors_strands.py.template +40 -0
- package/src/utils/agent-connection/files/py-core-strands/gateway/agentcore_gateway_mcp_client_strands.py.template +22 -0
- package/src/utils/agent-connection/files/py-core-strands/mcp/agentcore_mcp_client_strands.py.template +30 -0
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +11 -3
- package/src/utils/agent-core-constructs/agent-core-constructs.js +86 -38
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/agent-core-constructs/files/cdk/app/agent-core/__nameKebabCase__/__nameKebabCase__.ts.template +4 -0
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.ts.template +50 -0
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/agentcore-gateway.ts.template +487 -0
- package/src/utils/agent-core-constructs/files/cdk/core/agentcore-gateway/readiness-probe/index.js.template +55 -0
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +449 -0
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/render-cedar.cjs.template +13 -0
- package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +80 -12
- package/src/utils/agent-core-constructs/files/terraform/core/agentcore-gateway/gateway.tf.template +14 -0
- package/src/utils/api-constructs/api-constructs.d.ts +2 -2
- package/src/utils/api-constructs/api-constructs.js +30 -31
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/cdk/app/apis/rest/__apiNameKebabCase__.ts.template +3 -0
- package/src/utils/api-constructs/files/cdk/core/api/http/http-api.ts.template +23 -6
- package/src/utils/api-constructs/files/cdk/core/api/rest/api-gateway-account/index.js.template +58 -0
- package/src/utils/api-constructs/files/cdk/core/api/rest/api-gateway-account.ts.template +82 -0
- package/src/utils/api-constructs/files/cdk/core/api/rest/rest-api.ts.template +51 -15
- package/src/utils/api-constructs/files/cdk/core/api/utils/utils.ts.template +61 -7
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +1 -8
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +94 -4
- package/src/utils/api-constructs/files/terraform/core/api/http/http-api/http-api.tf.template +1 -1
- package/src/utils/api-constructs/files/terraform/core/api/rest/api-gateway-account/api-gateway-account.tf.template +83 -0
- package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +5 -11
- package/src/utils/api-constructs/open-api-metadata.d.ts +2 -2
- package/src/utils/api-constructs/open-api-metadata.js +22 -22
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/ast/website.d.ts +5 -1
- package/src/utils/ast/website.js +18 -20
- package/src/utils/ast/website.js.map +1 -1
- package/src/utils/ast.d.ts +33 -1
- package/src/utils/ast.js +130 -70
- package/src/utils/ast.js.map +1 -1
- package/src/utils/bundle/bundle.d.ts +1 -1
- package/src/utils/bundle/bundle.js +50 -47
- package/src/utils/bundle/bundle.js.map +1 -1
- package/src/utils/commands.js +47 -26
- package/src/utils/commands.js.map +1 -1
- package/src/utils/config/index.d.ts +5 -5
- package/src/utils/config/index.js +5 -4
- package/src/utils/config/index.js.map +1 -1
- package/src/utils/config/utils.d.ts +13 -3
- package/src/utils/config/utils.js +65 -43
- package/src/utils/config/utils.js.map +1 -1
- package/src/utils/connection/open-api/files/components/__apiNameClassName__Provider.tsx.template +1 -1
- package/src/utils/connection/open-api/react.d.ts +6 -6
- package/src/utils/connection/open-api/react.js +81 -77
- package/src/utils/connection/open-api/react.js.map +1 -1
- package/src/utils/containers.d.ts +1 -1
- package/src/utils/containers.js +22 -23
- package/src/utils/containers.js.map +1 -1
- package/src/utils/dynamodb-constructs/dynamodb-constructs.d.ts +18 -0
- package/src/utils/dynamodb-constructs/dynamodb-constructs.js +48 -0
- package/src/utils/dynamodb-constructs/dynamodb-constructs.js.map +1 -0
- package/src/utils/dynamodb-constructs/files/cdk/app/dynamodb/__nameKebabCase__.ts.template +43 -0
- package/src/utils/dynamodb-constructs/files/cdk/core/dynamodb.ts.template +105 -0
- package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +72 -0
- package/src/utils/dynamodb-constructs/files/terraform/core/dynamodb/dynamodb.tf.template +155 -0
- package/src/utils/files/common/scripts/src/dynamodb/create-local-table.ts.template +195 -0
- package/src/utils/files/common/scripts/src/dynamodb/pull-image.ts.template +13 -0
- package/src/utils/files/common/scripts/src/dynamodb/start-container.ts.template +106 -0
- package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/cdk-command.ts.template +6 -3
- package/src/utils/files/common/scripts/src/rdb/mysql/wait-for-mysql-db.ts.template +32 -0
- package/src/utils/files/common/scripts/src/rdb/postgres/wait-for-postgres-db.ts.template +32 -0
- package/src/utils/files/common/scripts/src/rdb/shared/pull-image.ts.template +16 -0
- package/src/utils/files/common/scripts/src/rdb/shared/start-container.ts.template +117 -0
- package/src/utils/files/terraform/src/core/asset-bucket/asset-bucket.tf.template +2 -2
- package/src/utils/files/terraform/src/core/runtime-config/appconfig/appconfig.tf.template +1 -1
- package/src/utils/files/terraform/src/core/runtime-config/appconfig-deployment/appconfig-deployment.tf.template +12 -6
- package/src/utils/files/terraform/src/core/runtime-config/entry/entry.tf.template +1 -1
- package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +9 -4
- package/src/utils/files/website/hooks/sigv4/useSigV4.tsx.template +1 -1
- package/src/utils/format.d.ts +45 -2
- package/src/utils/format.js +323 -81
- package/src/utils/format.js.map +1 -1
- package/src/utils/fs.d.ts +1 -1
- package/src/utils/fs.js +14 -13
- package/src/utils/fs.js.map +1 -1
- package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +2 -2
- package/src/utils/function-constructs/function-constructs.d.ts +2 -2
- package/src/utils/function-constructs/function-constructs.js +21 -26
- package/src/utils/function-constructs/function-constructs.js.map +1 -1
- package/src/utils/generators.d.ts +4 -0
- package/src/utils/generators.js +18 -20
- package/src/utils/generators.js.map +1 -1
- package/src/utils/git.d.ts +1 -1
- package/src/utils/git.js +19 -34
- package/src/utils/git.js.map +1 -1
- package/src/utils/iac-providers.js +5 -5
- package/src/utils/iac-providers.js.map +1 -1
- package/src/utils/iac.d.ts +1 -1
- package/src/utils/iac.js +12 -14
- package/src/utils/iac.js.map +1 -1
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +123 -14
- package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +28 -9
- package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +106 -6
- package/src/utils/identity-constructs/files/terraform/core/user-identity/main.tf.template +7 -0
- package/src/utils/identity-constructs/identity-constructs.d.ts +2 -2
- package/src/utils/identity-constructs/identity-constructs.js +24 -28
- package/src/utils/identity-constructs/identity-constructs.js.map +1 -1
- package/src/utils/install.d.ts +32 -0
- package/src/utils/install.js +56 -0
- package/src/utils/install.js.map +1 -0
- package/src/utils/js.d.ts +4 -0
- package/src/utils/js.js +27 -20
- package/src/utils/js.js.map +1 -1
- package/src/utils/mcp.d.ts +25 -0
- package/src/utils/mcp.js +74 -0
- package/src/utils/mcp.js.map +1 -0
- package/src/utils/metrics.d.ts +9 -3
- package/src/utils/metrics.js +61 -46
- package/src/utils/metrics.js.map +1 -1
- package/src/utils/mock-project-graph.js +8 -12
- package/src/utils/mock-project-graph.js.map +1 -1
- package/src/utils/names.d.ts +22 -0
- package/src/utils/names.js +44 -48
- package/src/utils/names.js.map +1 -1
- package/src/utils/npm-scope.d.ts +1 -1
- package/src/utils/npm-scope.js +9 -17
- package/src/utils/npm-scope.js.map +1 -1
- package/src/utils/nx.d.ts +25 -4
- package/src/utils/nx.js +144 -75
- package/src/utils/nx.js.map +1 -1
- package/src/utils/nxlv-python.d.ts +1 -1
- package/src/utils/nxlv-python.js +27 -15
- package/src/utils/nxlv-python.js.map +1 -1
- package/src/utils/object.js +5 -11
- package/src/utils/object.js.map +1 -1
- package/src/utils/paths.d.ts +1 -1
- package/src/utils/paths.js +11 -15
- package/src/utils/paths.js.map +1 -1
- package/src/utils/pkg-manager.js +4 -9
- package/src/utils/pkg-manager.js.map +1 -1
- package/src/utils/pnpm-workspace.d.ts +1 -1
- package/src/utils/pnpm-workspace.js +22 -22
- package/src/utils/pnpm-workspace.js.map +1 -1
- package/src/utils/port.d.ts +31 -3
- package/src/utils/port.js +72 -12
- package/src/utils/port.js.map +1 -1
- package/src/utils/py.d.ts +16 -3
- package/src/utils/py.js +72 -47
- package/src/utils/py.js.map +1 -1
- package/src/utils/rdb-constructs/files/cdk/app/dbs/__nameKebabCase__.ts.template +12 -2
- package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +35 -16
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +42 -48
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +31 -16
- package/src/utils/rdb-constructs/rdb-constructs.d.ts +5 -4
- package/src/utils/rdb-constructs/rdb-constructs.js +31 -41
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/shared-constructs-constants.d.ts +1 -0
- package/src/utils/shared-constructs-constants.js +16 -15
- package/src/utils/shared-constructs-constants.js.map +1 -1
- package/src/utils/shared-constructs.d.ts +2 -2
- package/src/utils/shared-constructs.js +57 -54
- package/src/utils/shared-constructs.js.map +1 -1
- package/src/utils/shared-dynamodb-scripts.d.ts +11 -0
- package/src/utils/shared-dynamodb-scripts.js +25 -0
- package/src/utils/shared-dynamodb-scripts.js.map +1 -0
- package/src/utils/shared-infra-config.d.ts +1 -1
- package/src/utils/shared-infra-config.js +30 -31
- package/src/utils/shared-infra-config.js.map +1 -1
- package/src/utils/shared-infra-scripts.d.ts +11 -0
- package/src/utils/shared-infra-scripts.js +40 -0
- package/src/utils/shared-infra-scripts.js.map +1 -0
- package/src/utils/shared-rdb-scripts.d.ts +16 -0
- package/src/utils/shared-rdb-scripts.js +28 -0
- package/src/utils/shared-rdb-scripts.js.map +1 -0
- package/src/utils/shared-scripts.d.ts +2 -5
- package/src/utils/shared-scripts.js +12 -38
- package/src/utils/shared-scripts.js.map +1 -1
- package/src/utils/shared-shadcn.d.ts +1 -1
- package/src/utils/shared-shadcn.js +64 -72
- package/src/utils/shared-shadcn.js.map +1 -1
- package/src/utils/test.d.ts +13 -1
- package/src/utils/test.js +35 -23
- package/src/utils/test.js.map +1 -1
- package/src/utils/toml.d.ts +1 -1
- package/src/utils/toml.js +10 -14
- package/src/utils/toml.js.map +1 -1
- package/src/utils/versions.d.ts +116 -84
- package/src/utils/versions.js +124 -102
- package/src/utils/versions.js.map +1 -1
- package/src/utils/website-constructs/files/cdk/core/static-website.ts.template +73 -5
- package/src/utils/website-constructs/files/terraform/app/static-websites/__websiteNameKebabCase__/__websiteNameKebabCase__.tf.template +1 -1
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +68 -19
- package/src/utils/website-constructs/website-constructs.d.ts +2 -2
- package/src/utils/website-constructs/website-constructs.js +31 -30
- package/src/utils/website-constructs/website-constructs.js.map +1 -1
- package/bin/aws-nx-mcp.js +0 -17
- package/bin/aws-nx-mcp.js.map +0 -1
- package/sdk/connection.d.ts +0 -6
- package/sdk/connection.js +0 -10
- package/sdk/connection.js.map +0 -1
- package/sdk/open-api.d.ts +0 -10
- package/sdk/open-api.js +0 -15
- package/sdk/open-api.js.map +0 -1
- package/sdk/py.d.ts +0 -14
- package/sdk/py.js +0 -18
- package/sdk/py.js.map +0 -1
- package/sdk/terraform.d.ts +0 -6
- package/sdk/terraform.js +0 -11
- package/sdk/terraform.js.map +0 -1
- package/sdk/ts.d.ts +0 -29
- package/sdk/ts.js +0 -47
- package/sdk/ts.js.map +0 -1
- package/sdk/utils/ast.js +0 -12
- package/sdk/utils/ast.js.map +0 -1
- package/sdk/utils/format.js +0 -10
- package/sdk/utils/format.js.map +0 -1
- package/sdk/utils/test.js +0 -10
- package/sdk/utils/test.js.map +0 -1
- package/src/connection/agent-serve-local.js +0 -48
- package/src/connection/agent-serve-local.js.map +0 -1
- package/src/connection/serve-local.d.ts +0 -15
- package/src/connection/serve-local.js +0 -41
- package/src/connection/serve-local.js.map +0 -1
- package/src/py/agent/a2a-connection/files/agent-connection/app/__targetAgentSnakeCase___client.py.template +0 -22
- package/src/py/agent/mcp-connection/files/agent-connection/app/__mcpServerSnakeCase___client.py.template +0 -22
- package/src/py/agent/react-connection/serve-local.js +0 -30
- package/src/py/agent/react-connection/serve-local.js.map +0 -1
- package/src/py/agent/scripts/http/chat.ts.template +0 -38
- package/src/ts/agent/a2a-connection/files/agent-connection/app/__targetAgentKebabCase__-client.ts.template +0 -19
- package/src/ts/agent/mcp-connection/files/agent-connection/app/__mcpServerKebabCase__-client.ts.template +0 -19
- package/src/ts/agent/react-connection/serve-local.d.ts +0 -13
- package/src/ts/agent/react-connection/serve-local.js +0 -19
- package/src/ts/agent/react-connection/serve-local.js.map +0 -1
- package/src/ts/agent/scripts/http/chat.ts.template +0 -35
- package/src/ts/lib/eslint.d.ts +0 -14
- package/src/ts/lib/eslint.js +0 -90
- package/src/ts/lib/eslint.js.map +0 -1
- package/src/ts/lib/grit/eslint-dependency-checks-warn.grit +0 -1
- package/src/ts/rdb/files/scripts/docker-pull.ts.template +0 -15
- package/src/ts/rdb/files/scripts/docker-start.ts.template +0 -84
- package/src/ts/rdb/files/scripts/wait-for-db.ts.template +0 -55
- package/src/ts/rdb/files/src/constants.ts.template +0 -8
- package/src/utils/agent-connection/files/core-a2a/agentcore-a2a-client.ts.template +0 -123
- package/src/utils/agent-connection/files/core-mcp/agentcore-mcp-client.ts.template +0 -128
- package/src/utils/agent-connection/files/py-core-a2a/agentcore_a2a_client.py.template +0 -120
- package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_client.py.template +0 -89
- /package/{bin → src/bin}/aws-nx-mcp.d.ts +0 -0
- /package/src/utils/agent-connection/files/{core-runtime-config/with-session-id.ts.template → core-strands/base/with-session-id-strands.ts.template} +0 -0
- /package/src/utils/agent-connection/files/{py-core-runtime-config/with_session_id.py.template → py-core-strands/base/with_session_id_strands.py.template} +0 -0
- /package/src/utils/files/common/scripts/src/{index.ts.template → infra/index.ts.template} +0 -0
- /package/src/utils/files/common/scripts/src/{infra-deploy.ts.template → infra/infra-deploy.ts.template} +0 -0
- /package/src/utils/files/common/scripts/src/{infra-destroy.ts.template → infra/infra-destroy.ts.template} +0 -0
- /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/credentials.ts.template +0 -0
- /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/run.ts.template +0 -0
- /package/src/utils/files/common/scripts/src/{stage-credentials → infra/stage-credentials}/stage-parser.ts.template +0 -0
|
@@ -0,0 +1,4833 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for HTTP API with Cognito auth and snapshot them > terraform-http-cognito-files 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"http-api.tf": "# Core HTTP API Gateway module
|
|
6
|
+
# This module creates the API Gateway HTTP API, stage, and logging resources
|
|
7
|
+
|
|
8
|
+
terraform {
|
|
9
|
+
required_version = ">= 1.0"
|
|
10
|
+
|
|
11
|
+
required_providers {
|
|
12
|
+
aws = {
|
|
13
|
+
source = "hashicorp/aws"
|
|
14
|
+
version = "6.52.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Core HTTP API Gateway Variables
|
|
20
|
+
|
|
21
|
+
variable "api_name" {
|
|
22
|
+
description = "Name of the HTTP API Gateway"
|
|
23
|
+
type = string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
variable "api_description" {
|
|
27
|
+
description = "Description of the HTTP API Gateway"
|
|
28
|
+
type = string
|
|
29
|
+
default = "HTTP API Gateway"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
variable "stage_name" {
|
|
33
|
+
description = "Name of the API Gateway stage"
|
|
34
|
+
type = string
|
|
35
|
+
default = "prod"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
variable "stage_auto_deploy" {
|
|
39
|
+
description = "Whether to automatically deploy the API stage"
|
|
40
|
+
type = bool
|
|
41
|
+
default = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# CORS Configuration
|
|
45
|
+
|
|
46
|
+
variable "cors_allow_credentials" {
|
|
47
|
+
description = "Whether to allow credentials in CORS requests"
|
|
48
|
+
type = bool
|
|
49
|
+
default = false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
variable "cors_allow_headers" {
|
|
53
|
+
description = "List of allowed headers for CORS"
|
|
54
|
+
type = list(string)
|
|
55
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
variable "cors_allow_methods" {
|
|
59
|
+
description = "List of allowed HTTP methods for CORS"
|
|
60
|
+
type = list(string)
|
|
61
|
+
default = ["*"]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
variable "cors_allow_origins" {
|
|
65
|
+
description = "List of allowed origins for CORS"
|
|
66
|
+
type = list(string)
|
|
67
|
+
default = ["*"]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
variable "cors_expose_headers" {
|
|
71
|
+
description = "List of headers to expose in CORS responses"
|
|
72
|
+
type = list(string)
|
|
73
|
+
default = []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
variable "cors_max_age" {
|
|
77
|
+
description = "Maximum age for CORS preflight requests in seconds"
|
|
78
|
+
type = number
|
|
79
|
+
default = 86400
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Tags
|
|
83
|
+
|
|
84
|
+
variable "tags" {
|
|
85
|
+
description = "Tags to apply to all resources"
|
|
86
|
+
type = map(string)
|
|
87
|
+
default = {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Data sources
|
|
91
|
+
data "aws_region" "current" {}
|
|
92
|
+
data "aws_caller_identity" "current" {}
|
|
93
|
+
|
|
94
|
+
# Resources
|
|
95
|
+
|
|
96
|
+
# KMS key for CloudWatch log group encryption
|
|
97
|
+
resource "aws_kms_key" "logs_key" {
|
|
98
|
+
description = "KMS key for CloudWatch log group encryption"
|
|
99
|
+
deletion_window_in_days = 7
|
|
100
|
+
enable_key_rotation = true
|
|
101
|
+
|
|
102
|
+
policy = jsonencode({
|
|
103
|
+
Version = "2012-10-17"
|
|
104
|
+
Statement = [
|
|
105
|
+
{
|
|
106
|
+
Sid = "Enable IAM User Permissions"
|
|
107
|
+
Effect = "Allow"
|
|
108
|
+
Principal = {
|
|
109
|
+
AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
|
|
110
|
+
}
|
|
111
|
+
Action = "kms:*"
|
|
112
|
+
Resource = "*"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
Sid = "Allow CloudWatch Logs"
|
|
116
|
+
Effect = "Allow"
|
|
117
|
+
Principal = {
|
|
118
|
+
Service = "logs.\${data.aws_region.current.region}.amazonaws.com"
|
|
119
|
+
}
|
|
120
|
+
Action = [
|
|
121
|
+
"kms:Encrypt",
|
|
122
|
+
"kms:Decrypt",
|
|
123
|
+
"kms:ReEncrypt*",
|
|
124
|
+
"kms:GenerateDataKey*",
|
|
125
|
+
"kms:DescribeKey"
|
|
126
|
+
]
|
|
127
|
+
Resource = "*"
|
|
128
|
+
Condition = {
|
|
129
|
+
ArnEquals = {
|
|
130
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/\${var.api_name}"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
tags = var.tags
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
resource "aws_kms_alias" "logs_key_alias" {
|
|
141
|
+
name = "alias/\${var.api_name}-api-logs-encryption"
|
|
142
|
+
target_key_id = aws_kms_key.logs_key.key_id
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# HTTP API Gateway
|
|
146
|
+
resource "aws_apigatewayv2_api" "http_api" {
|
|
147
|
+
name = var.api_name
|
|
148
|
+
protocol_type = "HTTP"
|
|
149
|
+
description = var.api_description
|
|
150
|
+
|
|
151
|
+
cors_configuration {
|
|
152
|
+
allow_credentials = var.cors_allow_credentials
|
|
153
|
+
allow_headers = var.cors_allow_headers
|
|
154
|
+
allow_methods = var.cors_allow_methods
|
|
155
|
+
allow_origins = var.cors_allow_origins
|
|
156
|
+
expose_headers = var.cors_expose_headers
|
|
157
|
+
max_age = var.cors_max_age
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
tags = var.tags
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# API Gateway stage
|
|
164
|
+
resource "aws_apigatewayv2_stage" "api_stage" {
|
|
165
|
+
api_id = aws_apigatewayv2_api.http_api.id
|
|
166
|
+
name = var.stage_name
|
|
167
|
+
auto_deploy = var.stage_auto_deploy
|
|
168
|
+
|
|
169
|
+
access_log_settings {
|
|
170
|
+
destination_arn = aws_cloudwatch_log_group.api_logs.arn
|
|
171
|
+
format = jsonencode({
|
|
172
|
+
requestId = "$context.requestId"
|
|
173
|
+
ip = "$context.identity.sourceIp"
|
|
174
|
+
requestTime = "$context.requestTime"
|
|
175
|
+
httpMethod = "$context.httpMethod"
|
|
176
|
+
routeKey = "$context.routeKey"
|
|
177
|
+
status = "$context.status"
|
|
178
|
+
protocol = "$context.protocol"
|
|
179
|
+
responseLength = "$context.responseLength"
|
|
180
|
+
error = "$context.error.message"
|
|
181
|
+
integrationError = "$context.integrationErrorMessage"
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
default_route_settings {
|
|
186
|
+
throttling_burst_limit = 5000
|
|
187
|
+
throttling_rate_limit = 10000
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
tags = var.tags
|
|
191
|
+
|
|
192
|
+
depends_on = [aws_cloudwatch_log_group.api_logs]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# CloudWatch Log Group for API Gateway
|
|
196
|
+
resource "aws_cloudwatch_log_group" "api_logs" {
|
|
197
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
198
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
199
|
+
name = "/aws/apigateway/\${var.api_name}"
|
|
200
|
+
kms_key_id = aws_kms_key.logs_key.arn
|
|
201
|
+
tags = var.tags
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Outputs
|
|
205
|
+
|
|
206
|
+
output "api_id" {
|
|
207
|
+
description = "ID of the HTTP API Gateway"
|
|
208
|
+
value = aws_apigatewayv2_api.http_api.id
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
output "api_arn" {
|
|
212
|
+
description = "ARN of the HTTP API Gateway"
|
|
213
|
+
value = aws_apigatewayv2_api.http_api.arn
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
output "api_endpoint" {
|
|
217
|
+
description = "Base URL of the HTTP API Gateway"
|
|
218
|
+
value = aws_apigatewayv2_api.http_api.api_endpoint
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
output "api_execution_arn" {
|
|
222
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
223
|
+
value = aws_apigatewayv2_api.http_api.execution_arn
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
output "stage_id" {
|
|
227
|
+
description = "ID of the API Gateway stage"
|
|
228
|
+
value = aws_apigatewayv2_stage.api_stage.id
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
output "stage_arn" {
|
|
232
|
+
description = "ARN of the API Gateway stage"
|
|
233
|
+
value = aws_apigatewayv2_stage.api_stage.arn
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
output "stage_execution_arn" {
|
|
237
|
+
description = "Execution ARN of the API Gateway stage"
|
|
238
|
+
value = aws_apigatewayv2_stage.api_stage.execution_arn
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
output "stage_invoke_url" {
|
|
242
|
+
description = "Invoke URL of the API Gateway stage"
|
|
243
|
+
value = aws_apigatewayv2_stage.api_stage.invoke_url
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
output "api_log_group_name" {
|
|
247
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
248
|
+
value = aws_cloudwatch_log_group.api_logs.name
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
output "api_log_group_arn" {
|
|
252
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
253
|
+
value = aws_cloudwatch_log_group.api_logs.arn
|
|
254
|
+
}",
|
|
255
|
+
"test-api.tf": "terraform {
|
|
256
|
+
required_version = ">= 1.0"
|
|
257
|
+
|
|
258
|
+
required_providers {
|
|
259
|
+
aws = {
|
|
260
|
+
source = "hashicorp/aws"
|
|
261
|
+
version = "6.52.0"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Authentication Configuration
|
|
267
|
+
variable "user_pool_id" {
|
|
268
|
+
description = "Cognito User Pool ID for authentication"
|
|
269
|
+
type = string
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
variable "user_pool_client_ids" {
|
|
273
|
+
description = "List of Cognito User Pool Client IDs"
|
|
274
|
+
type = list(string)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
variable "env" {
|
|
278
|
+
description = "Environment variables for the Lambda function"
|
|
279
|
+
type = map(string)
|
|
280
|
+
default = {}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
variable "additional_iam_policy_statements" {
|
|
284
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
285
|
+
type = list(object({
|
|
286
|
+
Effect = string
|
|
287
|
+
Action = list(string)
|
|
288
|
+
Resource = list(string)
|
|
289
|
+
}))
|
|
290
|
+
default = []
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
variable "asset_bucket_name" {
|
|
294
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
295
|
+
type = string
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# CORS Configuration (passed to core module)
|
|
299
|
+
variable "cors_allow_credentials" {
|
|
300
|
+
description = "Whether to allow credentials in CORS requests"
|
|
301
|
+
type = bool
|
|
302
|
+
default = false
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
variable "cors_allow_headers" {
|
|
306
|
+
description = "List of allowed headers for CORS"
|
|
307
|
+
type = list(string)
|
|
308
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
variable "cors_allow_methods" {
|
|
312
|
+
description = "List of allowed HTTP methods for CORS"
|
|
313
|
+
type = list(string)
|
|
314
|
+
default = ["*"]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
variable "cors_allow_origins" {
|
|
318
|
+
description = "List of allowed origins for CORS"
|
|
319
|
+
type = list(string)
|
|
320
|
+
default = ["*"]
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
variable "cors_expose_headers" {
|
|
324
|
+
description = "List of headers to expose in CORS responses"
|
|
325
|
+
type = list(string)
|
|
326
|
+
default = []
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# Tags
|
|
330
|
+
variable "tags" {
|
|
331
|
+
description = "Tags to apply to all resources"
|
|
332
|
+
type = map(string)
|
|
333
|
+
default = {}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# Get current AWS region and account ID
|
|
337
|
+
data "aws_region" "current" {}
|
|
338
|
+
data "aws_caller_identity" "current" {}
|
|
339
|
+
|
|
340
|
+
resource "random_string" "suffix" {
|
|
341
|
+
length = 8
|
|
342
|
+
special = false
|
|
343
|
+
upper = false
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
# Resources
|
|
347
|
+
|
|
348
|
+
# Create Lambda ZIP file from the bundle directory
|
|
349
|
+
data "archive_file" "lambda_zip" {
|
|
350
|
+
type = "zip"
|
|
351
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
352
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
356
|
+
bucket = var.asset_bucket_name
|
|
357
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
358
|
+
source = data.archive_file.lambda_zip.output_path
|
|
359
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
360
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# Use the core HTTP API module
|
|
365
|
+
module "http_api" {
|
|
366
|
+
source = "../../../core/api/http-api"
|
|
367
|
+
|
|
368
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
369
|
+
api_description = "TestApi HTTP API"
|
|
370
|
+
stage_name = "$default"
|
|
371
|
+
stage_auto_deploy = true
|
|
372
|
+
|
|
373
|
+
# CORS Configuration
|
|
374
|
+
cors_allow_credentials = var.cors_allow_credentials
|
|
375
|
+
cors_allow_headers = var.cors_allow_headers
|
|
376
|
+
cors_allow_methods = var.cors_allow_methods
|
|
377
|
+
cors_allow_origins = var.cors_allow_origins
|
|
378
|
+
cors_expose_headers = var.cors_expose_headers
|
|
379
|
+
|
|
380
|
+
# Tags
|
|
381
|
+
tags = var.tags
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
# Lambda function
|
|
385
|
+
# This configures a single "router" lambda to serve all requests
|
|
386
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
387
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
388
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
389
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
390
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
391
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
392
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
393
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
394
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
395
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
396
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
397
|
+
handler = "run.sh"
|
|
398
|
+
runtime = "python3.14"
|
|
399
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
400
|
+
timeout = 30
|
|
401
|
+
memory_size = 128
|
|
402
|
+
publish = true
|
|
403
|
+
|
|
404
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
405
|
+
|
|
406
|
+
# Enable X-Ray tracing
|
|
407
|
+
tracing_config {
|
|
408
|
+
mode = "Active"
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# Enable SnapStart for faster cold starts
|
|
412
|
+
snap_start {
|
|
413
|
+
apply_on = "PublishedVersions"
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
environment {
|
|
417
|
+
variables = merge({
|
|
418
|
+
PORT = "8000"
|
|
419
|
+
AWS_LWA_INVOKE_MODE = "buffered"
|
|
420
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
421
|
+
}, var.env)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
tags = var.tags
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# IAM role for Lambda execution
|
|
428
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
429
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
430
|
+
|
|
431
|
+
assume_role_policy = jsonencode({
|
|
432
|
+
Version = "2012-10-17"
|
|
433
|
+
Statement = [
|
|
434
|
+
{
|
|
435
|
+
Action = "sts:AssumeRole"
|
|
436
|
+
Effect = "Allow"
|
|
437
|
+
Principal = {
|
|
438
|
+
Service = "lambda.amazonaws.com"
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
tags = var.tags
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# Attach basic execution policy to Lambda role
|
|
448
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
449
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
450
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
454
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
455
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
456
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
# Additional IAM policies for Lambda (if provided)
|
|
460
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
461
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
462
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
463
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
464
|
+
|
|
465
|
+
policy = jsonencode({
|
|
466
|
+
Version = "2012-10-17"
|
|
467
|
+
Statement = var.additional_iam_policy_statements
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
# CloudWatch Log Group for Lambda
|
|
472
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
473
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
474
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
475
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
476
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
477
|
+
tags = var.tags
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
481
|
+
resource "aws_lambda_alias" "live" {
|
|
482
|
+
name = "live"
|
|
483
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
484
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
485
|
+
|
|
486
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# Cognito User Pool Authorizer
|
|
490
|
+
resource "aws_apigatewayv2_authorizer" "cognito_authorizer" {
|
|
491
|
+
api_id = module.http_api.api_id
|
|
492
|
+
authorizer_type = "JWT"
|
|
493
|
+
identity_sources = ["$request.header.Authorization"]
|
|
494
|
+
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
495
|
+
|
|
496
|
+
jwt_configuration {
|
|
497
|
+
audience = var.user_pool_client_ids
|
|
498
|
+
issuer = "https://cognito-idp.\${data.aws_region.current.region}.amazonaws.com/\${var.user_pool_id}"
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
# Lambda integration for HTTP API
|
|
503
|
+
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
504
|
+
api_id = module.http_api.api_id
|
|
505
|
+
integration_type = "AWS_PROXY"
|
|
506
|
+
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
507
|
+
|
|
508
|
+
payload_format_version = "2.0"
|
|
509
|
+
timeout_milliseconds = 30000
|
|
510
|
+
|
|
511
|
+
depends_on = [aws_lambda_alias.live]
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
# Route for proxy integration (catches all requests)
|
|
515
|
+
resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
516
|
+
# NB: OPTIONS is omitted here since API Gateway manages responding to preflight requests
|
|
517
|
+
# when cors settings are configured
|
|
518
|
+
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
519
|
+
|
|
520
|
+
api_id = module.http_api.api_id
|
|
521
|
+
route_key = "\${each.key} /{proxy+}"
|
|
522
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
523
|
+
|
|
524
|
+
authorization_type = "JWT"
|
|
525
|
+
authorizer_id = aws_apigatewayv2_authorizer.cognito_authorizer.id
|
|
526
|
+
|
|
527
|
+
depends_on = [aws_apigatewayv2_integration.lambda_integration, aws_apigatewayv2_authorizer.cognito_authorizer]
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
# Add API url to runtime config
|
|
531
|
+
module "add_url_to_runtime_config" {
|
|
532
|
+
source = "../../../core/runtime-config/entry"
|
|
533
|
+
|
|
534
|
+
namespace = "connection"
|
|
535
|
+
key = "apis"
|
|
536
|
+
value = { "TestApi" = module.http_api.stage_invoke_url }
|
|
537
|
+
|
|
538
|
+
depends_on = [module.http_api]
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
542
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
543
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
544
|
+
action = "lambda:InvokeFunction"
|
|
545
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
546
|
+
qualifier = aws_lambda_alias.live.name
|
|
547
|
+
principal = "apigateway.amazonaws.com"
|
|
548
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/*"
|
|
549
|
+
|
|
550
|
+
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
# Outputs
|
|
554
|
+
|
|
555
|
+
# API Gateway Outputs (from core module)
|
|
556
|
+
output "api_id" {
|
|
557
|
+
description = "ID of the HTTP API Gateway"
|
|
558
|
+
value = module.http_api.api_id
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
output "api_arn" {
|
|
562
|
+
description = "ARN of the HTTP API Gateway"
|
|
563
|
+
value = module.http_api.api_arn
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
output "api_endpoint" {
|
|
567
|
+
description = "Base URL of the HTTP API Gateway"
|
|
568
|
+
value = module.http_api.api_endpoint
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
output "api_execution_arn" {
|
|
572
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
573
|
+
value = module.http_api.api_execution_arn
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
output "stage_invoke_url" {
|
|
577
|
+
description = "Invoke URL of the API Gateway stage"
|
|
578
|
+
value = module.http_api.stage_invoke_url
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
output "stage_arn" {
|
|
582
|
+
description = "ARN of the API Gateway stage"
|
|
583
|
+
value = module.http_api.stage_arn
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
output "stage_execution_arn" {
|
|
587
|
+
description = "Execution ARN of the API Gateway stage"
|
|
588
|
+
value = module.http_api.stage_execution_arn
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
# Lambda Function Outputs
|
|
592
|
+
output "lambda_function_name" {
|
|
593
|
+
description = "Name of the Lambda function"
|
|
594
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
output "lambda_function_arn" {
|
|
598
|
+
description = "ARN of the Lambda function"
|
|
599
|
+
value = aws_lambda_function.api_lambda.arn
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
output "lambda_invoke_arn" {
|
|
603
|
+
description = "Invoke ARN of the Lambda function"
|
|
604
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
output "lambda_qualified_arn" {
|
|
608
|
+
description = "Qualified ARN of the Lambda function"
|
|
609
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
output "lambda_version" {
|
|
613
|
+
description = "Version of the Lambda function"
|
|
614
|
+
value = aws_lambda_function.api_lambda.version
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
output "lambda_source_code_hash" {
|
|
618
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
619
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
output "lambda_source_code_size" {
|
|
623
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
624
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
# IAM Role Outputs
|
|
628
|
+
output "lambda_execution_role_arn" {
|
|
629
|
+
description = "ARN of the Lambda execution role"
|
|
630
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
output "lambda_execution_role_name" {
|
|
634
|
+
description = "Name of the Lambda execution role"
|
|
635
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
# Integration Outputs
|
|
639
|
+
output "integration_id" {
|
|
640
|
+
description = "ID of the Lambda integration"
|
|
641
|
+
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
# CloudWatch Log Groups
|
|
645
|
+
output "lambda_log_group_name" {
|
|
646
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
647
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
output "lambda_log_group_arn" {
|
|
651
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
652
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
output "api_log_group_name" {
|
|
656
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
657
|
+
value = module.http_api.api_log_group_name
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
output "api_log_group_arn" {
|
|
661
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
662
|
+
value = module.http_api.api_log_group_arn
|
|
663
|
+
}",
|
|
664
|
+
}
|
|
665
|
+
`;
|
|
666
|
+
|
|
667
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for HTTP API with Custom auth and snapshot them > terraform-http-custom-files 1`] = `
|
|
668
|
+
{
|
|
669
|
+
"http-api.tf": "# Core HTTP API Gateway module
|
|
670
|
+
# This module creates the API Gateway HTTP API, stage, and logging resources
|
|
671
|
+
|
|
672
|
+
terraform {
|
|
673
|
+
required_version = ">= 1.0"
|
|
674
|
+
|
|
675
|
+
required_providers {
|
|
676
|
+
aws = {
|
|
677
|
+
source = "hashicorp/aws"
|
|
678
|
+
version = "6.52.0"
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
# Core HTTP API Gateway Variables
|
|
684
|
+
|
|
685
|
+
variable "api_name" {
|
|
686
|
+
description = "Name of the HTTP API Gateway"
|
|
687
|
+
type = string
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
variable "api_description" {
|
|
691
|
+
description = "Description of the HTTP API Gateway"
|
|
692
|
+
type = string
|
|
693
|
+
default = "HTTP API Gateway"
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
variable "stage_name" {
|
|
697
|
+
description = "Name of the API Gateway stage"
|
|
698
|
+
type = string
|
|
699
|
+
default = "prod"
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
variable "stage_auto_deploy" {
|
|
703
|
+
description = "Whether to automatically deploy the API stage"
|
|
704
|
+
type = bool
|
|
705
|
+
default = true
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# CORS Configuration
|
|
709
|
+
|
|
710
|
+
variable "cors_allow_credentials" {
|
|
711
|
+
description = "Whether to allow credentials in CORS requests"
|
|
712
|
+
type = bool
|
|
713
|
+
default = false
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
variable "cors_allow_headers" {
|
|
717
|
+
description = "List of allowed headers for CORS"
|
|
718
|
+
type = list(string)
|
|
719
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
variable "cors_allow_methods" {
|
|
723
|
+
description = "List of allowed HTTP methods for CORS"
|
|
724
|
+
type = list(string)
|
|
725
|
+
default = ["*"]
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
variable "cors_allow_origins" {
|
|
729
|
+
description = "List of allowed origins for CORS"
|
|
730
|
+
type = list(string)
|
|
731
|
+
default = ["*"]
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
variable "cors_expose_headers" {
|
|
735
|
+
description = "List of headers to expose in CORS responses"
|
|
736
|
+
type = list(string)
|
|
737
|
+
default = []
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
variable "cors_max_age" {
|
|
741
|
+
description = "Maximum age for CORS preflight requests in seconds"
|
|
742
|
+
type = number
|
|
743
|
+
default = 86400
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
# Tags
|
|
747
|
+
|
|
748
|
+
variable "tags" {
|
|
749
|
+
description = "Tags to apply to all resources"
|
|
750
|
+
type = map(string)
|
|
751
|
+
default = {}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
# Data sources
|
|
755
|
+
data "aws_region" "current" {}
|
|
756
|
+
data "aws_caller_identity" "current" {}
|
|
757
|
+
|
|
758
|
+
# Resources
|
|
759
|
+
|
|
760
|
+
# KMS key for CloudWatch log group encryption
|
|
761
|
+
resource "aws_kms_key" "logs_key" {
|
|
762
|
+
description = "KMS key for CloudWatch log group encryption"
|
|
763
|
+
deletion_window_in_days = 7
|
|
764
|
+
enable_key_rotation = true
|
|
765
|
+
|
|
766
|
+
policy = jsonencode({
|
|
767
|
+
Version = "2012-10-17"
|
|
768
|
+
Statement = [
|
|
769
|
+
{
|
|
770
|
+
Sid = "Enable IAM User Permissions"
|
|
771
|
+
Effect = "Allow"
|
|
772
|
+
Principal = {
|
|
773
|
+
AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
|
|
774
|
+
}
|
|
775
|
+
Action = "kms:*"
|
|
776
|
+
Resource = "*"
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
Sid = "Allow CloudWatch Logs"
|
|
780
|
+
Effect = "Allow"
|
|
781
|
+
Principal = {
|
|
782
|
+
Service = "logs.\${data.aws_region.current.region}.amazonaws.com"
|
|
783
|
+
}
|
|
784
|
+
Action = [
|
|
785
|
+
"kms:Encrypt",
|
|
786
|
+
"kms:Decrypt",
|
|
787
|
+
"kms:ReEncrypt*",
|
|
788
|
+
"kms:GenerateDataKey*",
|
|
789
|
+
"kms:DescribeKey"
|
|
790
|
+
]
|
|
791
|
+
Resource = "*"
|
|
792
|
+
Condition = {
|
|
793
|
+
ArnEquals = {
|
|
794
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/\${var.api_name}"
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
]
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
tags = var.tags
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
resource "aws_kms_alias" "logs_key_alias" {
|
|
805
|
+
name = "alias/\${var.api_name}-api-logs-encryption"
|
|
806
|
+
target_key_id = aws_kms_key.logs_key.key_id
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
# HTTP API Gateway
|
|
810
|
+
resource "aws_apigatewayv2_api" "http_api" {
|
|
811
|
+
name = var.api_name
|
|
812
|
+
protocol_type = "HTTP"
|
|
813
|
+
description = var.api_description
|
|
814
|
+
|
|
815
|
+
cors_configuration {
|
|
816
|
+
allow_credentials = var.cors_allow_credentials
|
|
817
|
+
allow_headers = var.cors_allow_headers
|
|
818
|
+
allow_methods = var.cors_allow_methods
|
|
819
|
+
allow_origins = var.cors_allow_origins
|
|
820
|
+
expose_headers = var.cors_expose_headers
|
|
821
|
+
max_age = var.cors_max_age
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
tags = var.tags
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
# API Gateway stage
|
|
828
|
+
resource "aws_apigatewayv2_stage" "api_stage" {
|
|
829
|
+
api_id = aws_apigatewayv2_api.http_api.id
|
|
830
|
+
name = var.stage_name
|
|
831
|
+
auto_deploy = var.stage_auto_deploy
|
|
832
|
+
|
|
833
|
+
access_log_settings {
|
|
834
|
+
destination_arn = aws_cloudwatch_log_group.api_logs.arn
|
|
835
|
+
format = jsonencode({
|
|
836
|
+
requestId = "$context.requestId"
|
|
837
|
+
ip = "$context.identity.sourceIp"
|
|
838
|
+
requestTime = "$context.requestTime"
|
|
839
|
+
httpMethod = "$context.httpMethod"
|
|
840
|
+
routeKey = "$context.routeKey"
|
|
841
|
+
status = "$context.status"
|
|
842
|
+
protocol = "$context.protocol"
|
|
843
|
+
responseLength = "$context.responseLength"
|
|
844
|
+
error = "$context.error.message"
|
|
845
|
+
integrationError = "$context.integrationErrorMessage"
|
|
846
|
+
})
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
default_route_settings {
|
|
850
|
+
throttling_burst_limit = 5000
|
|
851
|
+
throttling_rate_limit = 10000
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
tags = var.tags
|
|
855
|
+
|
|
856
|
+
depends_on = [aws_cloudwatch_log_group.api_logs]
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
# CloudWatch Log Group for API Gateway
|
|
860
|
+
resource "aws_cloudwatch_log_group" "api_logs" {
|
|
861
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
862
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
863
|
+
name = "/aws/apigateway/\${var.api_name}"
|
|
864
|
+
kms_key_id = aws_kms_key.logs_key.arn
|
|
865
|
+
tags = var.tags
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
# Outputs
|
|
869
|
+
|
|
870
|
+
output "api_id" {
|
|
871
|
+
description = "ID of the HTTP API Gateway"
|
|
872
|
+
value = aws_apigatewayv2_api.http_api.id
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
output "api_arn" {
|
|
876
|
+
description = "ARN of the HTTP API Gateway"
|
|
877
|
+
value = aws_apigatewayv2_api.http_api.arn
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
output "api_endpoint" {
|
|
881
|
+
description = "Base URL of the HTTP API Gateway"
|
|
882
|
+
value = aws_apigatewayv2_api.http_api.api_endpoint
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
output "api_execution_arn" {
|
|
886
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
887
|
+
value = aws_apigatewayv2_api.http_api.execution_arn
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
output "stage_id" {
|
|
891
|
+
description = "ID of the API Gateway stage"
|
|
892
|
+
value = aws_apigatewayv2_stage.api_stage.id
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
output "stage_arn" {
|
|
896
|
+
description = "ARN of the API Gateway stage"
|
|
897
|
+
value = aws_apigatewayv2_stage.api_stage.arn
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
output "stage_execution_arn" {
|
|
901
|
+
description = "Execution ARN of the API Gateway stage"
|
|
902
|
+
value = aws_apigatewayv2_stage.api_stage.execution_arn
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
output "stage_invoke_url" {
|
|
906
|
+
description = "Invoke URL of the API Gateway stage"
|
|
907
|
+
value = aws_apigatewayv2_stage.api_stage.invoke_url
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
output "api_log_group_name" {
|
|
911
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
912
|
+
value = aws_cloudwatch_log_group.api_logs.name
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
output "api_log_group_arn" {
|
|
916
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
917
|
+
value = aws_cloudwatch_log_group.api_logs.arn
|
|
918
|
+
}",
|
|
919
|
+
"test-api.tf": "terraform {
|
|
920
|
+
required_version = ">= 1.0"
|
|
921
|
+
|
|
922
|
+
required_providers {
|
|
923
|
+
aws = {
|
|
924
|
+
source = "hashicorp/aws"
|
|
925
|
+
version = "6.52.0"
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
variable "env" {
|
|
932
|
+
description = "Environment variables for the Lambda function"
|
|
933
|
+
type = map(string)
|
|
934
|
+
default = {}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
variable "additional_iam_policy_statements" {
|
|
938
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
939
|
+
type = list(object({
|
|
940
|
+
Effect = string
|
|
941
|
+
Action = list(string)
|
|
942
|
+
Resource = list(string)
|
|
943
|
+
}))
|
|
944
|
+
default = []
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
variable "asset_bucket_name" {
|
|
948
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
949
|
+
type = string
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
# CORS Configuration (passed to core module)
|
|
953
|
+
variable "cors_allow_credentials" {
|
|
954
|
+
description = "Whether to allow credentials in CORS requests"
|
|
955
|
+
type = bool
|
|
956
|
+
default = false
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
variable "cors_allow_headers" {
|
|
960
|
+
description = "List of allowed headers for CORS"
|
|
961
|
+
type = list(string)
|
|
962
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
variable "cors_allow_methods" {
|
|
966
|
+
description = "List of allowed HTTP methods for CORS"
|
|
967
|
+
type = list(string)
|
|
968
|
+
default = ["*"]
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
variable "cors_allow_origins" {
|
|
972
|
+
description = "List of allowed origins for CORS"
|
|
973
|
+
type = list(string)
|
|
974
|
+
default = ["*"]
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
variable "cors_expose_headers" {
|
|
978
|
+
description = "List of headers to expose in CORS responses"
|
|
979
|
+
type = list(string)
|
|
980
|
+
default = []
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
# Tags
|
|
984
|
+
variable "tags" {
|
|
985
|
+
description = "Tags to apply to all resources"
|
|
986
|
+
type = map(string)
|
|
987
|
+
default = {}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
# Get current AWS region and account ID
|
|
991
|
+
data "aws_region" "current" {}
|
|
992
|
+
data "aws_caller_identity" "current" {}
|
|
993
|
+
|
|
994
|
+
resource "random_string" "suffix" {
|
|
995
|
+
length = 8
|
|
996
|
+
special = false
|
|
997
|
+
upper = false
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
# Resources
|
|
1001
|
+
|
|
1002
|
+
# Create Lambda ZIP file from the bundle directory
|
|
1003
|
+
data "archive_file" "lambda_zip" {
|
|
1004
|
+
type = "zip"
|
|
1005
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
1006
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
1010
|
+
bucket = var.asset_bucket_name
|
|
1011
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
1012
|
+
source = data.archive_file.lambda_zip.output_path
|
|
1013
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
1014
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
# Use the core HTTP API module
|
|
1019
|
+
module "http_api" {
|
|
1020
|
+
source = "../../../core/api/http-api"
|
|
1021
|
+
|
|
1022
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
1023
|
+
api_description = "TestApi HTTP API"
|
|
1024
|
+
stage_name = "$default"
|
|
1025
|
+
stage_auto_deploy = true
|
|
1026
|
+
|
|
1027
|
+
# CORS Configuration
|
|
1028
|
+
cors_allow_credentials = var.cors_allow_credentials
|
|
1029
|
+
cors_allow_headers = var.cors_allow_headers
|
|
1030
|
+
cors_allow_methods = var.cors_allow_methods
|
|
1031
|
+
cors_allow_origins = var.cors_allow_origins
|
|
1032
|
+
cors_expose_headers = var.cors_expose_headers
|
|
1033
|
+
|
|
1034
|
+
# Tags
|
|
1035
|
+
tags = var.tags
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
# Lambda function
|
|
1039
|
+
# This configures a single "router" lambda to serve all requests
|
|
1040
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
1041
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
1042
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
1043
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
1044
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
1045
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
1046
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
1047
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
1048
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
1049
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
1050
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
1051
|
+
handler = "run.sh"
|
|
1052
|
+
runtime = "python3.14"
|
|
1053
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
1054
|
+
timeout = 30
|
|
1055
|
+
memory_size = 128
|
|
1056
|
+
publish = true
|
|
1057
|
+
|
|
1058
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
1059
|
+
|
|
1060
|
+
# Enable X-Ray tracing
|
|
1061
|
+
tracing_config {
|
|
1062
|
+
mode = "Active"
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
# Enable SnapStart for faster cold starts
|
|
1066
|
+
snap_start {
|
|
1067
|
+
apply_on = "PublishedVersions"
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
environment {
|
|
1071
|
+
variables = merge({
|
|
1072
|
+
PORT = "8000"
|
|
1073
|
+
AWS_LWA_INVOKE_MODE = "buffered"
|
|
1074
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
1075
|
+
}, var.env)
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
tags = var.tags
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
# IAM role for Lambda execution
|
|
1082
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
1083
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
1084
|
+
|
|
1085
|
+
assume_role_policy = jsonencode({
|
|
1086
|
+
Version = "2012-10-17"
|
|
1087
|
+
Statement = [
|
|
1088
|
+
{
|
|
1089
|
+
Action = "sts:AssumeRole"
|
|
1090
|
+
Effect = "Allow"
|
|
1091
|
+
Principal = {
|
|
1092
|
+
Service = "lambda.amazonaws.com"
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
]
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
tags = var.tags
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
# Attach basic execution policy to Lambda role
|
|
1102
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
1103
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
1104
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
1108
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
1109
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
1110
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
# Additional IAM policies for Lambda (if provided)
|
|
1114
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
1115
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
1116
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
1117
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
1118
|
+
|
|
1119
|
+
policy = jsonencode({
|
|
1120
|
+
Version = "2012-10-17"
|
|
1121
|
+
Statement = var.additional_iam_policy_statements
|
|
1122
|
+
})
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
# CloudWatch Log Group for Lambda
|
|
1126
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
1127
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
1128
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1129
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1130
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
1131
|
+
tags = var.tags
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
1135
|
+
resource "aws_lambda_alias" "live" {
|
|
1136
|
+
name = "live"
|
|
1137
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
1138
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
1139
|
+
|
|
1140
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
# Custom Lambda Authorizer
|
|
1144
|
+
resource "aws_lambda_function" "authorizer_lambda" {
|
|
1145
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
1146
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for authorizer
|
|
1147
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
1148
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
1149
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
1150
|
+
s3_bucket = aws_s3_object.authorizer_zip.bucket
|
|
1151
|
+
s3_key = aws_s3_object.authorizer_zip.key
|
|
1152
|
+
s3_object_version = aws_s3_object.authorizer_zip.version_id
|
|
1153
|
+
function_name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
1154
|
+
role = aws_iam_role.authorizer_execution_role.arn
|
|
1155
|
+
handler = "proj_test_api.authorizer.handler"
|
|
1156
|
+
runtime = "python3.14"
|
|
1157
|
+
timeout = 10
|
|
1158
|
+
memory_size = 128
|
|
1159
|
+
|
|
1160
|
+
source_code_hash = data.archive_file.authorizer_zip.output_base64sha256
|
|
1161
|
+
|
|
1162
|
+
tracing_config {
|
|
1163
|
+
mode = "Active"
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
tags = var.tags
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
resource "aws_iam_role" "authorizer_execution_role" {
|
|
1170
|
+
name = "TestApiAuthorizer-role-\${random_string.suffix.result}"
|
|
1171
|
+
|
|
1172
|
+
assume_role_policy = jsonencode({
|
|
1173
|
+
Version = "2012-10-17"
|
|
1174
|
+
Statement = [
|
|
1175
|
+
{
|
|
1176
|
+
Action = "sts:AssumeRole"
|
|
1177
|
+
Effect = "Allow"
|
|
1178
|
+
Principal = {
|
|
1179
|
+
Service = "lambda.amazonaws.com"
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
]
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
tags = var.tags
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
resource "aws_iam_role_policy_attachment" "authorizer_basic_execution" {
|
|
1189
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
1190
|
+
role = aws_iam_role.authorizer_execution_role.name
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
resource "aws_iam_role_policy_attachment" "authorizer_xray_execution" {
|
|
1194
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
1195
|
+
role = aws_iam_role.authorizer_execution_role.name
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
resource "aws_cloudwatch_log_group" "authorizer_logs" {
|
|
1199
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
1200
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1201
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1202
|
+
name = "/aws/lambda/TestApiAuthorizer-\${random_string.suffix.result}"
|
|
1203
|
+
tags = var.tags
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
data "archive_file" "authorizer_zip" {
|
|
1207
|
+
type = "zip"
|
|
1208
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
1209
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/authorizer.zip"
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
resource "aws_s3_object" "authorizer_zip" {
|
|
1213
|
+
bucket = var.asset_bucket_name
|
|
1214
|
+
key = "apis/test-api/authorizer-\${data.archive_file.authorizer_zip.output_sha256}.zip"
|
|
1215
|
+
source = data.archive_file.authorizer_zip.output_path
|
|
1216
|
+
source_hash = data.archive_file.authorizer_zip.output_base64sha256
|
|
1217
|
+
etag = data.archive_file.authorizer_zip.output_md5
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
resource "aws_apigatewayv2_authorizer" "custom_authorizer" {
|
|
1221
|
+
api_id = module.http_api.api_id
|
|
1222
|
+
authorizer_type = "REQUEST"
|
|
1223
|
+
authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
|
|
1224
|
+
authorizer_payload_format_version = "2.0"
|
|
1225
|
+
authorizer_result_ttl_in_seconds = 0
|
|
1226
|
+
enable_simple_responses = true
|
|
1227
|
+
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
resource "aws_lambda_permission" "authorizer_invoke" {
|
|
1231
|
+
statement_id = "AllowAPIGatewayInvokeAuthorizer"
|
|
1232
|
+
action = "lambda:InvokeFunction"
|
|
1233
|
+
function_name = aws_lambda_function.authorizer_lambda.function_name
|
|
1234
|
+
principal = "apigateway.amazonaws.com"
|
|
1235
|
+
source_arn = "\${module.http_api.api_execution_arn}/authorizers/\${aws_apigatewayv2_authorizer.custom_authorizer.id}"
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
# Lambda integration for HTTP API
|
|
1239
|
+
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
1240
|
+
api_id = module.http_api.api_id
|
|
1241
|
+
integration_type = "AWS_PROXY"
|
|
1242
|
+
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
1243
|
+
|
|
1244
|
+
payload_format_version = "2.0"
|
|
1245
|
+
timeout_milliseconds = 30000
|
|
1246
|
+
|
|
1247
|
+
depends_on = [aws_lambda_alias.live]
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
# Route for proxy integration (catches all requests)
|
|
1251
|
+
resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
1252
|
+
# NB: OPTIONS is omitted here since API Gateway manages responding to preflight requests
|
|
1253
|
+
# when cors settings are configured
|
|
1254
|
+
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
1255
|
+
|
|
1256
|
+
api_id = module.http_api.api_id
|
|
1257
|
+
route_key = "\${each.key} /{proxy+}"
|
|
1258
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
1259
|
+
|
|
1260
|
+
authorization_type = "CUSTOM"
|
|
1261
|
+
authorizer_id = aws_apigatewayv2_authorizer.custom_authorizer.id
|
|
1262
|
+
|
|
1263
|
+
depends_on = [aws_apigatewayv2_integration.lambda_integration, aws_apigatewayv2_authorizer.custom_authorizer]
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
# Add API url to runtime config
|
|
1267
|
+
module "add_url_to_runtime_config" {
|
|
1268
|
+
source = "../../../core/runtime-config/entry"
|
|
1269
|
+
|
|
1270
|
+
namespace = "connection"
|
|
1271
|
+
key = "apis"
|
|
1272
|
+
value = { "TestApi" = module.http_api.stage_invoke_url }
|
|
1273
|
+
|
|
1274
|
+
depends_on = [module.http_api]
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
1278
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
1279
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
1280
|
+
action = "lambda:InvokeFunction"
|
|
1281
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
1282
|
+
qualifier = aws_lambda_alias.live.name
|
|
1283
|
+
principal = "apigateway.amazonaws.com"
|
|
1284
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/*"
|
|
1285
|
+
|
|
1286
|
+
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
# Outputs
|
|
1290
|
+
|
|
1291
|
+
# API Gateway Outputs (from core module)
|
|
1292
|
+
output "api_id" {
|
|
1293
|
+
description = "ID of the HTTP API Gateway"
|
|
1294
|
+
value = module.http_api.api_id
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
output "api_arn" {
|
|
1298
|
+
description = "ARN of the HTTP API Gateway"
|
|
1299
|
+
value = module.http_api.api_arn
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
output "api_endpoint" {
|
|
1303
|
+
description = "Base URL of the HTTP API Gateway"
|
|
1304
|
+
value = module.http_api.api_endpoint
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
output "api_execution_arn" {
|
|
1308
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
1309
|
+
value = module.http_api.api_execution_arn
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
output "stage_invoke_url" {
|
|
1313
|
+
description = "Invoke URL of the API Gateway stage"
|
|
1314
|
+
value = module.http_api.stage_invoke_url
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
output "stage_arn" {
|
|
1318
|
+
description = "ARN of the API Gateway stage"
|
|
1319
|
+
value = module.http_api.stage_arn
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
output "stage_execution_arn" {
|
|
1323
|
+
description = "Execution ARN of the API Gateway stage"
|
|
1324
|
+
value = module.http_api.stage_execution_arn
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
# Lambda Function Outputs
|
|
1328
|
+
output "lambda_function_name" {
|
|
1329
|
+
description = "Name of the Lambda function"
|
|
1330
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
output "lambda_function_arn" {
|
|
1334
|
+
description = "ARN of the Lambda function"
|
|
1335
|
+
value = aws_lambda_function.api_lambda.arn
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
output "lambda_invoke_arn" {
|
|
1339
|
+
description = "Invoke ARN of the Lambda function"
|
|
1340
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
output "lambda_qualified_arn" {
|
|
1344
|
+
description = "Qualified ARN of the Lambda function"
|
|
1345
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
output "lambda_version" {
|
|
1349
|
+
description = "Version of the Lambda function"
|
|
1350
|
+
value = aws_lambda_function.api_lambda.version
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
output "lambda_source_code_hash" {
|
|
1354
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
1355
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
output "lambda_source_code_size" {
|
|
1359
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
1360
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
# IAM Role Outputs
|
|
1364
|
+
output "lambda_execution_role_arn" {
|
|
1365
|
+
description = "ARN of the Lambda execution role"
|
|
1366
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
output "lambda_execution_role_name" {
|
|
1370
|
+
description = "Name of the Lambda execution role"
|
|
1371
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
# Integration Outputs
|
|
1375
|
+
output "integration_id" {
|
|
1376
|
+
description = "ID of the Lambda integration"
|
|
1377
|
+
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
# CloudWatch Log Groups
|
|
1381
|
+
output "lambda_log_group_name" {
|
|
1382
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
1383
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
output "lambda_log_group_arn" {
|
|
1387
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
1388
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
output "api_log_group_name" {
|
|
1392
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
1393
|
+
value = module.http_api.api_log_group_name
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
output "api_log_group_arn" {
|
|
1397
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
1398
|
+
value = module.http_api.api_log_group_arn
|
|
1399
|
+
}",
|
|
1400
|
+
}
|
|
1401
|
+
`;
|
|
1402
|
+
|
|
1403
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for HTTP API with IAM auth and snapshot them > terraform-http-iam-files 1`] = `
|
|
1404
|
+
{
|
|
1405
|
+
"http-api.tf": "# Core HTTP API Gateway module
|
|
1406
|
+
# This module creates the API Gateway HTTP API, stage, and logging resources
|
|
1407
|
+
|
|
1408
|
+
terraform {
|
|
1409
|
+
required_version = ">= 1.0"
|
|
1410
|
+
|
|
1411
|
+
required_providers {
|
|
1412
|
+
aws = {
|
|
1413
|
+
source = "hashicorp/aws"
|
|
1414
|
+
version = "6.52.0"
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
# Core HTTP API Gateway Variables
|
|
1420
|
+
|
|
1421
|
+
variable "api_name" {
|
|
1422
|
+
description = "Name of the HTTP API Gateway"
|
|
1423
|
+
type = string
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
variable "api_description" {
|
|
1427
|
+
description = "Description of the HTTP API Gateway"
|
|
1428
|
+
type = string
|
|
1429
|
+
default = "HTTP API Gateway"
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
variable "stage_name" {
|
|
1433
|
+
description = "Name of the API Gateway stage"
|
|
1434
|
+
type = string
|
|
1435
|
+
default = "prod"
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
variable "stage_auto_deploy" {
|
|
1439
|
+
description = "Whether to automatically deploy the API stage"
|
|
1440
|
+
type = bool
|
|
1441
|
+
default = true
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
# CORS Configuration
|
|
1445
|
+
|
|
1446
|
+
variable "cors_allow_credentials" {
|
|
1447
|
+
description = "Whether to allow credentials in CORS requests"
|
|
1448
|
+
type = bool
|
|
1449
|
+
default = false
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
variable "cors_allow_headers" {
|
|
1453
|
+
description = "List of allowed headers for CORS"
|
|
1454
|
+
type = list(string)
|
|
1455
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
variable "cors_allow_methods" {
|
|
1459
|
+
description = "List of allowed HTTP methods for CORS"
|
|
1460
|
+
type = list(string)
|
|
1461
|
+
default = ["*"]
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
variable "cors_allow_origins" {
|
|
1465
|
+
description = "List of allowed origins for CORS"
|
|
1466
|
+
type = list(string)
|
|
1467
|
+
default = ["*"]
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
variable "cors_expose_headers" {
|
|
1471
|
+
description = "List of headers to expose in CORS responses"
|
|
1472
|
+
type = list(string)
|
|
1473
|
+
default = []
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
variable "cors_max_age" {
|
|
1477
|
+
description = "Maximum age for CORS preflight requests in seconds"
|
|
1478
|
+
type = number
|
|
1479
|
+
default = 86400
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
# Tags
|
|
1483
|
+
|
|
1484
|
+
variable "tags" {
|
|
1485
|
+
description = "Tags to apply to all resources"
|
|
1486
|
+
type = map(string)
|
|
1487
|
+
default = {}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
# Data sources
|
|
1491
|
+
data "aws_region" "current" {}
|
|
1492
|
+
data "aws_caller_identity" "current" {}
|
|
1493
|
+
|
|
1494
|
+
# Resources
|
|
1495
|
+
|
|
1496
|
+
# KMS key for CloudWatch log group encryption
|
|
1497
|
+
resource "aws_kms_key" "logs_key" {
|
|
1498
|
+
description = "KMS key for CloudWatch log group encryption"
|
|
1499
|
+
deletion_window_in_days = 7
|
|
1500
|
+
enable_key_rotation = true
|
|
1501
|
+
|
|
1502
|
+
policy = jsonencode({
|
|
1503
|
+
Version = "2012-10-17"
|
|
1504
|
+
Statement = [
|
|
1505
|
+
{
|
|
1506
|
+
Sid = "Enable IAM User Permissions"
|
|
1507
|
+
Effect = "Allow"
|
|
1508
|
+
Principal = {
|
|
1509
|
+
AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
|
|
1510
|
+
}
|
|
1511
|
+
Action = "kms:*"
|
|
1512
|
+
Resource = "*"
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
Sid = "Allow CloudWatch Logs"
|
|
1516
|
+
Effect = "Allow"
|
|
1517
|
+
Principal = {
|
|
1518
|
+
Service = "logs.\${data.aws_region.current.region}.amazonaws.com"
|
|
1519
|
+
}
|
|
1520
|
+
Action = [
|
|
1521
|
+
"kms:Encrypt",
|
|
1522
|
+
"kms:Decrypt",
|
|
1523
|
+
"kms:ReEncrypt*",
|
|
1524
|
+
"kms:GenerateDataKey*",
|
|
1525
|
+
"kms:DescribeKey"
|
|
1526
|
+
]
|
|
1527
|
+
Resource = "*"
|
|
1528
|
+
Condition = {
|
|
1529
|
+
ArnEquals = {
|
|
1530
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/\${var.api_name}"
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
]
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
tags = var.tags
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
resource "aws_kms_alias" "logs_key_alias" {
|
|
1541
|
+
name = "alias/\${var.api_name}-api-logs-encryption"
|
|
1542
|
+
target_key_id = aws_kms_key.logs_key.key_id
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
# HTTP API Gateway
|
|
1546
|
+
resource "aws_apigatewayv2_api" "http_api" {
|
|
1547
|
+
name = var.api_name
|
|
1548
|
+
protocol_type = "HTTP"
|
|
1549
|
+
description = var.api_description
|
|
1550
|
+
|
|
1551
|
+
cors_configuration {
|
|
1552
|
+
allow_credentials = var.cors_allow_credentials
|
|
1553
|
+
allow_headers = var.cors_allow_headers
|
|
1554
|
+
allow_methods = var.cors_allow_methods
|
|
1555
|
+
allow_origins = var.cors_allow_origins
|
|
1556
|
+
expose_headers = var.cors_expose_headers
|
|
1557
|
+
max_age = var.cors_max_age
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
tags = var.tags
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
# API Gateway stage
|
|
1564
|
+
resource "aws_apigatewayv2_stage" "api_stage" {
|
|
1565
|
+
api_id = aws_apigatewayv2_api.http_api.id
|
|
1566
|
+
name = var.stage_name
|
|
1567
|
+
auto_deploy = var.stage_auto_deploy
|
|
1568
|
+
|
|
1569
|
+
access_log_settings {
|
|
1570
|
+
destination_arn = aws_cloudwatch_log_group.api_logs.arn
|
|
1571
|
+
format = jsonencode({
|
|
1572
|
+
requestId = "$context.requestId"
|
|
1573
|
+
ip = "$context.identity.sourceIp"
|
|
1574
|
+
requestTime = "$context.requestTime"
|
|
1575
|
+
httpMethod = "$context.httpMethod"
|
|
1576
|
+
routeKey = "$context.routeKey"
|
|
1577
|
+
status = "$context.status"
|
|
1578
|
+
protocol = "$context.protocol"
|
|
1579
|
+
responseLength = "$context.responseLength"
|
|
1580
|
+
error = "$context.error.message"
|
|
1581
|
+
integrationError = "$context.integrationErrorMessage"
|
|
1582
|
+
})
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
default_route_settings {
|
|
1586
|
+
throttling_burst_limit = 5000
|
|
1587
|
+
throttling_rate_limit = 10000
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
tags = var.tags
|
|
1591
|
+
|
|
1592
|
+
depends_on = [aws_cloudwatch_log_group.api_logs]
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
# CloudWatch Log Group for API Gateway
|
|
1596
|
+
resource "aws_cloudwatch_log_group" "api_logs" {
|
|
1597
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1598
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1599
|
+
name = "/aws/apigateway/\${var.api_name}"
|
|
1600
|
+
kms_key_id = aws_kms_key.logs_key.arn
|
|
1601
|
+
tags = var.tags
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
# Outputs
|
|
1605
|
+
|
|
1606
|
+
output "api_id" {
|
|
1607
|
+
description = "ID of the HTTP API Gateway"
|
|
1608
|
+
value = aws_apigatewayv2_api.http_api.id
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
output "api_arn" {
|
|
1612
|
+
description = "ARN of the HTTP API Gateway"
|
|
1613
|
+
value = aws_apigatewayv2_api.http_api.arn
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
output "api_endpoint" {
|
|
1617
|
+
description = "Base URL of the HTTP API Gateway"
|
|
1618
|
+
value = aws_apigatewayv2_api.http_api.api_endpoint
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
output "api_execution_arn" {
|
|
1622
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
1623
|
+
value = aws_apigatewayv2_api.http_api.execution_arn
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
output "stage_id" {
|
|
1627
|
+
description = "ID of the API Gateway stage"
|
|
1628
|
+
value = aws_apigatewayv2_stage.api_stage.id
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
output "stage_arn" {
|
|
1632
|
+
description = "ARN of the API Gateway stage"
|
|
1633
|
+
value = aws_apigatewayv2_stage.api_stage.arn
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
output "stage_execution_arn" {
|
|
1637
|
+
description = "Execution ARN of the API Gateway stage"
|
|
1638
|
+
value = aws_apigatewayv2_stage.api_stage.execution_arn
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
output "stage_invoke_url" {
|
|
1642
|
+
description = "Invoke URL of the API Gateway stage"
|
|
1643
|
+
value = aws_apigatewayv2_stage.api_stage.invoke_url
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
output "api_log_group_name" {
|
|
1647
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
1648
|
+
value = aws_cloudwatch_log_group.api_logs.name
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
output "api_log_group_arn" {
|
|
1652
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
1653
|
+
value = aws_cloudwatch_log_group.api_logs.arn
|
|
1654
|
+
}",
|
|
1655
|
+
"test-api.tf": "terraform {
|
|
1656
|
+
required_version = ">= 1.0"
|
|
1657
|
+
|
|
1658
|
+
required_providers {
|
|
1659
|
+
aws = {
|
|
1660
|
+
source = "hashicorp/aws"
|
|
1661
|
+
version = "6.52.0"
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
variable "env" {
|
|
1668
|
+
description = "Environment variables for the Lambda function"
|
|
1669
|
+
type = map(string)
|
|
1670
|
+
default = {}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
variable "additional_iam_policy_statements" {
|
|
1674
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
1675
|
+
type = list(object({
|
|
1676
|
+
Effect = string
|
|
1677
|
+
Action = list(string)
|
|
1678
|
+
Resource = list(string)
|
|
1679
|
+
}))
|
|
1680
|
+
default = []
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
variable "asset_bucket_name" {
|
|
1684
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
1685
|
+
type = string
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
# CORS Configuration (passed to core module)
|
|
1689
|
+
variable "cors_allow_credentials" {
|
|
1690
|
+
description = "Whether to allow credentials in CORS requests"
|
|
1691
|
+
type = bool
|
|
1692
|
+
default = false
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
variable "cors_allow_headers" {
|
|
1696
|
+
description = "List of allowed headers for CORS"
|
|
1697
|
+
type = list(string)
|
|
1698
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
variable "cors_allow_methods" {
|
|
1702
|
+
description = "List of allowed HTTP methods for CORS"
|
|
1703
|
+
type = list(string)
|
|
1704
|
+
default = ["*"]
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
variable "cors_allow_origins" {
|
|
1708
|
+
description = "List of allowed origins for CORS"
|
|
1709
|
+
type = list(string)
|
|
1710
|
+
default = ["*"]
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
variable "cors_expose_headers" {
|
|
1714
|
+
description = "List of headers to expose in CORS responses"
|
|
1715
|
+
type = list(string)
|
|
1716
|
+
default = []
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
# Tags
|
|
1720
|
+
variable "tags" {
|
|
1721
|
+
description = "Tags to apply to all resources"
|
|
1722
|
+
type = map(string)
|
|
1723
|
+
default = {}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
# Get current AWS region and account ID
|
|
1727
|
+
data "aws_region" "current" {}
|
|
1728
|
+
data "aws_caller_identity" "current" {}
|
|
1729
|
+
|
|
1730
|
+
resource "random_string" "suffix" {
|
|
1731
|
+
length = 8
|
|
1732
|
+
special = false
|
|
1733
|
+
upper = false
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
# Resources
|
|
1737
|
+
|
|
1738
|
+
# Create Lambda ZIP file from the bundle directory
|
|
1739
|
+
data "archive_file" "lambda_zip" {
|
|
1740
|
+
type = "zip"
|
|
1741
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
1742
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
1746
|
+
bucket = var.asset_bucket_name
|
|
1747
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
1748
|
+
source = data.archive_file.lambda_zip.output_path
|
|
1749
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
1750
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
# Use the core HTTP API module
|
|
1755
|
+
module "http_api" {
|
|
1756
|
+
source = "../../../core/api/http-api"
|
|
1757
|
+
|
|
1758
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
1759
|
+
api_description = "TestApi HTTP API"
|
|
1760
|
+
stage_name = "$default"
|
|
1761
|
+
stage_auto_deploy = true
|
|
1762
|
+
|
|
1763
|
+
# CORS Configuration
|
|
1764
|
+
cors_allow_credentials = var.cors_allow_credentials
|
|
1765
|
+
cors_allow_headers = var.cors_allow_headers
|
|
1766
|
+
cors_allow_methods = var.cors_allow_methods
|
|
1767
|
+
cors_allow_origins = var.cors_allow_origins
|
|
1768
|
+
cors_expose_headers = var.cors_expose_headers
|
|
1769
|
+
|
|
1770
|
+
# Tags
|
|
1771
|
+
tags = var.tags
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
# Lambda function
|
|
1775
|
+
# This configures a single "router" lambda to serve all requests
|
|
1776
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
1777
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
1778
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
1779
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
1780
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
1781
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
1782
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
1783
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
1784
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
1785
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
1786
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
1787
|
+
handler = "run.sh"
|
|
1788
|
+
runtime = "python3.14"
|
|
1789
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
1790
|
+
timeout = 30
|
|
1791
|
+
memory_size = 128
|
|
1792
|
+
publish = true
|
|
1793
|
+
|
|
1794
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
1795
|
+
|
|
1796
|
+
# Enable X-Ray tracing
|
|
1797
|
+
tracing_config {
|
|
1798
|
+
mode = "Active"
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
# Enable SnapStart for faster cold starts
|
|
1802
|
+
snap_start {
|
|
1803
|
+
apply_on = "PublishedVersions"
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
environment {
|
|
1807
|
+
variables = merge({
|
|
1808
|
+
PORT = "8000"
|
|
1809
|
+
AWS_LWA_INVOKE_MODE = "buffered"
|
|
1810
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
1811
|
+
}, var.env)
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
tags = var.tags
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
# IAM role for Lambda execution
|
|
1818
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
1819
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
1820
|
+
|
|
1821
|
+
assume_role_policy = jsonencode({
|
|
1822
|
+
Version = "2012-10-17"
|
|
1823
|
+
Statement = [
|
|
1824
|
+
{
|
|
1825
|
+
Action = "sts:AssumeRole"
|
|
1826
|
+
Effect = "Allow"
|
|
1827
|
+
Principal = {
|
|
1828
|
+
Service = "lambda.amazonaws.com"
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
]
|
|
1832
|
+
})
|
|
1833
|
+
|
|
1834
|
+
tags = var.tags
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
# Attach basic execution policy to Lambda role
|
|
1838
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
1839
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
1840
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
1844
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
1845
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
1846
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
# Additional IAM policies for Lambda (if provided)
|
|
1850
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
1851
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
1852
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
1853
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
1854
|
+
|
|
1855
|
+
policy = jsonencode({
|
|
1856
|
+
Version = "2012-10-17"
|
|
1857
|
+
Statement = var.additional_iam_policy_statements
|
|
1858
|
+
})
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
# CloudWatch Log Group for Lambda
|
|
1862
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
1863
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
1864
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1865
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1866
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
1867
|
+
tags = var.tags
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
1871
|
+
resource "aws_lambda_alias" "live" {
|
|
1872
|
+
name = "live"
|
|
1873
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
1874
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
1875
|
+
|
|
1876
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
# Lambda integration for HTTP API
|
|
1881
|
+
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
1882
|
+
api_id = module.http_api.api_id
|
|
1883
|
+
integration_type = "AWS_PROXY"
|
|
1884
|
+
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
1885
|
+
|
|
1886
|
+
payload_format_version = "2.0"
|
|
1887
|
+
timeout_milliseconds = 30000
|
|
1888
|
+
|
|
1889
|
+
depends_on = [aws_lambda_alias.live]
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
# Route for proxy integration (catches all requests)
|
|
1893
|
+
resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
1894
|
+
# NB: OPTIONS is omitted here since API Gateway manages responding to preflight requests
|
|
1895
|
+
# when cors settings are configured
|
|
1896
|
+
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
1897
|
+
|
|
1898
|
+
api_id = module.http_api.api_id
|
|
1899
|
+
route_key = "\${each.key} /{proxy+}"
|
|
1900
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
1901
|
+
|
|
1902
|
+
authorization_type = "AWS_IAM"
|
|
1903
|
+
|
|
1904
|
+
depends_on = [aws_apigatewayv2_integration.lambda_integration]
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
# Add API url to runtime config
|
|
1908
|
+
module "add_url_to_runtime_config" {
|
|
1909
|
+
source = "../../../core/runtime-config/entry"
|
|
1910
|
+
|
|
1911
|
+
namespace = "connection"
|
|
1912
|
+
key = "apis"
|
|
1913
|
+
value = { "TestApi" = module.http_api.stage_invoke_url }
|
|
1914
|
+
|
|
1915
|
+
depends_on = [module.http_api]
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
1919
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
1920
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
1921
|
+
action = "lambda:InvokeFunction"
|
|
1922
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
1923
|
+
qualifier = aws_lambda_alias.live.name
|
|
1924
|
+
principal = "apigateway.amazonaws.com"
|
|
1925
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/*"
|
|
1926
|
+
|
|
1927
|
+
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
# Outputs
|
|
1931
|
+
|
|
1932
|
+
# API Gateway Outputs (from core module)
|
|
1933
|
+
output "api_id" {
|
|
1934
|
+
description = "ID of the HTTP API Gateway"
|
|
1935
|
+
value = module.http_api.api_id
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
output "api_arn" {
|
|
1939
|
+
description = "ARN of the HTTP API Gateway"
|
|
1940
|
+
value = module.http_api.api_arn
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
output "api_endpoint" {
|
|
1944
|
+
description = "Base URL of the HTTP API Gateway"
|
|
1945
|
+
value = module.http_api.api_endpoint
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
output "api_execution_arn" {
|
|
1949
|
+
description = "Execution ARN of the HTTP API Gateway"
|
|
1950
|
+
value = module.http_api.api_execution_arn
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
output "stage_invoke_url" {
|
|
1954
|
+
description = "Invoke URL of the API Gateway stage"
|
|
1955
|
+
value = module.http_api.stage_invoke_url
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
output "stage_arn" {
|
|
1959
|
+
description = "ARN of the API Gateway stage"
|
|
1960
|
+
value = module.http_api.stage_arn
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
output "stage_execution_arn" {
|
|
1964
|
+
description = "Execution ARN of the API Gateway stage"
|
|
1965
|
+
value = module.http_api.stage_execution_arn
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
# Lambda Function Outputs
|
|
1969
|
+
output "lambda_function_name" {
|
|
1970
|
+
description = "Name of the Lambda function"
|
|
1971
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
output "lambda_function_arn" {
|
|
1975
|
+
description = "ARN of the Lambda function"
|
|
1976
|
+
value = aws_lambda_function.api_lambda.arn
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
output "lambda_invoke_arn" {
|
|
1980
|
+
description = "Invoke ARN of the Lambda function"
|
|
1981
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
output "lambda_qualified_arn" {
|
|
1985
|
+
description = "Qualified ARN of the Lambda function"
|
|
1986
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
output "lambda_version" {
|
|
1990
|
+
description = "Version of the Lambda function"
|
|
1991
|
+
value = aws_lambda_function.api_lambda.version
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
output "lambda_source_code_hash" {
|
|
1995
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
1996
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
output "lambda_source_code_size" {
|
|
2000
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
2001
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
# IAM Role Outputs
|
|
2005
|
+
output "lambda_execution_role_arn" {
|
|
2006
|
+
description = "ARN of the Lambda execution role"
|
|
2007
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
output "lambda_execution_role_name" {
|
|
2011
|
+
description = "Name of the Lambda execution role"
|
|
2012
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
# Integration Outputs
|
|
2016
|
+
output "integration_id" {
|
|
2017
|
+
description = "ID of the Lambda integration"
|
|
2018
|
+
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
# CloudWatch Log Groups
|
|
2022
|
+
output "lambda_log_group_name" {
|
|
2023
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
2024
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
output "lambda_log_group_arn" {
|
|
2028
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
2029
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
output "api_log_group_name" {
|
|
2033
|
+
description = "Name of the API Gateway CloudWatch log group"
|
|
2034
|
+
value = module.http_api.api_log_group_name
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
output "api_log_group_arn" {
|
|
2038
|
+
description = "ARN of the API Gateway CloudWatch log group"
|
|
2039
|
+
value = module.http_api.api_log_group_arn
|
|
2040
|
+
}",
|
|
2041
|
+
}
|
|
2042
|
+
`;
|
|
2043
|
+
|
|
2044
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for REST API with Cognito auth and snapshot them > terraform-rest-cognito-files 1`] = `
|
|
2045
|
+
{
|
|
2046
|
+
"rest-api.tf": "# Core REST API Gateway module
|
|
2047
|
+
# This module creates the API Gateway REST API, deployment, stage, and logging resources
|
|
2048
|
+
|
|
2049
|
+
terraform {
|
|
2050
|
+
required_version = ">= 1.0"
|
|
2051
|
+
|
|
2052
|
+
required_providers {
|
|
2053
|
+
aws = {
|
|
2054
|
+
source = "hashicorp/aws"
|
|
2055
|
+
version = "6.52.0"
|
|
2056
|
+
}
|
|
2057
|
+
random = {
|
|
2058
|
+
source = "hashicorp/random"
|
|
2059
|
+
version = "3.9.0"
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
# Core REST API Gateway Variables
|
|
2065
|
+
|
|
2066
|
+
variable "api_name" {
|
|
2067
|
+
description = "Name of the REST API Gateway"
|
|
2068
|
+
type = string
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
variable "api_description" {
|
|
2072
|
+
description = "Description of the REST API Gateway"
|
|
2073
|
+
type = string
|
|
2074
|
+
default = "REST API Gateway"
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
variable "stage_name" {
|
|
2078
|
+
description = "Name of the API Gateway stage"
|
|
2079
|
+
type = string
|
|
2080
|
+
default = "prod"
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
variable "stage_auto_deploy" {
|
|
2084
|
+
description = "Whether to automatically deploy the API stage"
|
|
2085
|
+
type = bool
|
|
2086
|
+
default = true
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
variable "enable_waf" {
|
|
2090
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
|
|
2091
|
+
type = bool
|
|
2092
|
+
default = true
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
# CORS Configuration
|
|
2096
|
+
|
|
2097
|
+
variable "cors_allow_headers" {
|
|
2098
|
+
description = "List of allowed headers for CORS"
|
|
2099
|
+
type = list(string)
|
|
2100
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
variable "cors_allow_methods" {
|
|
2104
|
+
description = "List of allowed HTTP methods for CORS"
|
|
2105
|
+
type = list(string)
|
|
2106
|
+
default = ["*"]
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
variable "cors_allow_origins" {
|
|
2110
|
+
description = "List of allowed origins for CORS"
|
|
2111
|
+
type = list(string)
|
|
2112
|
+
default = ["*"]
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
# Tags
|
|
2116
|
+
|
|
2117
|
+
variable "tags" {
|
|
2118
|
+
description = "Tags to apply to all resources"
|
|
2119
|
+
type = map(string)
|
|
2120
|
+
default = {}
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
# Data sources
|
|
2124
|
+
data "aws_region" "current" {}
|
|
2125
|
+
data "aws_caller_identity" "current" {}
|
|
2126
|
+
|
|
2127
|
+
# Resources
|
|
2128
|
+
|
|
2129
|
+
# REST API Gateway
|
|
2130
|
+
resource "aws_api_gateway_rest_api" "rest_api" {
|
|
2131
|
+
name = var.api_name
|
|
2132
|
+
description = var.api_description
|
|
2133
|
+
|
|
2134
|
+
endpoint_configuration {
|
|
2135
|
+
types = ["REGIONAL"]
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
lifecycle {
|
|
2139
|
+
create_before_destroy = true
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
tags = var.tags
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
# Note: Deployment, stage and access logging are created in the consuming module
|
|
2146
|
+
# (e.g., foo-api.tf) after all methods and integrations are defined
|
|
2147
|
+
|
|
2148
|
+
# Gateway Response for CORS (4XX errors)
|
|
2149
|
+
resource "aws_api_gateway_gateway_response" "cors_4xx" {
|
|
2150
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
2151
|
+
response_type = "DEFAULT_4XX"
|
|
2152
|
+
|
|
2153
|
+
response_parameters = {
|
|
2154
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
2155
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
2156
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
# Gateway Response for CORS (5XX errors)
|
|
2161
|
+
resource "aws_api_gateway_gateway_response" "cors_5xx" {
|
|
2162
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
2163
|
+
response_type = "DEFAULT_5XX"
|
|
2164
|
+
|
|
2165
|
+
response_parameters = {
|
|
2166
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
2167
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
2168
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
resource "aws_wafv2_web_acl" "api_waf" {
|
|
2173
|
+
#checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
|
|
2174
|
+
count = var.enable_waf ? 1 : 0
|
|
2175
|
+
|
|
2176
|
+
name = "\${var.api_name}-waf"
|
|
2177
|
+
scope = "REGIONAL"
|
|
2178
|
+
|
|
2179
|
+
default_action {
|
|
2180
|
+
allow {}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
rule {
|
|
2184
|
+
name = "CRSRule"
|
|
2185
|
+
priority = 0
|
|
2186
|
+
|
|
2187
|
+
override_action {
|
|
2188
|
+
none {}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
statement {
|
|
2192
|
+
managed_rule_group_statement {
|
|
2193
|
+
name = "AWSManagedRulesCommonRuleSet"
|
|
2194
|
+
vendor_name = "AWS"
|
|
2195
|
+
|
|
2196
|
+
# Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
|
|
2197
|
+
rule_action_override {
|
|
2198
|
+
name = "SizeRestrictions_BODY"
|
|
2199
|
+
action_to_use {
|
|
2200
|
+
count {}
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
visibility_config {
|
|
2207
|
+
cloudwatch_metrics_enabled = true
|
|
2208
|
+
metric_name = "\${var.api_name}WebAcl-CRS"
|
|
2209
|
+
sampled_requests_enabled = true
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
rule {
|
|
2214
|
+
name = "KnownBadInputsRule"
|
|
2215
|
+
priority = 1
|
|
2216
|
+
|
|
2217
|
+
override_action {
|
|
2218
|
+
none {}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
statement {
|
|
2222
|
+
managed_rule_group_statement {
|
|
2223
|
+
name = "AWSManagedRulesKnownBadInputsRuleSet"
|
|
2224
|
+
vendor_name = "AWS"
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
visibility_config {
|
|
2229
|
+
cloudwatch_metrics_enabled = true
|
|
2230
|
+
metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
|
|
2231
|
+
sampled_requests_enabled = true
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
visibility_config {
|
|
2236
|
+
cloudwatch_metrics_enabled = true
|
|
2237
|
+
metric_name = "\${var.api_name}WebAcl"
|
|
2238
|
+
sampled_requests_enabled = true
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
tags = var.tags
|
|
2242
|
+
|
|
2243
|
+
lifecycle {
|
|
2244
|
+
create_before_destroy = true
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
2249
|
+
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
2250
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2251
|
+
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
2252
|
+
count = var.enable_waf ? 1 : 0
|
|
2253
|
+
|
|
2254
|
+
name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
|
|
2255
|
+
retention_in_days = 30
|
|
2256
|
+
tags = var.tags
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
resource "random_id" "waf_log_suffix" {
|
|
2260
|
+
count = var.enable_waf ? 1 : 0
|
|
2261
|
+
byte_length = 4
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
2265
|
+
count = var.enable_waf ? 1 : 0
|
|
2266
|
+
|
|
2267
|
+
log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
|
|
2268
|
+
resource_arn = aws_wafv2_web_acl.api_waf[0].arn
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
|
|
2272
|
+
# Outputs
|
|
2273
|
+
|
|
2274
|
+
output "api_id" {
|
|
2275
|
+
description = "ID of the REST API Gateway"
|
|
2276
|
+
value = aws_api_gateway_rest_api.rest_api.id
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
output "api_arn" {
|
|
2280
|
+
description = "ARN of the REST API Gateway"
|
|
2281
|
+
value = aws_api_gateway_rest_api.rest_api.arn
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
output "api_endpoint" {
|
|
2285
|
+
description = "Base URL of the REST API Gateway"
|
|
2286
|
+
value = "https://\${aws_api_gateway_rest_api.rest_api.id}.execute-api.\${data.aws_region.current.id}.amazonaws.com"
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
output "api_execution_arn" {
|
|
2290
|
+
description = "Execution ARN of the REST API Gateway"
|
|
2291
|
+
value = aws_api_gateway_rest_api.rest_api.execution_arn
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
output "api_root_resource_id" {
|
|
2295
|
+
description = "Root resource ID of the REST API Gateway"
|
|
2296
|
+
value = aws_api_gateway_rest_api.rest_api.root_resource_id
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
output "waf_web_acl_arn" {
|
|
2300
|
+
description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
|
|
2301
|
+
value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
# Note: Stage, deployment and access log outputs are provided by the consuming module",
|
|
2305
|
+
"test-api.tf": "terraform {
|
|
2306
|
+
required_version = ">= 1.0"
|
|
2307
|
+
|
|
2308
|
+
required_providers {
|
|
2309
|
+
aws = {
|
|
2310
|
+
source = "hashicorp/aws"
|
|
2311
|
+
version = "6.52.0"
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
# Authentication Configuration
|
|
2317
|
+
variable "user_pool_id" {
|
|
2318
|
+
description = "Cognito User Pool ID for authentication"
|
|
2319
|
+
type = string
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
variable "user_pool_client_ids" {
|
|
2323
|
+
description = "List of Cognito User Pool Client IDs"
|
|
2324
|
+
type = list(string)
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
variable "env" {
|
|
2328
|
+
description = "Environment variables for the Lambda function"
|
|
2329
|
+
type = map(string)
|
|
2330
|
+
default = {}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
variable "additional_iam_policy_statements" {
|
|
2334
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
2335
|
+
type = list(object({
|
|
2336
|
+
Effect = string
|
|
2337
|
+
Action = list(string)
|
|
2338
|
+
Resource = list(string)
|
|
2339
|
+
}))
|
|
2340
|
+
default = []
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
variable "asset_bucket_name" {
|
|
2344
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
2345
|
+
type = string
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
# CORS Configuration (passed to core module)
|
|
2349
|
+
|
|
2350
|
+
variable "cors_allow_headers" {
|
|
2351
|
+
description = "List of allowed headers for CORS"
|
|
2352
|
+
type = list(string)
|
|
2353
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
variable "cors_allow_methods" {
|
|
2357
|
+
description = "List of allowed HTTP methods for CORS"
|
|
2358
|
+
type = list(string)
|
|
2359
|
+
default = ["*"]
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
variable "cors_allow_origins" {
|
|
2363
|
+
description = "List of allowed origins for CORS"
|
|
2364
|
+
type = list(string)
|
|
2365
|
+
default = ["*"]
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
# WAF Configuration
|
|
2369
|
+
variable "enable_waf" {
|
|
2370
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
|
|
2371
|
+
type = bool
|
|
2372
|
+
default = true
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
# Tags
|
|
2376
|
+
variable "tags" {
|
|
2377
|
+
description = "Tags to apply to all resources"
|
|
2378
|
+
type = map(string)
|
|
2379
|
+
default = {}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
# Get current AWS region and account ID
|
|
2383
|
+
data "aws_region" "current" {}
|
|
2384
|
+
data "aws_caller_identity" "current" {}
|
|
2385
|
+
|
|
2386
|
+
resource "random_string" "suffix" {
|
|
2387
|
+
length = 8
|
|
2388
|
+
special = false
|
|
2389
|
+
upper = false
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
# Resources
|
|
2393
|
+
|
|
2394
|
+
# Create Lambda ZIP file from the bundle directory
|
|
2395
|
+
data "archive_file" "lambda_zip" {
|
|
2396
|
+
type = "zip"
|
|
2397
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
2398
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
2402
|
+
bucket = var.asset_bucket_name
|
|
2403
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
2404
|
+
source = data.archive_file.lambda_zip.output_path
|
|
2405
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
2406
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
# Use the core REST API module
|
|
2410
|
+
# Account-level CloudWatch role required for REST API access logging
|
|
2411
|
+
module "account" {
|
|
2412
|
+
source = "../../../core/api/api-gateway-account"
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
module "rest_api" {
|
|
2416
|
+
source = "../../../core/api/rest-api"
|
|
2417
|
+
|
|
2418
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
2419
|
+
api_description = "TestApi REST API"
|
|
2420
|
+
stage_name = "prod"
|
|
2421
|
+
stage_auto_deploy = true
|
|
2422
|
+
|
|
2423
|
+
# WAF Configuration
|
|
2424
|
+
enable_waf = var.enable_waf
|
|
2425
|
+
|
|
2426
|
+
# CORS Configuration
|
|
2427
|
+
cors_allow_headers = var.cors_allow_headers
|
|
2428
|
+
cors_allow_methods = var.cors_allow_methods
|
|
2429
|
+
cors_allow_origins = var.cors_allow_origins
|
|
2430
|
+
|
|
2431
|
+
# Tags
|
|
2432
|
+
tags = var.tags
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
resource "aws_wafv2_web_acl_association" "api_waf_association" {
|
|
2436
|
+
count = var.enable_waf ? 1 : 0
|
|
2437
|
+
|
|
2438
|
+
resource_arn = aws_api_gateway_stage.api_stage.arn
|
|
2439
|
+
web_acl_arn = module.rest_api.waf_web_acl_arn
|
|
2440
|
+
|
|
2441
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
# Lambda function
|
|
2445
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
2446
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
2447
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
2448
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
2449
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
2450
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
2451
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
2452
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
2453
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
2454
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
2455
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
2456
|
+
handler = "run.sh"
|
|
2457
|
+
runtime = "python3.14"
|
|
2458
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
2459
|
+
timeout = 30
|
|
2460
|
+
memory_size = 128
|
|
2461
|
+
publish = true
|
|
2462
|
+
|
|
2463
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
2464
|
+
|
|
2465
|
+
# Enable X-Ray tracing
|
|
2466
|
+
tracing_config {
|
|
2467
|
+
mode = "Active"
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
# Enable SnapStart for faster cold starts
|
|
2471
|
+
snap_start {
|
|
2472
|
+
apply_on = "PublishedVersions"
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
environment {
|
|
2476
|
+
variables = merge({
|
|
2477
|
+
PORT = "8000"
|
|
2478
|
+
AWS_LWA_INVOKE_MODE = "response_stream"
|
|
2479
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
2480
|
+
}, var.env)
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
tags = var.tags
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
# IAM role for Lambda execution
|
|
2487
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
2488
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
2489
|
+
|
|
2490
|
+
assume_role_policy = jsonencode({
|
|
2491
|
+
Version = "2012-10-17"
|
|
2492
|
+
Statement = [
|
|
2493
|
+
{
|
|
2494
|
+
Action = "sts:AssumeRole"
|
|
2495
|
+
Effect = "Allow"
|
|
2496
|
+
Principal = {
|
|
2497
|
+
Service = "lambda.amazonaws.com"
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
]
|
|
2501
|
+
})
|
|
2502
|
+
|
|
2503
|
+
tags = var.tags
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
# Attach basic execution policy to Lambda role
|
|
2507
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
2508
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
2509
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
2513
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
2514
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
2515
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
# Additional IAM policies for Lambda (if provided)
|
|
2519
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
2520
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
2521
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
2522
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
2523
|
+
|
|
2524
|
+
policy = jsonencode({
|
|
2525
|
+
Version = "2012-10-17"
|
|
2526
|
+
Statement = var.additional_iam_policy_statements
|
|
2527
|
+
})
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
# CloudWatch Log Group for Lambda
|
|
2531
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
2532
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2533
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
2534
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
2535
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
2536
|
+
tags = var.tags
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
2540
|
+
resource "aws_lambda_alias" "live" {
|
|
2541
|
+
name = "live"
|
|
2542
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
2543
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
2544
|
+
|
|
2545
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
# Cognito User Pool Authorizer
|
|
2549
|
+
resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
2550
|
+
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
2551
|
+
rest_api_id = module.rest_api.api_id
|
|
2552
|
+
type = "COGNITO_USER_POOLS"
|
|
2553
|
+
provider_arns = ["arn:aws:cognito-idp:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:userpool/\${var.user_pool_id}"]
|
|
2554
|
+
identity_source = "method.request.header.Authorization"
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
# Create proxy resource (captures all paths)
|
|
2558
|
+
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
2559
|
+
rest_api_id = module.rest_api.api_id
|
|
2560
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
2561
|
+
path_part = "{proxy+}"
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
# Lambda integration for REST API
|
|
2565
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
2566
|
+
rest_api_id = module.rest_api.api_id
|
|
2567
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2568
|
+
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
2569
|
+
|
|
2570
|
+
integration_http_method = "POST"
|
|
2571
|
+
type = "AWS_PROXY"
|
|
2572
|
+
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
2573
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
2574
|
+
response_transfer_mode = "STREAM"
|
|
2575
|
+
|
|
2576
|
+
depends_on = [aws_lambda_alias.live]
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
# Method for proxy integration
|
|
2580
|
+
resource "aws_api_gateway_method" "proxy_method" {
|
|
2581
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
2582
|
+
rest_api_id = module.rest_api.api_id
|
|
2583
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2584
|
+
http_method = "ANY"
|
|
2585
|
+
|
|
2586
|
+
authorization = "COGNITO_USER_POOLS"
|
|
2587
|
+
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
2588
|
+
# Accept access tokens from both sign-in flows: 'openid' (Cognito hosted UI)
|
|
2589
|
+
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
2590
|
+
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
2591
|
+
|
|
2592
|
+
request_parameters = {
|
|
2593
|
+
"method.request.path.proxy" = true
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
# OPTIONS method for CORS preflight
|
|
2600
|
+
resource "aws_api_gateway_method" "options_method" {
|
|
2601
|
+
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
2602
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
2603
|
+
rest_api_id = module.rest_api.api_id
|
|
2604
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2605
|
+
http_method = "OPTIONS"
|
|
2606
|
+
authorization = "NONE"
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
# CORS integration for OPTIONS method
|
|
2610
|
+
resource "aws_api_gateway_integration" "options_integration" {
|
|
2611
|
+
rest_api_id = module.rest_api.api_id
|
|
2612
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2613
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
2614
|
+
|
|
2615
|
+
type = "MOCK"
|
|
2616
|
+
request_templates = {
|
|
2617
|
+
"application/json" = "{\\"statusCode\\": 204}"
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
# OPTIONS method response
|
|
2622
|
+
resource "aws_api_gateway_method_response" "options_response" {
|
|
2623
|
+
rest_api_id = module.rest_api.api_id
|
|
2624
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2625
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
2626
|
+
status_code = "204"
|
|
2627
|
+
|
|
2628
|
+
response_parameters = {
|
|
2629
|
+
"method.response.header.Access-Control-Allow-Headers" = true
|
|
2630
|
+
"method.response.header.Access-Control-Allow-Methods" = true
|
|
2631
|
+
"method.response.header.Access-Control-Allow-Origin" = true
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
# OPTIONS integration response
|
|
2636
|
+
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
2637
|
+
rest_api_id = module.rest_api.api_id
|
|
2638
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2639
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
2640
|
+
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
2641
|
+
|
|
2642
|
+
response_parameters = {
|
|
2643
|
+
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
2644
|
+
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
2645
|
+
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
# API Gateway deployment
|
|
2650
|
+
resource "aws_api_gateway_deployment" "api_deployment" {
|
|
2651
|
+
rest_api_id = module.rest_api.api_id
|
|
2652
|
+
|
|
2653
|
+
triggers = {
|
|
2654
|
+
redeployment = sha1(jsonencode([
|
|
2655
|
+
aws_api_gateway_resource.proxy_resource.id,
|
|
2656
|
+
aws_api_gateway_method.proxy_method.id,
|
|
2657
|
+
aws_api_gateway_integration.lambda_integration.id,
|
|
2658
|
+
aws_api_gateway_method.options_method.id,
|
|
2659
|
+
aws_api_gateway_integration.options_integration.id,
|
|
2660
|
+
]))
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
lifecycle {
|
|
2664
|
+
create_before_destroy = true
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
depends_on = [
|
|
2668
|
+
aws_api_gateway_method.proxy_method,
|
|
2669
|
+
aws_api_gateway_integration.lambda_integration,
|
|
2670
|
+
aws_api_gateway_method.options_method,
|
|
2671
|
+
aws_api_gateway_integration.options_integration,
|
|
2672
|
+
aws_api_gateway_method_response.options_response,
|
|
2673
|
+
aws_api_gateway_integration_response.options_integration_response,
|
|
2674
|
+
]
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
# KMS key for encrypting access logs at rest
|
|
2678
|
+
resource "aws_kms_key" "access_logs" {
|
|
2679
|
+
description = "TestApi API access log encryption"
|
|
2680
|
+
deletion_window_in_days = 7
|
|
2681
|
+
enable_key_rotation = true
|
|
2682
|
+
|
|
2683
|
+
policy = jsonencode({
|
|
2684
|
+
Version = "2012-10-17"
|
|
2685
|
+
Statement = [
|
|
2686
|
+
{
|
|
2687
|
+
Sid = "Enable IAM User Permissions"
|
|
2688
|
+
Effect = "Allow"
|
|
2689
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
2690
|
+
Action = "kms:*"
|
|
2691
|
+
Resource = "*"
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
Sid = "Allow CloudWatch Logs"
|
|
2695
|
+
Effect = "Allow"
|
|
2696
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
2697
|
+
Action = [
|
|
2698
|
+
"kms:Encrypt",
|
|
2699
|
+
"kms:Decrypt",
|
|
2700
|
+
"kms:ReEncrypt*",
|
|
2701
|
+
"kms:GenerateDataKey*",
|
|
2702
|
+
"kms:DescribeKey"
|
|
2703
|
+
]
|
|
2704
|
+
Resource = "*"
|
|
2705
|
+
Condition = {
|
|
2706
|
+
ArnEquals = {
|
|
2707
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
]
|
|
2712
|
+
})
|
|
2713
|
+
|
|
2714
|
+
tags = var.tags
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
resource "aws_kms_alias" "access_logs" {
|
|
2718
|
+
name = "alias/test-api-\${random_string.suffix.result}-access-logs"
|
|
2719
|
+
target_key_id = aws_kms_key.access_logs.key_id
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
# Access logs for the API Gateway stage
|
|
2723
|
+
resource "aws_cloudwatch_log_group" "access_logs" {
|
|
2724
|
+
name = "/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
2725
|
+
retention_in_days = 365
|
|
2726
|
+
kms_key_id = aws_kms_key.access_logs.arn
|
|
2727
|
+
tags = var.tags
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
# API Gateway stage
|
|
2731
|
+
resource "aws_api_gateway_stage" "api_stage" {
|
|
2732
|
+
#checkov:skip=CKV_AWS_120:API Gateway caching not required for this use case
|
|
2733
|
+
#checkov:skip=CKV2_AWS_4:Access logging is enabled below; verbose execution logging is intentionally not enabled
|
|
2734
|
+
#checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
|
|
2735
|
+
#checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
|
|
2736
|
+
deployment_id = aws_api_gateway_deployment.api_deployment.id
|
|
2737
|
+
rest_api_id = module.rest_api.api_id
|
|
2738
|
+
stage_name = "prod"
|
|
2739
|
+
xray_tracing_enabled = true
|
|
2740
|
+
|
|
2741
|
+
access_log_settings {
|
|
2742
|
+
destination_arn = aws_cloudwatch_log_group.access_logs.arn
|
|
2743
|
+
format = jsonencode({
|
|
2744
|
+
requestId = "$context.requestId"
|
|
2745
|
+
ip = "$context.identity.sourceIp"
|
|
2746
|
+
caller = "$context.identity.caller"
|
|
2747
|
+
user = "$context.identity.user"
|
|
2748
|
+
requestTime = "$context.requestTime"
|
|
2749
|
+
httpMethod = "$context.httpMethod"
|
|
2750
|
+
resourcePath = "$context.resourcePath"
|
|
2751
|
+
status = "$context.status"
|
|
2752
|
+
protocol = "$context.protocol"
|
|
2753
|
+
responseLength = "$context.responseLength"
|
|
2754
|
+
})
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
tags = var.tags
|
|
2758
|
+
|
|
2759
|
+
# Ensure the account-level role is configured before the stage is created
|
|
2760
|
+
depends_on = [aws_api_gateway_deployment.api_deployment, module.account]
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
# Default throttling applied to all resource paths and methods
|
|
2764
|
+
resource "aws_api_gateway_method_settings" "throttling" {
|
|
2765
|
+
#checkov:skip=CKV_AWS_225:API Gateway caching not required for this use case
|
|
2766
|
+
rest_api_id = module.rest_api.api_id
|
|
2767
|
+
stage_name = aws_api_gateway_stage.api_stage.stage_name
|
|
2768
|
+
method_path = "*/*"
|
|
2769
|
+
|
|
2770
|
+
settings {
|
|
2771
|
+
throttling_rate_limit = 10000
|
|
2772
|
+
throttling_burst_limit = 5000
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
# API Gateway Resource Policy
|
|
2777
|
+
resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
2778
|
+
rest_api_id = module.rest_api.api_id
|
|
2779
|
+
|
|
2780
|
+
policy = jsonencode({
|
|
2781
|
+
Version = "2012-10-17"
|
|
2782
|
+
Statement = [
|
|
2783
|
+
{
|
|
2784
|
+
# Allow all callers to invoke the API in the resource policy, since auth is handled by Cognito
|
|
2785
|
+
Effect = "Allow"
|
|
2786
|
+
Principal = "*"
|
|
2787
|
+
Action = "execute-api:Invoke"
|
|
2788
|
+
Resource = "execute-api:/*"
|
|
2789
|
+
}
|
|
2790
|
+
]
|
|
2791
|
+
})
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
2795
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
2796
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
2797
|
+
action = "lambda:InvokeFunction"
|
|
2798
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
2799
|
+
qualifier = aws_lambda_alias.live.name
|
|
2800
|
+
principal = "apigateway.amazonaws.com"
|
|
2801
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
2802
|
+
|
|
2803
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
2807
|
+
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
2808
|
+
statement_id = "AllowStreamingFromAPIGateway"
|
|
2809
|
+
action = "lambda:InvokeFunctionWithResponseStream"
|
|
2810
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
2811
|
+
qualifier = aws_lambda_alias.live.name
|
|
2812
|
+
principal = "apigateway.amazonaws.com"
|
|
2813
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
2814
|
+
|
|
2815
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
# Add API url to runtime config
|
|
2819
|
+
module "add_url_to_runtime_config" {
|
|
2820
|
+
source = "../../../core/runtime-config/entry"
|
|
2821
|
+
|
|
2822
|
+
namespace = "connection"
|
|
2823
|
+
key = "apis"
|
|
2824
|
+
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
2825
|
+
|
|
2826
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
# Outputs
|
|
2830
|
+
|
|
2831
|
+
# API Gateway Outputs (from core module)
|
|
2832
|
+
output "api_id" {
|
|
2833
|
+
description = "ID of the REST API Gateway"
|
|
2834
|
+
value = module.rest_api.api_id
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
output "api_arn" {
|
|
2838
|
+
description = "ARN of the REST API Gateway"
|
|
2839
|
+
value = module.rest_api.api_arn
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
output "api_endpoint" {
|
|
2843
|
+
description = "Base URL of the REST API Gateway"
|
|
2844
|
+
value = module.rest_api.api_endpoint
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
output "api_execution_arn" {
|
|
2848
|
+
description = "Execution ARN of the REST API Gateway"
|
|
2849
|
+
value = module.rest_api.api_execution_arn
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
output "stage_invoke_url" {
|
|
2853
|
+
description = "Invoke URL of the API Gateway stage"
|
|
2854
|
+
value = aws_api_gateway_stage.api_stage.invoke_url
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
output "stage_arn" {
|
|
2858
|
+
description = "ARN of the API Gateway stage"
|
|
2859
|
+
value = aws_api_gateway_stage.api_stage.arn
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
output "stage_execution_arn" {
|
|
2863
|
+
description = "Execution ARN of the API Gateway stage"
|
|
2864
|
+
value = aws_api_gateway_stage.api_stage.execution_arn
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
output "deployment_id" {
|
|
2868
|
+
description = "ID of the API Gateway deployment"
|
|
2869
|
+
value = aws_api_gateway_deployment.api_deployment.id
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
output "stage_id" {
|
|
2873
|
+
description = "ID of the API Gateway stage"
|
|
2874
|
+
value = aws_api_gateway_stage.api_stage.id
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
# Lambda Function Outputs
|
|
2878
|
+
output "lambda_function_name" {
|
|
2879
|
+
description = "Name of the Lambda function"
|
|
2880
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
output "lambda_function_arn" {
|
|
2884
|
+
description = "ARN of the Lambda function"
|
|
2885
|
+
value = aws_lambda_function.api_lambda.arn
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
output "lambda_invoke_arn" {
|
|
2889
|
+
description = "Invoke ARN of the Lambda function"
|
|
2890
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
output "lambda_qualified_arn" {
|
|
2894
|
+
description = "Qualified ARN of the Lambda function"
|
|
2895
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
output "lambda_version" {
|
|
2899
|
+
description = "Version of the Lambda function"
|
|
2900
|
+
value = aws_lambda_function.api_lambda.version
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
output "lambda_source_code_hash" {
|
|
2904
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
2905
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
output "lambda_source_code_size" {
|
|
2909
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
2910
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
# IAM Role Outputs
|
|
2914
|
+
output "lambda_execution_role_arn" {
|
|
2915
|
+
description = "ARN of the Lambda execution role"
|
|
2916
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
output "lambda_execution_role_name" {
|
|
2920
|
+
description = "Name of the Lambda execution role"
|
|
2921
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
# Integration Outputs
|
|
2925
|
+
output "integration_id" {
|
|
2926
|
+
description = "ID of the Lambda integration"
|
|
2927
|
+
value = aws_api_gateway_integration.lambda_integration.id
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
output "proxy_resource_id" {
|
|
2931
|
+
description = "ID of the proxy resource"
|
|
2932
|
+
value = aws_api_gateway_resource.proxy_resource.id
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
output "proxy_method_id" {
|
|
2936
|
+
description = "ID of the proxy method"
|
|
2937
|
+
value = aws_api_gateway_method.proxy_method.id
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
# CloudWatch Log Groups
|
|
2941
|
+
output "lambda_log_group_name" {
|
|
2942
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
2943
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
output "lambda_log_group_arn" {
|
|
2947
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
2948
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
2949
|
+
}
|
|
2950
|
+
",
|
|
2951
|
+
}
|
|
2952
|
+
`;
|
|
2953
|
+
|
|
2954
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for REST API with IAM auth and snapshot them > terraform-rest-iam-files 1`] = `
|
|
2955
|
+
{
|
|
2956
|
+
"rest-api.tf": "# Core REST API Gateway module
|
|
2957
|
+
# This module creates the API Gateway REST API, deployment, stage, and logging resources
|
|
2958
|
+
|
|
2959
|
+
terraform {
|
|
2960
|
+
required_version = ">= 1.0"
|
|
2961
|
+
|
|
2962
|
+
required_providers {
|
|
2963
|
+
aws = {
|
|
2964
|
+
source = "hashicorp/aws"
|
|
2965
|
+
version = "6.52.0"
|
|
2966
|
+
}
|
|
2967
|
+
random = {
|
|
2968
|
+
source = "hashicorp/random"
|
|
2969
|
+
version = "3.9.0"
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
# Core REST API Gateway Variables
|
|
2975
|
+
|
|
2976
|
+
variable "api_name" {
|
|
2977
|
+
description = "Name of the REST API Gateway"
|
|
2978
|
+
type = string
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
variable "api_description" {
|
|
2982
|
+
description = "Description of the REST API Gateway"
|
|
2983
|
+
type = string
|
|
2984
|
+
default = "REST API Gateway"
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
variable "stage_name" {
|
|
2988
|
+
description = "Name of the API Gateway stage"
|
|
2989
|
+
type = string
|
|
2990
|
+
default = "prod"
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
variable "stage_auto_deploy" {
|
|
2994
|
+
description = "Whether to automatically deploy the API stage"
|
|
2995
|
+
type = bool
|
|
2996
|
+
default = true
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
variable "enable_waf" {
|
|
3000
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
|
|
3001
|
+
type = bool
|
|
3002
|
+
default = true
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
# CORS Configuration
|
|
3006
|
+
|
|
3007
|
+
variable "cors_allow_headers" {
|
|
3008
|
+
description = "List of allowed headers for CORS"
|
|
3009
|
+
type = list(string)
|
|
3010
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
variable "cors_allow_methods" {
|
|
3014
|
+
description = "List of allowed HTTP methods for CORS"
|
|
3015
|
+
type = list(string)
|
|
3016
|
+
default = ["*"]
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
variable "cors_allow_origins" {
|
|
3020
|
+
description = "List of allowed origins for CORS"
|
|
3021
|
+
type = list(string)
|
|
3022
|
+
default = ["*"]
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
# Tags
|
|
3026
|
+
|
|
3027
|
+
variable "tags" {
|
|
3028
|
+
description = "Tags to apply to all resources"
|
|
3029
|
+
type = map(string)
|
|
3030
|
+
default = {}
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
# Data sources
|
|
3034
|
+
data "aws_region" "current" {}
|
|
3035
|
+
data "aws_caller_identity" "current" {}
|
|
3036
|
+
|
|
3037
|
+
# Resources
|
|
3038
|
+
|
|
3039
|
+
# REST API Gateway
|
|
3040
|
+
resource "aws_api_gateway_rest_api" "rest_api" {
|
|
3041
|
+
name = var.api_name
|
|
3042
|
+
description = var.api_description
|
|
3043
|
+
|
|
3044
|
+
endpoint_configuration {
|
|
3045
|
+
types = ["REGIONAL"]
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
lifecycle {
|
|
3049
|
+
create_before_destroy = true
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
tags = var.tags
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
# Note: Deployment, stage and access logging are created in the consuming module
|
|
3056
|
+
# (e.g., foo-api.tf) after all methods and integrations are defined
|
|
3057
|
+
|
|
3058
|
+
# Gateway Response for CORS (4XX errors)
|
|
3059
|
+
resource "aws_api_gateway_gateway_response" "cors_4xx" {
|
|
3060
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
3061
|
+
response_type = "DEFAULT_4XX"
|
|
3062
|
+
|
|
3063
|
+
response_parameters = {
|
|
3064
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3065
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3066
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
# Gateway Response for CORS (5XX errors)
|
|
3071
|
+
resource "aws_api_gateway_gateway_response" "cors_5xx" {
|
|
3072
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
3073
|
+
response_type = "DEFAULT_5XX"
|
|
3074
|
+
|
|
3075
|
+
response_parameters = {
|
|
3076
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3077
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3078
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
resource "aws_wafv2_web_acl" "api_waf" {
|
|
3083
|
+
#checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
|
|
3084
|
+
count = var.enable_waf ? 1 : 0
|
|
3085
|
+
|
|
3086
|
+
name = "\${var.api_name}-waf"
|
|
3087
|
+
scope = "REGIONAL"
|
|
3088
|
+
|
|
3089
|
+
default_action {
|
|
3090
|
+
allow {}
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
rule {
|
|
3094
|
+
name = "CRSRule"
|
|
3095
|
+
priority = 0
|
|
3096
|
+
|
|
3097
|
+
override_action {
|
|
3098
|
+
none {}
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
statement {
|
|
3102
|
+
managed_rule_group_statement {
|
|
3103
|
+
name = "AWSManagedRulesCommonRuleSet"
|
|
3104
|
+
vendor_name = "AWS"
|
|
3105
|
+
|
|
3106
|
+
# Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
|
|
3107
|
+
rule_action_override {
|
|
3108
|
+
name = "SizeRestrictions_BODY"
|
|
3109
|
+
action_to_use {
|
|
3110
|
+
count {}
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
visibility_config {
|
|
3117
|
+
cloudwatch_metrics_enabled = true
|
|
3118
|
+
metric_name = "\${var.api_name}WebAcl-CRS"
|
|
3119
|
+
sampled_requests_enabled = true
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
rule {
|
|
3124
|
+
name = "KnownBadInputsRule"
|
|
3125
|
+
priority = 1
|
|
3126
|
+
|
|
3127
|
+
override_action {
|
|
3128
|
+
none {}
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
statement {
|
|
3132
|
+
managed_rule_group_statement {
|
|
3133
|
+
name = "AWSManagedRulesKnownBadInputsRuleSet"
|
|
3134
|
+
vendor_name = "AWS"
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
visibility_config {
|
|
3139
|
+
cloudwatch_metrics_enabled = true
|
|
3140
|
+
metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
|
|
3141
|
+
sampled_requests_enabled = true
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
visibility_config {
|
|
3146
|
+
cloudwatch_metrics_enabled = true
|
|
3147
|
+
metric_name = "\${var.api_name}WebAcl"
|
|
3148
|
+
sampled_requests_enabled = true
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
tags = var.tags
|
|
3152
|
+
|
|
3153
|
+
lifecycle {
|
|
3154
|
+
create_before_destroy = true
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
3159
|
+
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
3160
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3161
|
+
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
3162
|
+
count = var.enable_waf ? 1 : 0
|
|
3163
|
+
|
|
3164
|
+
name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
|
|
3165
|
+
retention_in_days = 30
|
|
3166
|
+
tags = var.tags
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
resource "random_id" "waf_log_suffix" {
|
|
3170
|
+
count = var.enable_waf ? 1 : 0
|
|
3171
|
+
byte_length = 4
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
3175
|
+
count = var.enable_waf ? 1 : 0
|
|
3176
|
+
|
|
3177
|
+
log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
|
|
3178
|
+
resource_arn = aws_wafv2_web_acl.api_waf[0].arn
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
|
|
3182
|
+
# Outputs
|
|
3183
|
+
|
|
3184
|
+
output "api_id" {
|
|
3185
|
+
description = "ID of the REST API Gateway"
|
|
3186
|
+
value = aws_api_gateway_rest_api.rest_api.id
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
output "api_arn" {
|
|
3190
|
+
description = "ARN of the REST API Gateway"
|
|
3191
|
+
value = aws_api_gateway_rest_api.rest_api.arn
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
output "api_endpoint" {
|
|
3195
|
+
description = "Base URL of the REST API Gateway"
|
|
3196
|
+
value = "https://\${aws_api_gateway_rest_api.rest_api.id}.execute-api.\${data.aws_region.current.id}.amazonaws.com"
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
output "api_execution_arn" {
|
|
3200
|
+
description = "Execution ARN of the REST API Gateway"
|
|
3201
|
+
value = aws_api_gateway_rest_api.rest_api.execution_arn
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
output "api_root_resource_id" {
|
|
3205
|
+
description = "Root resource ID of the REST API Gateway"
|
|
3206
|
+
value = aws_api_gateway_rest_api.rest_api.root_resource_id
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
output "waf_web_acl_arn" {
|
|
3210
|
+
description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
|
|
3211
|
+
value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
# Note: Stage, deployment and access log outputs are provided by the consuming module",
|
|
3215
|
+
"test-api.tf": "terraform {
|
|
3216
|
+
required_version = ">= 1.0"
|
|
3217
|
+
|
|
3218
|
+
required_providers {
|
|
3219
|
+
aws = {
|
|
3220
|
+
source = "hashicorp/aws"
|
|
3221
|
+
version = "6.52.0"
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
|
|
3227
|
+
variable "env" {
|
|
3228
|
+
description = "Environment variables for the Lambda function"
|
|
3229
|
+
type = map(string)
|
|
3230
|
+
default = {}
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
variable "additional_iam_policy_statements" {
|
|
3234
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
3235
|
+
type = list(object({
|
|
3236
|
+
Effect = string
|
|
3237
|
+
Action = list(string)
|
|
3238
|
+
Resource = list(string)
|
|
3239
|
+
}))
|
|
3240
|
+
default = []
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
variable "asset_bucket_name" {
|
|
3244
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
3245
|
+
type = string
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
# CORS Configuration (passed to core module)
|
|
3249
|
+
|
|
3250
|
+
variable "cors_allow_headers" {
|
|
3251
|
+
description = "List of allowed headers for CORS"
|
|
3252
|
+
type = list(string)
|
|
3253
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
variable "cors_allow_methods" {
|
|
3257
|
+
description = "List of allowed HTTP methods for CORS"
|
|
3258
|
+
type = list(string)
|
|
3259
|
+
default = ["*"]
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
variable "cors_allow_origins" {
|
|
3263
|
+
description = "List of allowed origins for CORS"
|
|
3264
|
+
type = list(string)
|
|
3265
|
+
default = ["*"]
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
# WAF Configuration
|
|
3269
|
+
variable "enable_waf" {
|
|
3270
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
|
|
3271
|
+
type = bool
|
|
3272
|
+
default = true
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
# Tags
|
|
3276
|
+
variable "tags" {
|
|
3277
|
+
description = "Tags to apply to all resources"
|
|
3278
|
+
type = map(string)
|
|
3279
|
+
default = {}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
# Get current AWS region and account ID
|
|
3283
|
+
data "aws_region" "current" {}
|
|
3284
|
+
data "aws_caller_identity" "current" {}
|
|
3285
|
+
|
|
3286
|
+
resource "random_string" "suffix" {
|
|
3287
|
+
length = 8
|
|
3288
|
+
special = false
|
|
3289
|
+
upper = false
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
# Resources
|
|
3293
|
+
|
|
3294
|
+
# Create Lambda ZIP file from the bundle directory
|
|
3295
|
+
data "archive_file" "lambda_zip" {
|
|
3296
|
+
type = "zip"
|
|
3297
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
3298
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
3302
|
+
bucket = var.asset_bucket_name
|
|
3303
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
3304
|
+
source = data.archive_file.lambda_zip.output_path
|
|
3305
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
3306
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
# Use the core REST API module
|
|
3310
|
+
# Account-level CloudWatch role required for REST API access logging
|
|
3311
|
+
module "account" {
|
|
3312
|
+
source = "../../../core/api/api-gateway-account"
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
module "rest_api" {
|
|
3316
|
+
source = "../../../core/api/rest-api"
|
|
3317
|
+
|
|
3318
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
3319
|
+
api_description = "TestApi REST API"
|
|
3320
|
+
stage_name = "prod"
|
|
3321
|
+
stage_auto_deploy = true
|
|
3322
|
+
|
|
3323
|
+
# WAF Configuration
|
|
3324
|
+
enable_waf = var.enable_waf
|
|
3325
|
+
|
|
3326
|
+
# CORS Configuration
|
|
3327
|
+
cors_allow_headers = var.cors_allow_headers
|
|
3328
|
+
cors_allow_methods = var.cors_allow_methods
|
|
3329
|
+
cors_allow_origins = var.cors_allow_origins
|
|
3330
|
+
|
|
3331
|
+
# Tags
|
|
3332
|
+
tags = var.tags
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
resource "aws_wafv2_web_acl_association" "api_waf_association" {
|
|
3336
|
+
count = var.enable_waf ? 1 : 0
|
|
3337
|
+
|
|
3338
|
+
resource_arn = aws_api_gateway_stage.api_stage.arn
|
|
3339
|
+
web_acl_arn = module.rest_api.waf_web_acl_arn
|
|
3340
|
+
|
|
3341
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
# Lambda function
|
|
3345
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
3346
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
3347
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3348
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
3349
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
3350
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
3351
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3352
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
3353
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3354
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
3355
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
3356
|
+
handler = "run.sh"
|
|
3357
|
+
runtime = "python3.14"
|
|
3358
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
3359
|
+
timeout = 30
|
|
3360
|
+
memory_size = 128
|
|
3361
|
+
publish = true
|
|
3362
|
+
|
|
3363
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
3364
|
+
|
|
3365
|
+
# Enable X-Ray tracing
|
|
3366
|
+
tracing_config {
|
|
3367
|
+
mode = "Active"
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
# Enable SnapStart for faster cold starts
|
|
3371
|
+
snap_start {
|
|
3372
|
+
apply_on = "PublishedVersions"
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
environment {
|
|
3376
|
+
variables = merge({
|
|
3377
|
+
PORT = "8000"
|
|
3378
|
+
AWS_LWA_INVOKE_MODE = "response_stream"
|
|
3379
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
3380
|
+
}, var.env)
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
tags = var.tags
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
# IAM role for Lambda execution
|
|
3387
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
3388
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
3389
|
+
|
|
3390
|
+
assume_role_policy = jsonencode({
|
|
3391
|
+
Version = "2012-10-17"
|
|
3392
|
+
Statement = [
|
|
3393
|
+
{
|
|
3394
|
+
Action = "sts:AssumeRole"
|
|
3395
|
+
Effect = "Allow"
|
|
3396
|
+
Principal = {
|
|
3397
|
+
Service = "lambda.amazonaws.com"
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
]
|
|
3401
|
+
})
|
|
3402
|
+
|
|
3403
|
+
tags = var.tags
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
# Attach basic execution policy to Lambda role
|
|
3407
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3408
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
3409
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
3413
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3414
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
3415
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
# Additional IAM policies for Lambda (if provided)
|
|
3419
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
3420
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
3421
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
3422
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
3423
|
+
|
|
3424
|
+
policy = jsonencode({
|
|
3425
|
+
Version = "2012-10-17"
|
|
3426
|
+
Statement = var.additional_iam_policy_statements
|
|
3427
|
+
})
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
# CloudWatch Log Group for Lambda
|
|
3431
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
3432
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3433
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
3434
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
3435
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
3436
|
+
tags = var.tags
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
3440
|
+
resource "aws_lambda_alias" "live" {
|
|
3441
|
+
name = "live"
|
|
3442
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
3443
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
3444
|
+
|
|
3445
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
|
|
3449
|
+
# Create proxy resource (captures all paths)
|
|
3450
|
+
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
3451
|
+
rest_api_id = module.rest_api.api_id
|
|
3452
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
3453
|
+
path_part = "{proxy+}"
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
# Lambda integration for REST API
|
|
3457
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3458
|
+
rest_api_id = module.rest_api.api_id
|
|
3459
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3460
|
+
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
3461
|
+
|
|
3462
|
+
integration_http_method = "POST"
|
|
3463
|
+
type = "AWS_PROXY"
|
|
3464
|
+
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
3465
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
3466
|
+
response_transfer_mode = "STREAM"
|
|
3467
|
+
|
|
3468
|
+
depends_on = [aws_lambda_alias.live]
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
# Method for proxy integration
|
|
3472
|
+
resource "aws_api_gateway_method" "proxy_method" {
|
|
3473
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
3474
|
+
rest_api_id = module.rest_api.api_id
|
|
3475
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3476
|
+
http_method = "ANY"
|
|
3477
|
+
|
|
3478
|
+
authorization = "AWS_IAM"
|
|
3479
|
+
|
|
3480
|
+
request_parameters = {
|
|
3481
|
+
"method.request.path.proxy" = true
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
depends_on = []
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
# OPTIONS method for CORS preflight
|
|
3488
|
+
resource "aws_api_gateway_method" "options_method" {
|
|
3489
|
+
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
3490
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
3491
|
+
rest_api_id = module.rest_api.api_id
|
|
3492
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3493
|
+
http_method = "OPTIONS"
|
|
3494
|
+
authorization = "NONE"
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
# CORS integration for OPTIONS method
|
|
3498
|
+
resource "aws_api_gateway_integration" "options_integration" {
|
|
3499
|
+
rest_api_id = module.rest_api.api_id
|
|
3500
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3501
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
3502
|
+
|
|
3503
|
+
type = "MOCK"
|
|
3504
|
+
request_templates = {
|
|
3505
|
+
"application/json" = "{\\"statusCode\\": 204}"
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
# OPTIONS method response
|
|
3510
|
+
resource "aws_api_gateway_method_response" "options_response" {
|
|
3511
|
+
rest_api_id = module.rest_api.api_id
|
|
3512
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3513
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
3514
|
+
status_code = "204"
|
|
3515
|
+
|
|
3516
|
+
response_parameters = {
|
|
3517
|
+
"method.response.header.Access-Control-Allow-Headers" = true
|
|
3518
|
+
"method.response.header.Access-Control-Allow-Methods" = true
|
|
3519
|
+
"method.response.header.Access-Control-Allow-Origin" = true
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
# OPTIONS integration response
|
|
3524
|
+
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
3525
|
+
rest_api_id = module.rest_api.api_id
|
|
3526
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3527
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
3528
|
+
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
3529
|
+
|
|
3530
|
+
response_parameters = {
|
|
3531
|
+
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3532
|
+
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3533
|
+
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
# API Gateway deployment
|
|
3538
|
+
resource "aws_api_gateway_deployment" "api_deployment" {
|
|
3539
|
+
rest_api_id = module.rest_api.api_id
|
|
3540
|
+
|
|
3541
|
+
triggers = {
|
|
3542
|
+
redeployment = sha1(jsonencode([
|
|
3543
|
+
aws_api_gateway_resource.proxy_resource.id,
|
|
3544
|
+
aws_api_gateway_method.proxy_method.id,
|
|
3545
|
+
aws_api_gateway_integration.lambda_integration.id,
|
|
3546
|
+
aws_api_gateway_method.options_method.id,
|
|
3547
|
+
aws_api_gateway_integration.options_integration.id,
|
|
3548
|
+
]))
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
lifecycle {
|
|
3552
|
+
create_before_destroy = true
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
depends_on = [
|
|
3556
|
+
aws_api_gateway_method.proxy_method,
|
|
3557
|
+
aws_api_gateway_integration.lambda_integration,
|
|
3558
|
+
aws_api_gateway_method.options_method,
|
|
3559
|
+
aws_api_gateway_integration.options_integration,
|
|
3560
|
+
aws_api_gateway_method_response.options_response,
|
|
3561
|
+
aws_api_gateway_integration_response.options_integration_response,
|
|
3562
|
+
]
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
# KMS key for encrypting access logs at rest
|
|
3566
|
+
resource "aws_kms_key" "access_logs" {
|
|
3567
|
+
description = "TestApi API access log encryption"
|
|
3568
|
+
deletion_window_in_days = 7
|
|
3569
|
+
enable_key_rotation = true
|
|
3570
|
+
|
|
3571
|
+
policy = jsonencode({
|
|
3572
|
+
Version = "2012-10-17"
|
|
3573
|
+
Statement = [
|
|
3574
|
+
{
|
|
3575
|
+
Sid = "Enable IAM User Permissions"
|
|
3576
|
+
Effect = "Allow"
|
|
3577
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
3578
|
+
Action = "kms:*"
|
|
3579
|
+
Resource = "*"
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
Sid = "Allow CloudWatch Logs"
|
|
3583
|
+
Effect = "Allow"
|
|
3584
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
3585
|
+
Action = [
|
|
3586
|
+
"kms:Encrypt",
|
|
3587
|
+
"kms:Decrypt",
|
|
3588
|
+
"kms:ReEncrypt*",
|
|
3589
|
+
"kms:GenerateDataKey*",
|
|
3590
|
+
"kms:DescribeKey"
|
|
3591
|
+
]
|
|
3592
|
+
Resource = "*"
|
|
3593
|
+
Condition = {
|
|
3594
|
+
ArnEquals = {
|
|
3595
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
]
|
|
3600
|
+
})
|
|
3601
|
+
|
|
3602
|
+
tags = var.tags
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
resource "aws_kms_alias" "access_logs" {
|
|
3606
|
+
name = "alias/test-api-\${random_string.suffix.result}-access-logs"
|
|
3607
|
+
target_key_id = aws_kms_key.access_logs.key_id
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
# Access logs for the API Gateway stage
|
|
3611
|
+
resource "aws_cloudwatch_log_group" "access_logs" {
|
|
3612
|
+
name = "/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
3613
|
+
retention_in_days = 365
|
|
3614
|
+
kms_key_id = aws_kms_key.access_logs.arn
|
|
3615
|
+
tags = var.tags
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
# API Gateway stage
|
|
3619
|
+
resource "aws_api_gateway_stage" "api_stage" {
|
|
3620
|
+
#checkov:skip=CKV_AWS_120:API Gateway caching not required for this use case
|
|
3621
|
+
#checkov:skip=CKV2_AWS_4:Access logging is enabled below; verbose execution logging is intentionally not enabled
|
|
3622
|
+
#checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
|
|
3623
|
+
#checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
|
|
3624
|
+
deployment_id = aws_api_gateway_deployment.api_deployment.id
|
|
3625
|
+
rest_api_id = module.rest_api.api_id
|
|
3626
|
+
stage_name = "prod"
|
|
3627
|
+
xray_tracing_enabled = true
|
|
3628
|
+
|
|
3629
|
+
access_log_settings {
|
|
3630
|
+
destination_arn = aws_cloudwatch_log_group.access_logs.arn
|
|
3631
|
+
format = jsonencode({
|
|
3632
|
+
requestId = "$context.requestId"
|
|
3633
|
+
ip = "$context.identity.sourceIp"
|
|
3634
|
+
caller = "$context.identity.caller"
|
|
3635
|
+
user = "$context.identity.user"
|
|
3636
|
+
requestTime = "$context.requestTime"
|
|
3637
|
+
httpMethod = "$context.httpMethod"
|
|
3638
|
+
resourcePath = "$context.resourcePath"
|
|
3639
|
+
status = "$context.status"
|
|
3640
|
+
protocol = "$context.protocol"
|
|
3641
|
+
responseLength = "$context.responseLength"
|
|
3642
|
+
})
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
tags = var.tags
|
|
3646
|
+
|
|
3647
|
+
# Ensure the account-level role is configured before the stage is created
|
|
3648
|
+
depends_on = [aws_api_gateway_deployment.api_deployment, module.account]
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
# Default throttling applied to all resource paths and methods
|
|
3652
|
+
resource "aws_api_gateway_method_settings" "throttling" {
|
|
3653
|
+
#checkov:skip=CKV_AWS_225:API Gateway caching not required for this use case
|
|
3654
|
+
rest_api_id = module.rest_api.api_id
|
|
3655
|
+
stage_name = aws_api_gateway_stage.api_stage.stage_name
|
|
3656
|
+
method_path = "*/*"
|
|
3657
|
+
|
|
3658
|
+
settings {
|
|
3659
|
+
throttling_rate_limit = 10000
|
|
3660
|
+
throttling_burst_limit = 5000
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
# API Gateway Resource Policy
|
|
3665
|
+
resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
3666
|
+
rest_api_id = module.rest_api.api_id
|
|
3667
|
+
|
|
3668
|
+
policy = jsonencode({
|
|
3669
|
+
Version = "2012-10-17"
|
|
3670
|
+
Statement = [
|
|
3671
|
+
{
|
|
3672
|
+
# Grant any AWS credentials from the account to call the API
|
|
3673
|
+
# Machine to machine fine-grained access can be defined here using more specific principals
|
|
3674
|
+
# (eg roles or users) and resources (eg which api paths may be invoked by which principal) if required
|
|
3675
|
+
Effect = "Allow"
|
|
3676
|
+
Principal = {
|
|
3677
|
+
AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
|
|
3678
|
+
}
|
|
3679
|
+
Action = "execute-api:Invoke"
|
|
3680
|
+
Resource = "execute-api:/*"
|
|
3681
|
+
},
|
|
3682
|
+
{
|
|
3683
|
+
# Open up OPTIONS to allow browsers to make unauthenticated preflight requests
|
|
3684
|
+
Effect = "Allow"
|
|
3685
|
+
Principal = "*"
|
|
3686
|
+
Action = "execute-api:Invoke"
|
|
3687
|
+
Resource = "execute-api:/*/OPTIONS/*"
|
|
3688
|
+
}
|
|
3689
|
+
]
|
|
3690
|
+
})
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
3694
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3695
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
3696
|
+
action = "lambda:InvokeFunction"
|
|
3697
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
3698
|
+
qualifier = aws_lambda_alias.live.name
|
|
3699
|
+
principal = "apigateway.amazonaws.com"
|
|
3700
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
3701
|
+
|
|
3702
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
3706
|
+
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
3707
|
+
statement_id = "AllowStreamingFromAPIGateway"
|
|
3708
|
+
action = "lambda:InvokeFunctionWithResponseStream"
|
|
3709
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
3710
|
+
qualifier = aws_lambda_alias.live.name
|
|
3711
|
+
principal = "apigateway.amazonaws.com"
|
|
3712
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
3713
|
+
|
|
3714
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
# Add API url to runtime config
|
|
3718
|
+
module "add_url_to_runtime_config" {
|
|
3719
|
+
source = "../../../core/runtime-config/entry"
|
|
3720
|
+
|
|
3721
|
+
namespace = "connection"
|
|
3722
|
+
key = "apis"
|
|
3723
|
+
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
3724
|
+
|
|
3725
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
# Outputs
|
|
3729
|
+
|
|
3730
|
+
# API Gateway Outputs (from core module)
|
|
3731
|
+
output "api_id" {
|
|
3732
|
+
description = "ID of the REST API Gateway"
|
|
3733
|
+
value = module.rest_api.api_id
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3736
|
+
output "api_arn" {
|
|
3737
|
+
description = "ARN of the REST API Gateway"
|
|
3738
|
+
value = module.rest_api.api_arn
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
output "api_endpoint" {
|
|
3742
|
+
description = "Base URL of the REST API Gateway"
|
|
3743
|
+
value = module.rest_api.api_endpoint
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
output "api_execution_arn" {
|
|
3747
|
+
description = "Execution ARN of the REST API Gateway"
|
|
3748
|
+
value = module.rest_api.api_execution_arn
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
output "stage_invoke_url" {
|
|
3752
|
+
description = "Invoke URL of the API Gateway stage"
|
|
3753
|
+
value = aws_api_gateway_stage.api_stage.invoke_url
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
output "stage_arn" {
|
|
3757
|
+
description = "ARN of the API Gateway stage"
|
|
3758
|
+
value = aws_api_gateway_stage.api_stage.arn
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
output "stage_execution_arn" {
|
|
3762
|
+
description = "Execution ARN of the API Gateway stage"
|
|
3763
|
+
value = aws_api_gateway_stage.api_stage.execution_arn
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
output "deployment_id" {
|
|
3767
|
+
description = "ID of the API Gateway deployment"
|
|
3768
|
+
value = aws_api_gateway_deployment.api_deployment.id
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
output "stage_id" {
|
|
3772
|
+
description = "ID of the API Gateway stage"
|
|
3773
|
+
value = aws_api_gateway_stage.api_stage.id
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
# Lambda Function Outputs
|
|
3777
|
+
output "lambda_function_name" {
|
|
3778
|
+
description = "Name of the Lambda function"
|
|
3779
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
output "lambda_function_arn" {
|
|
3783
|
+
description = "ARN of the Lambda function"
|
|
3784
|
+
value = aws_lambda_function.api_lambda.arn
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
output "lambda_invoke_arn" {
|
|
3788
|
+
description = "Invoke ARN of the Lambda function"
|
|
3789
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
output "lambda_qualified_arn" {
|
|
3793
|
+
description = "Qualified ARN of the Lambda function"
|
|
3794
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
output "lambda_version" {
|
|
3798
|
+
description = "Version of the Lambda function"
|
|
3799
|
+
value = aws_lambda_function.api_lambda.version
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
output "lambda_source_code_hash" {
|
|
3803
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
3804
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
output "lambda_source_code_size" {
|
|
3808
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
3809
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
# IAM Role Outputs
|
|
3813
|
+
output "lambda_execution_role_arn" {
|
|
3814
|
+
description = "ARN of the Lambda execution role"
|
|
3815
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
output "lambda_execution_role_name" {
|
|
3819
|
+
description = "Name of the Lambda execution role"
|
|
3820
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
# Integration Outputs
|
|
3824
|
+
output "integration_id" {
|
|
3825
|
+
description = "ID of the Lambda integration"
|
|
3826
|
+
value = aws_api_gateway_integration.lambda_integration.id
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
output "proxy_resource_id" {
|
|
3830
|
+
description = "ID of the proxy resource"
|
|
3831
|
+
value = aws_api_gateway_resource.proxy_resource.id
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
output "proxy_method_id" {
|
|
3835
|
+
description = "ID of the proxy method"
|
|
3836
|
+
value = aws_api_gateway_method.proxy_method.id
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
# CloudWatch Log Groups
|
|
3840
|
+
output "lambda_log_group_name" {
|
|
3841
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
3842
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
output "lambda_log_group_arn" {
|
|
3846
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
3847
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
3848
|
+
}
|
|
3849
|
+
",
|
|
3850
|
+
}
|
|
3851
|
+
`;
|
|
3852
|
+
|
|
3853
|
+
exports[`fastapi project generator > terraform iac > should generate terraform files for REST API with None auth and snapshot them > terraform-rest-none-files 1`] = `
|
|
3854
|
+
{
|
|
3855
|
+
"rest-api.tf": "# Core REST API Gateway module
|
|
3856
|
+
# This module creates the API Gateway REST API, deployment, stage, and logging resources
|
|
3857
|
+
|
|
3858
|
+
terraform {
|
|
3859
|
+
required_version = ">= 1.0"
|
|
3860
|
+
|
|
3861
|
+
required_providers {
|
|
3862
|
+
aws = {
|
|
3863
|
+
source = "hashicorp/aws"
|
|
3864
|
+
version = "6.52.0"
|
|
3865
|
+
}
|
|
3866
|
+
random = {
|
|
3867
|
+
source = "hashicorp/random"
|
|
3868
|
+
version = "3.9.0"
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
# Core REST API Gateway Variables
|
|
3874
|
+
|
|
3875
|
+
variable "api_name" {
|
|
3876
|
+
description = "Name of the REST API Gateway"
|
|
3877
|
+
type = string
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
variable "api_description" {
|
|
3881
|
+
description = "Description of the REST API Gateway"
|
|
3882
|
+
type = string
|
|
3883
|
+
default = "REST API Gateway"
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
variable "stage_name" {
|
|
3887
|
+
description = "Name of the API Gateway stage"
|
|
3888
|
+
type = string
|
|
3889
|
+
default = "prod"
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
variable "stage_auto_deploy" {
|
|
3893
|
+
description = "Whether to automatically deploy the API stage"
|
|
3894
|
+
type = bool
|
|
3895
|
+
default = true
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
variable "enable_waf" {
|
|
3899
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
|
|
3900
|
+
type = bool
|
|
3901
|
+
default = true
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
# CORS Configuration
|
|
3905
|
+
|
|
3906
|
+
variable "cors_allow_headers" {
|
|
3907
|
+
description = "List of allowed headers for CORS"
|
|
3908
|
+
type = list(string)
|
|
3909
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
variable "cors_allow_methods" {
|
|
3913
|
+
description = "List of allowed HTTP methods for CORS"
|
|
3914
|
+
type = list(string)
|
|
3915
|
+
default = ["*"]
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
variable "cors_allow_origins" {
|
|
3919
|
+
description = "List of allowed origins for CORS"
|
|
3920
|
+
type = list(string)
|
|
3921
|
+
default = ["*"]
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
# Tags
|
|
3925
|
+
|
|
3926
|
+
variable "tags" {
|
|
3927
|
+
description = "Tags to apply to all resources"
|
|
3928
|
+
type = map(string)
|
|
3929
|
+
default = {}
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
# Data sources
|
|
3933
|
+
data "aws_region" "current" {}
|
|
3934
|
+
data "aws_caller_identity" "current" {}
|
|
3935
|
+
|
|
3936
|
+
# Resources
|
|
3937
|
+
|
|
3938
|
+
# REST API Gateway
|
|
3939
|
+
resource "aws_api_gateway_rest_api" "rest_api" {
|
|
3940
|
+
name = var.api_name
|
|
3941
|
+
description = var.api_description
|
|
3942
|
+
|
|
3943
|
+
endpoint_configuration {
|
|
3944
|
+
types = ["REGIONAL"]
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
lifecycle {
|
|
3948
|
+
create_before_destroy = true
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
tags = var.tags
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
# Note: Deployment, stage and access logging are created in the consuming module
|
|
3955
|
+
# (e.g., foo-api.tf) after all methods and integrations are defined
|
|
3956
|
+
|
|
3957
|
+
# Gateway Response for CORS (4XX errors)
|
|
3958
|
+
resource "aws_api_gateway_gateway_response" "cors_4xx" {
|
|
3959
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
3960
|
+
response_type = "DEFAULT_4XX"
|
|
3961
|
+
|
|
3962
|
+
response_parameters = {
|
|
3963
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3964
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3965
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
# Gateway Response for CORS (5XX errors)
|
|
3970
|
+
resource "aws_api_gateway_gateway_response" "cors_5xx" {
|
|
3971
|
+
rest_api_id = aws_api_gateway_rest_api.rest_api.id
|
|
3972
|
+
response_type = "DEFAULT_5XX"
|
|
3973
|
+
|
|
3974
|
+
response_parameters = {
|
|
3975
|
+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3976
|
+
"gatewayresponse.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3977
|
+
"gatewayresponse.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
resource "aws_wafv2_web_acl" "api_waf" {
|
|
3982
|
+
#checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
|
|
3983
|
+
count = var.enable_waf ? 1 : 0
|
|
3984
|
+
|
|
3985
|
+
name = "\${var.api_name}-waf"
|
|
3986
|
+
scope = "REGIONAL"
|
|
3987
|
+
|
|
3988
|
+
default_action {
|
|
3989
|
+
allow {}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
rule {
|
|
3993
|
+
name = "CRSRule"
|
|
3994
|
+
priority = 0
|
|
3995
|
+
|
|
3996
|
+
override_action {
|
|
3997
|
+
none {}
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
statement {
|
|
4001
|
+
managed_rule_group_statement {
|
|
4002
|
+
name = "AWSManagedRulesCommonRuleSet"
|
|
4003
|
+
vendor_name = "AWS"
|
|
4004
|
+
|
|
4005
|
+
# Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
|
|
4006
|
+
rule_action_override {
|
|
4007
|
+
name = "SizeRestrictions_BODY"
|
|
4008
|
+
action_to_use {
|
|
4009
|
+
count {}
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
visibility_config {
|
|
4016
|
+
cloudwatch_metrics_enabled = true
|
|
4017
|
+
metric_name = "\${var.api_name}WebAcl-CRS"
|
|
4018
|
+
sampled_requests_enabled = true
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
rule {
|
|
4023
|
+
name = "KnownBadInputsRule"
|
|
4024
|
+
priority = 1
|
|
4025
|
+
|
|
4026
|
+
override_action {
|
|
4027
|
+
none {}
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
statement {
|
|
4031
|
+
managed_rule_group_statement {
|
|
4032
|
+
name = "AWSManagedRulesKnownBadInputsRuleSet"
|
|
4033
|
+
vendor_name = "AWS"
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
visibility_config {
|
|
4038
|
+
cloudwatch_metrics_enabled = true
|
|
4039
|
+
metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
|
|
4040
|
+
sampled_requests_enabled = true
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
visibility_config {
|
|
4045
|
+
cloudwatch_metrics_enabled = true
|
|
4046
|
+
metric_name = "\${var.api_name}WebAcl"
|
|
4047
|
+
sampled_requests_enabled = true
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
tags = var.tags
|
|
4051
|
+
|
|
4052
|
+
lifecycle {
|
|
4053
|
+
create_before_destroy = true
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
4058
|
+
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
4059
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4060
|
+
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
4061
|
+
count = var.enable_waf ? 1 : 0
|
|
4062
|
+
|
|
4063
|
+
name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
|
|
4064
|
+
retention_in_days = 30
|
|
4065
|
+
tags = var.tags
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
resource "random_id" "waf_log_suffix" {
|
|
4069
|
+
count = var.enable_waf ? 1 : 0
|
|
4070
|
+
byte_length = 4
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
4074
|
+
count = var.enable_waf ? 1 : 0
|
|
4075
|
+
|
|
4076
|
+
log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
|
|
4077
|
+
resource_arn = aws_wafv2_web_acl.api_waf[0].arn
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
|
|
4081
|
+
# Outputs
|
|
4082
|
+
|
|
4083
|
+
output "api_id" {
|
|
4084
|
+
description = "ID of the REST API Gateway"
|
|
4085
|
+
value = aws_api_gateway_rest_api.rest_api.id
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
output "api_arn" {
|
|
4089
|
+
description = "ARN of the REST API Gateway"
|
|
4090
|
+
value = aws_api_gateway_rest_api.rest_api.arn
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
output "api_endpoint" {
|
|
4094
|
+
description = "Base URL of the REST API Gateway"
|
|
4095
|
+
value = "https://\${aws_api_gateway_rest_api.rest_api.id}.execute-api.\${data.aws_region.current.id}.amazonaws.com"
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
output "api_execution_arn" {
|
|
4099
|
+
description = "Execution ARN of the REST API Gateway"
|
|
4100
|
+
value = aws_api_gateway_rest_api.rest_api.execution_arn
|
|
4101
|
+
}
|
|
4102
|
+
|
|
4103
|
+
output "api_root_resource_id" {
|
|
4104
|
+
description = "Root resource ID of the REST API Gateway"
|
|
4105
|
+
value = aws_api_gateway_rest_api.rest_api.root_resource_id
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
output "waf_web_acl_arn" {
|
|
4109
|
+
description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
|
|
4110
|
+
value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
# Note: Stage, deployment and access log outputs are provided by the consuming module",
|
|
4114
|
+
"test-api.tf": "terraform {
|
|
4115
|
+
required_version = ">= 1.0"
|
|
4116
|
+
|
|
4117
|
+
required_providers {
|
|
4118
|
+
aws = {
|
|
4119
|
+
source = "hashicorp/aws"
|
|
4120
|
+
version = "6.52.0"
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
|
|
4126
|
+
variable "env" {
|
|
4127
|
+
description = "Environment variables for the Lambda function"
|
|
4128
|
+
type = map(string)
|
|
4129
|
+
default = {}
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
variable "additional_iam_policy_statements" {
|
|
4133
|
+
description = "Additional IAM policy statements for the Lambda function"
|
|
4134
|
+
type = list(object({
|
|
4135
|
+
Effect = string
|
|
4136
|
+
Action = list(string)
|
|
4137
|
+
Resource = list(string)
|
|
4138
|
+
}))
|
|
4139
|
+
default = []
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
variable "asset_bucket_name" {
|
|
4143
|
+
description = "Name of the shared asset S3 bucket used to stage the Lambda deployment zip. Instantiate the \`core/asset-bucket\` module once per deployment and pass its \`bucket_name\` output here."
|
|
4144
|
+
type = string
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
# CORS Configuration (passed to core module)
|
|
4148
|
+
|
|
4149
|
+
variable "cors_allow_headers" {
|
|
4150
|
+
description = "List of allowed headers for CORS"
|
|
4151
|
+
type = list(string)
|
|
4152
|
+
default = ["authorization", "content-type", "x-amz-content-sha256", "x-amz-date", "x-amz-security-token"]
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
variable "cors_allow_methods" {
|
|
4156
|
+
description = "List of allowed HTTP methods for CORS"
|
|
4157
|
+
type = list(string)
|
|
4158
|
+
default = ["*"]
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
variable "cors_allow_origins" {
|
|
4162
|
+
description = "List of allowed origins for CORS"
|
|
4163
|
+
type = list(string)
|
|
4164
|
+
default = ["*"]
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
# WAF Configuration
|
|
4168
|
+
variable "enable_waf" {
|
|
4169
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
|
|
4170
|
+
type = bool
|
|
4171
|
+
default = true
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
# Tags
|
|
4175
|
+
variable "tags" {
|
|
4176
|
+
description = "Tags to apply to all resources"
|
|
4177
|
+
type = map(string)
|
|
4178
|
+
default = {}
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
# Get current AWS region and account ID
|
|
4182
|
+
data "aws_region" "current" {}
|
|
4183
|
+
data "aws_caller_identity" "current" {}
|
|
4184
|
+
|
|
4185
|
+
resource "random_string" "suffix" {
|
|
4186
|
+
length = 8
|
|
4187
|
+
special = false
|
|
4188
|
+
upper = false
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
# Resources
|
|
4192
|
+
|
|
4193
|
+
# Create Lambda ZIP file from the bundle directory
|
|
4194
|
+
data "archive_file" "lambda_zip" {
|
|
4195
|
+
type = "zip"
|
|
4196
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
4197
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
4201
|
+
bucket = var.asset_bucket_name
|
|
4202
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
4203
|
+
source = data.archive_file.lambda_zip.output_path
|
|
4204
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
4205
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
# Use the core REST API module
|
|
4209
|
+
# Account-level CloudWatch role required for REST API access logging
|
|
4210
|
+
module "account" {
|
|
4211
|
+
source = "../../../core/api/api-gateway-account"
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
module "rest_api" {
|
|
4215
|
+
source = "../../../core/api/rest-api"
|
|
4216
|
+
|
|
4217
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
4218
|
+
api_description = "TestApi REST API"
|
|
4219
|
+
stage_name = "prod"
|
|
4220
|
+
stage_auto_deploy = true
|
|
4221
|
+
|
|
4222
|
+
# WAF Configuration
|
|
4223
|
+
enable_waf = var.enable_waf
|
|
4224
|
+
|
|
4225
|
+
# CORS Configuration
|
|
4226
|
+
cors_allow_headers = var.cors_allow_headers
|
|
4227
|
+
cors_allow_methods = var.cors_allow_methods
|
|
4228
|
+
cors_allow_origins = var.cors_allow_origins
|
|
4229
|
+
|
|
4230
|
+
# Tags
|
|
4231
|
+
tags = var.tags
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
resource "aws_wafv2_web_acl_association" "api_waf_association" {
|
|
4235
|
+
count = var.enable_waf ? 1 : 0
|
|
4236
|
+
|
|
4237
|
+
resource_arn = aws_api_gateway_stage.api_stage.arn
|
|
4238
|
+
web_acl_arn = module.rest_api.waf_web_acl_arn
|
|
4239
|
+
|
|
4240
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
# Lambda function
|
|
4244
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
4245
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
4246
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4247
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
4248
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
4249
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
4250
|
+
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4251
|
+
s3_key = aws_s3_object.lambda_zip.key
|
|
4252
|
+
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4253
|
+
function_name = "TestApiHandler-\${random_string.suffix.result}"
|
|
4254
|
+
role = aws_iam_role.lambda_execution_role.arn
|
|
4255
|
+
handler = "run.sh"
|
|
4256
|
+
runtime = "python3.14"
|
|
4257
|
+
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
4258
|
+
timeout = 30
|
|
4259
|
+
memory_size = 128
|
|
4260
|
+
publish = true
|
|
4261
|
+
|
|
4262
|
+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
4263
|
+
|
|
4264
|
+
# Enable X-Ray tracing
|
|
4265
|
+
tracing_config {
|
|
4266
|
+
mode = "Active"
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
# Enable SnapStart for faster cold starts
|
|
4270
|
+
snap_start {
|
|
4271
|
+
apply_on = "PublishedVersions"
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
environment {
|
|
4275
|
+
variables = merge({
|
|
4276
|
+
PORT = "8000"
|
|
4277
|
+
AWS_LWA_INVOKE_MODE = "response_stream"
|
|
4278
|
+
AWS_LAMBDA_EXEC_WRAPPER = "/opt/bootstrap"
|
|
4279
|
+
}, var.env)
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
tags = var.tags
|
|
4283
|
+
}
|
|
4284
|
+
|
|
4285
|
+
# IAM role for Lambda execution
|
|
4286
|
+
resource "aws_iam_role" "lambda_execution_role" {
|
|
4287
|
+
name = "TestApiHandler-execution-role-\${random_string.suffix.result}"
|
|
4288
|
+
|
|
4289
|
+
assume_role_policy = jsonencode({
|
|
4290
|
+
Version = "2012-10-17"
|
|
4291
|
+
Statement = [
|
|
4292
|
+
{
|
|
4293
|
+
Action = "sts:AssumeRole"
|
|
4294
|
+
Effect = "Allow"
|
|
4295
|
+
Principal = {
|
|
4296
|
+
Service = "lambda.amazonaws.com"
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
]
|
|
4300
|
+
})
|
|
4301
|
+
|
|
4302
|
+
tags = var.tags
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
# Attach basic execution policy to Lambda role
|
|
4306
|
+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4307
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4308
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
# Attach X-Ray tracing policy to Lambda role
|
|
4312
|
+
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4313
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4314
|
+
role = aws_iam_role.lambda_execution_role.name
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
# Additional IAM policies for Lambda (if provided)
|
|
4318
|
+
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4319
|
+
count = length(var.additional_iam_policy_statements) > 0 ? 1 : 0
|
|
4320
|
+
name = "TestApiHandler-additional-policies-\${random_string.suffix.result}"
|
|
4321
|
+
role = aws_iam_role.lambda_execution_role.id
|
|
4322
|
+
|
|
4323
|
+
policy = jsonencode({
|
|
4324
|
+
Version = "2012-10-17"
|
|
4325
|
+
Statement = var.additional_iam_policy_statements
|
|
4326
|
+
})
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
# CloudWatch Log Group for Lambda
|
|
4330
|
+
resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
4331
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4332
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4333
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4334
|
+
name = "/aws/lambda/TestApiHandler-\${random_string.suffix.result}"
|
|
4335
|
+
tags = var.tags
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
# Lambda alias pointing to the latest published version for SnapStart
|
|
4339
|
+
resource "aws_lambda_alias" "live" {
|
|
4340
|
+
name = "live"
|
|
4341
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
4342
|
+
function_version = aws_lambda_function.api_lambda.version
|
|
4343
|
+
|
|
4344
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
# Custom Lambda Authorizer
|
|
4348
|
+
resource "aws_lambda_function" "authorizer_lambda" {
|
|
4349
|
+
#checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
|
|
4350
|
+
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for authorizer
|
|
4351
|
+
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
4352
|
+
#checkov:skip=CKV_AWS_115:Concurrent execution limit not required for this use case
|
|
4353
|
+
#checkov:skip=CKV_AWS_173:Lambda environment variables encrypted by managed key
|
|
4354
|
+
s3_bucket = aws_s3_object.authorizer_zip.bucket
|
|
4355
|
+
s3_key = aws_s3_object.authorizer_zip.key
|
|
4356
|
+
s3_object_version = aws_s3_object.authorizer_zip.version_id
|
|
4357
|
+
function_name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
4358
|
+
role = aws_iam_role.authorizer_execution_role.arn
|
|
4359
|
+
handler = "proj_test_api.authorizer.handler"
|
|
4360
|
+
runtime = "python3.14"
|
|
4361
|
+
timeout = 10
|
|
4362
|
+
memory_size = 128
|
|
4363
|
+
|
|
4364
|
+
source_code_hash = data.archive_file.authorizer_zip.output_base64sha256
|
|
4365
|
+
|
|
4366
|
+
tracing_config {
|
|
4367
|
+
mode = "Active"
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
tags = var.tags
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
resource "aws_iam_role" "authorizer_execution_role" {
|
|
4374
|
+
name = "TestApiAuthorizer-role-\${random_string.suffix.result}"
|
|
4375
|
+
|
|
4376
|
+
assume_role_policy = jsonencode({
|
|
4377
|
+
Version = "2012-10-17"
|
|
4378
|
+
Statement = [
|
|
4379
|
+
{
|
|
4380
|
+
Action = "sts:AssumeRole"
|
|
4381
|
+
Effect = "Allow"
|
|
4382
|
+
Principal = {
|
|
4383
|
+
Service = "lambda.amazonaws.com"
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
]
|
|
4387
|
+
})
|
|
4388
|
+
|
|
4389
|
+
tags = var.tags
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
resource "aws_iam_role_policy_attachment" "authorizer_basic_execution" {
|
|
4393
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4394
|
+
role = aws_iam_role.authorizer_execution_role.name
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
resource "aws_iam_role_policy_attachment" "authorizer_xray_execution" {
|
|
4398
|
+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4399
|
+
role = aws_iam_role.authorizer_execution_role.name
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
resource "aws_cloudwatch_log_group" "authorizer_logs" {
|
|
4403
|
+
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4404
|
+
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4405
|
+
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4406
|
+
name = "/aws/lambda/TestApiAuthorizer-\${random_string.suffix.result}"
|
|
4407
|
+
tags = var.tags
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
data "archive_file" "authorizer_zip" {
|
|
4411
|
+
type = "zip"
|
|
4412
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
4413
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/authorizer.zip"
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
resource "aws_s3_object" "authorizer_zip" {
|
|
4417
|
+
bucket = var.asset_bucket_name
|
|
4418
|
+
key = "apis/test-api/authorizer-\${data.archive_file.authorizer_zip.output_sha256}.zip"
|
|
4419
|
+
source = data.archive_file.authorizer_zip.output_path
|
|
4420
|
+
source_hash = data.archive_file.authorizer_zip.output_base64sha256
|
|
4421
|
+
etag = data.archive_file.authorizer_zip.output_md5
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4424
|
+
resource "aws_api_gateway_authorizer" "custom_authorizer" {
|
|
4425
|
+
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
4426
|
+
rest_api_id = module.rest_api.api_id
|
|
4427
|
+
type = "TOKEN"
|
|
4428
|
+
authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
|
|
4429
|
+
identity_source = "method.request.header.Authorization"
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
resource "aws_lambda_permission" "authorizer_invoke" {
|
|
4433
|
+
statement_id = "AllowAPIGatewayInvokeAuthorizer"
|
|
4434
|
+
action = "lambda:InvokeFunction"
|
|
4435
|
+
function_name = aws_lambda_function.authorizer_lambda.function_name
|
|
4436
|
+
principal = "apigateway.amazonaws.com"
|
|
4437
|
+
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
# Create proxy resource (captures all paths)
|
|
4441
|
+
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
4442
|
+
rest_api_id = module.rest_api.api_id
|
|
4443
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
4444
|
+
path_part = "{proxy+}"
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
# Lambda integration for REST API
|
|
4448
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
4449
|
+
rest_api_id = module.rest_api.api_id
|
|
4450
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4451
|
+
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
4452
|
+
|
|
4453
|
+
integration_http_method = "POST"
|
|
4454
|
+
type = "AWS_PROXY"
|
|
4455
|
+
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
4456
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
4457
|
+
response_transfer_mode = "STREAM"
|
|
4458
|
+
|
|
4459
|
+
depends_on = [aws_lambda_alias.live]
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
# Method for proxy integration
|
|
4463
|
+
resource "aws_api_gateway_method" "proxy_method" {
|
|
4464
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
4465
|
+
rest_api_id = module.rest_api.api_id
|
|
4466
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4467
|
+
http_method = "ANY"
|
|
4468
|
+
|
|
4469
|
+
authorization = "CUSTOM"
|
|
4470
|
+
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
4471
|
+
|
|
4472
|
+
request_parameters = {
|
|
4473
|
+
"method.request.path.proxy" = true
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
# OPTIONS method for CORS preflight
|
|
4480
|
+
resource "aws_api_gateway_method" "options_method" {
|
|
4481
|
+
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
4482
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
4483
|
+
rest_api_id = module.rest_api.api_id
|
|
4484
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4485
|
+
http_method = "OPTIONS"
|
|
4486
|
+
authorization = "NONE"
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
# CORS integration for OPTIONS method
|
|
4490
|
+
resource "aws_api_gateway_integration" "options_integration" {
|
|
4491
|
+
rest_api_id = module.rest_api.api_id
|
|
4492
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4493
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
4494
|
+
|
|
4495
|
+
type = "MOCK"
|
|
4496
|
+
request_templates = {
|
|
4497
|
+
"application/json" = "{\\"statusCode\\": 204}"
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
# OPTIONS method response
|
|
4502
|
+
resource "aws_api_gateway_method_response" "options_response" {
|
|
4503
|
+
rest_api_id = module.rest_api.api_id
|
|
4504
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4505
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
4506
|
+
status_code = "204"
|
|
4507
|
+
|
|
4508
|
+
response_parameters = {
|
|
4509
|
+
"method.response.header.Access-Control-Allow-Headers" = true
|
|
4510
|
+
"method.response.header.Access-Control-Allow-Methods" = true
|
|
4511
|
+
"method.response.header.Access-Control-Allow-Origin" = true
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
# OPTIONS integration response
|
|
4516
|
+
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
4517
|
+
rest_api_id = module.rest_api.api_id
|
|
4518
|
+
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4519
|
+
http_method = aws_api_gateway_method.options_method.http_method
|
|
4520
|
+
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
4521
|
+
|
|
4522
|
+
response_parameters = {
|
|
4523
|
+
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
4524
|
+
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
4525
|
+
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4529
|
+
# API Gateway deployment
|
|
4530
|
+
resource "aws_api_gateway_deployment" "api_deployment" {
|
|
4531
|
+
rest_api_id = module.rest_api.api_id
|
|
4532
|
+
|
|
4533
|
+
triggers = {
|
|
4534
|
+
redeployment = sha1(jsonencode([
|
|
4535
|
+
aws_api_gateway_resource.proxy_resource.id,
|
|
4536
|
+
aws_api_gateway_method.proxy_method.id,
|
|
4537
|
+
aws_api_gateway_integration.lambda_integration.id,
|
|
4538
|
+
aws_api_gateway_method.options_method.id,
|
|
4539
|
+
aws_api_gateway_integration.options_integration.id,
|
|
4540
|
+
]))
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
lifecycle {
|
|
4544
|
+
create_before_destroy = true
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
depends_on = [
|
|
4548
|
+
aws_api_gateway_method.proxy_method,
|
|
4549
|
+
aws_api_gateway_integration.lambda_integration,
|
|
4550
|
+
aws_api_gateway_method.options_method,
|
|
4551
|
+
aws_api_gateway_integration.options_integration,
|
|
4552
|
+
aws_api_gateway_method_response.options_response,
|
|
4553
|
+
aws_api_gateway_integration_response.options_integration_response,
|
|
4554
|
+
aws_api_gateway_authorizer.custom_authorizer,
|
|
4555
|
+
]
|
|
4556
|
+
}
|
|
4557
|
+
|
|
4558
|
+
# KMS key for encrypting access logs at rest
|
|
4559
|
+
resource "aws_kms_key" "access_logs" {
|
|
4560
|
+
description = "TestApi API access log encryption"
|
|
4561
|
+
deletion_window_in_days = 7
|
|
4562
|
+
enable_key_rotation = true
|
|
4563
|
+
|
|
4564
|
+
policy = jsonencode({
|
|
4565
|
+
Version = "2012-10-17"
|
|
4566
|
+
Statement = [
|
|
4567
|
+
{
|
|
4568
|
+
Sid = "Enable IAM User Permissions"
|
|
4569
|
+
Effect = "Allow"
|
|
4570
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
4571
|
+
Action = "kms:*"
|
|
4572
|
+
Resource = "*"
|
|
4573
|
+
},
|
|
4574
|
+
{
|
|
4575
|
+
Sid = "Allow CloudWatch Logs"
|
|
4576
|
+
Effect = "Allow"
|
|
4577
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
4578
|
+
Action = [
|
|
4579
|
+
"kms:Encrypt",
|
|
4580
|
+
"kms:Decrypt",
|
|
4581
|
+
"kms:ReEncrypt*",
|
|
4582
|
+
"kms:GenerateDataKey*",
|
|
4583
|
+
"kms:DescribeKey"
|
|
4584
|
+
]
|
|
4585
|
+
Resource = "*"
|
|
4586
|
+
Condition = {
|
|
4587
|
+
ArnEquals = {
|
|
4588
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
]
|
|
4593
|
+
})
|
|
4594
|
+
|
|
4595
|
+
tags = var.tags
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
resource "aws_kms_alias" "access_logs" {
|
|
4599
|
+
name = "alias/test-api-\${random_string.suffix.result}-access-logs"
|
|
4600
|
+
target_key_id = aws_kms_key.access_logs.key_id
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
# Access logs for the API Gateway stage
|
|
4604
|
+
resource "aws_cloudwatch_log_group" "access_logs" {
|
|
4605
|
+
name = "/aws/apigateway/test-api-\${random_string.suffix.result}/access"
|
|
4606
|
+
retention_in_days = 365
|
|
4607
|
+
kms_key_id = aws_kms_key.access_logs.arn
|
|
4608
|
+
tags = var.tags
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
# API Gateway stage
|
|
4612
|
+
resource "aws_api_gateway_stage" "api_stage" {
|
|
4613
|
+
#checkov:skip=CKV_AWS_120:API Gateway caching not required for this use case
|
|
4614
|
+
#checkov:skip=CKV2_AWS_4:Access logging is enabled below; verbose execution logging is intentionally not enabled
|
|
4615
|
+
#checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
|
|
4616
|
+
#checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
|
|
4617
|
+
deployment_id = aws_api_gateway_deployment.api_deployment.id
|
|
4618
|
+
rest_api_id = module.rest_api.api_id
|
|
4619
|
+
stage_name = "prod"
|
|
4620
|
+
xray_tracing_enabled = true
|
|
4621
|
+
|
|
4622
|
+
access_log_settings {
|
|
4623
|
+
destination_arn = aws_cloudwatch_log_group.access_logs.arn
|
|
4624
|
+
format = jsonencode({
|
|
4625
|
+
requestId = "$context.requestId"
|
|
4626
|
+
ip = "$context.identity.sourceIp"
|
|
4627
|
+
caller = "$context.identity.caller"
|
|
4628
|
+
user = "$context.identity.user"
|
|
4629
|
+
requestTime = "$context.requestTime"
|
|
4630
|
+
httpMethod = "$context.httpMethod"
|
|
4631
|
+
resourcePath = "$context.resourcePath"
|
|
4632
|
+
status = "$context.status"
|
|
4633
|
+
protocol = "$context.protocol"
|
|
4634
|
+
responseLength = "$context.responseLength"
|
|
4635
|
+
})
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
tags = var.tags
|
|
4639
|
+
|
|
4640
|
+
# Ensure the account-level role is configured before the stage is created
|
|
4641
|
+
depends_on = [aws_api_gateway_deployment.api_deployment, module.account]
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
# Default throttling applied to all resource paths and methods
|
|
4645
|
+
resource "aws_api_gateway_method_settings" "throttling" {
|
|
4646
|
+
#checkov:skip=CKV_AWS_225:API Gateway caching not required for this use case
|
|
4647
|
+
rest_api_id = module.rest_api.api_id
|
|
4648
|
+
stage_name = aws_api_gateway_stage.api_stage.stage_name
|
|
4649
|
+
method_path = "*/*"
|
|
4650
|
+
|
|
4651
|
+
settings {
|
|
4652
|
+
throttling_rate_limit = 10000
|
|
4653
|
+
throttling_burst_limit = 5000
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
# API Gateway Resource Policy
|
|
4658
|
+
resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
4659
|
+
rest_api_id = module.rest_api.api_id
|
|
4660
|
+
|
|
4661
|
+
policy = jsonencode({
|
|
4662
|
+
Version = "2012-10-17"
|
|
4663
|
+
Statement = [
|
|
4664
|
+
{
|
|
4665
|
+
# Allow all callers to invoke the API in the resource policy, since auth is handled by the Lambda Authorizer
|
|
4666
|
+
Effect = "Allow"
|
|
4667
|
+
Principal = "*"
|
|
4668
|
+
Action = "execute-api:Invoke"
|
|
4669
|
+
Resource = "execute-api:/*"
|
|
4670
|
+
}
|
|
4671
|
+
]
|
|
4672
|
+
})
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
# Lambda permission for API Gateway to invoke the function via alias
|
|
4676
|
+
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
4677
|
+
statement_id = "AllowExecutionFromAPIGateway"
|
|
4678
|
+
action = "lambda:InvokeFunction"
|
|
4679
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
4680
|
+
qualifier = aws_lambda_alias.live.name
|
|
4681
|
+
principal = "apigateway.amazonaws.com"
|
|
4682
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
4683
|
+
|
|
4684
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
4688
|
+
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
4689
|
+
statement_id = "AllowStreamingFromAPIGateway"
|
|
4690
|
+
action = "lambda:InvokeFunctionWithResponseStream"
|
|
4691
|
+
function_name = aws_lambda_function.api_lambda.function_name
|
|
4692
|
+
qualifier = aws_lambda_alias.live.name
|
|
4693
|
+
principal = "apigateway.amazonaws.com"
|
|
4694
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/*"
|
|
4695
|
+
|
|
4696
|
+
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
# Add API url to runtime config
|
|
4700
|
+
module "add_url_to_runtime_config" {
|
|
4701
|
+
source = "../../../core/runtime-config/entry"
|
|
4702
|
+
|
|
4703
|
+
namespace = "connection"
|
|
4704
|
+
key = "apis"
|
|
4705
|
+
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
4706
|
+
|
|
4707
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
# Outputs
|
|
4711
|
+
|
|
4712
|
+
# API Gateway Outputs (from core module)
|
|
4713
|
+
output "api_id" {
|
|
4714
|
+
description = "ID of the REST API Gateway"
|
|
4715
|
+
value = module.rest_api.api_id
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
output "api_arn" {
|
|
4719
|
+
description = "ARN of the REST API Gateway"
|
|
4720
|
+
value = module.rest_api.api_arn
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
output "api_endpoint" {
|
|
4724
|
+
description = "Base URL of the REST API Gateway"
|
|
4725
|
+
value = module.rest_api.api_endpoint
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
output "api_execution_arn" {
|
|
4729
|
+
description = "Execution ARN of the REST API Gateway"
|
|
4730
|
+
value = module.rest_api.api_execution_arn
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
output "stage_invoke_url" {
|
|
4734
|
+
description = "Invoke URL of the API Gateway stage"
|
|
4735
|
+
value = aws_api_gateway_stage.api_stage.invoke_url
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
output "stage_arn" {
|
|
4739
|
+
description = "ARN of the API Gateway stage"
|
|
4740
|
+
value = aws_api_gateway_stage.api_stage.arn
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
output "stage_execution_arn" {
|
|
4744
|
+
description = "Execution ARN of the API Gateway stage"
|
|
4745
|
+
value = aws_api_gateway_stage.api_stage.execution_arn
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
output "deployment_id" {
|
|
4749
|
+
description = "ID of the API Gateway deployment"
|
|
4750
|
+
value = aws_api_gateway_deployment.api_deployment.id
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
output "stage_id" {
|
|
4754
|
+
description = "ID of the API Gateway stage"
|
|
4755
|
+
value = aws_api_gateway_stage.api_stage.id
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4758
|
+
# Lambda Function Outputs
|
|
4759
|
+
output "lambda_function_name" {
|
|
4760
|
+
description = "Name of the Lambda function"
|
|
4761
|
+
value = aws_lambda_function.api_lambda.function_name
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
output "lambda_function_arn" {
|
|
4765
|
+
description = "ARN of the Lambda function"
|
|
4766
|
+
value = aws_lambda_function.api_lambda.arn
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
output "lambda_invoke_arn" {
|
|
4770
|
+
description = "Invoke ARN of the Lambda function"
|
|
4771
|
+
value = aws_lambda_function.api_lambda.invoke_arn
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
output "lambda_qualified_arn" {
|
|
4775
|
+
description = "Qualified ARN of the Lambda function"
|
|
4776
|
+
value = aws_lambda_function.api_lambda.qualified_arn
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
output "lambda_version" {
|
|
4780
|
+
description = "Version of the Lambda function"
|
|
4781
|
+
value = aws_lambda_function.api_lambda.version
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
output "lambda_source_code_hash" {
|
|
4785
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package"
|
|
4786
|
+
value = aws_lambda_function.api_lambda.source_code_hash
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4789
|
+
output "lambda_source_code_size" {
|
|
4790
|
+
description = "Size of the Lambda deployment package in bytes"
|
|
4791
|
+
value = aws_lambda_function.api_lambda.source_code_size
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
# IAM Role Outputs
|
|
4795
|
+
output "lambda_execution_role_arn" {
|
|
4796
|
+
description = "ARN of the Lambda execution role"
|
|
4797
|
+
value = aws_iam_role.lambda_execution_role.arn
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4800
|
+
output "lambda_execution_role_name" {
|
|
4801
|
+
description = "Name of the Lambda execution role"
|
|
4802
|
+
value = aws_iam_role.lambda_execution_role.name
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
# Integration Outputs
|
|
4806
|
+
output "integration_id" {
|
|
4807
|
+
description = "ID of the Lambda integration"
|
|
4808
|
+
value = aws_api_gateway_integration.lambda_integration.id
|
|
4809
|
+
}
|
|
4810
|
+
|
|
4811
|
+
output "proxy_resource_id" {
|
|
4812
|
+
description = "ID of the proxy resource"
|
|
4813
|
+
value = aws_api_gateway_resource.proxy_resource.id
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
output "proxy_method_id" {
|
|
4817
|
+
description = "ID of the proxy method"
|
|
4818
|
+
value = aws_api_gateway_method.proxy_method.id
|
|
4819
|
+
}
|
|
4820
|
+
|
|
4821
|
+
# CloudWatch Log Groups
|
|
4822
|
+
output "lambda_log_group_name" {
|
|
4823
|
+
description = "Name of the Lambda CloudWatch log group"
|
|
4824
|
+
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4827
|
+
output "lambda_log_group_arn" {
|
|
4828
|
+
description = "ARN of the Lambda CloudWatch log group"
|
|
4829
|
+
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
4830
|
+
}
|
|
4831
|
+
",
|
|
4832
|
+
}
|
|
4833
|
+
`;
|