@aws/nx-plugin 1.0.0-rc.83 → 1.0.0-rc.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generators.json +7 -0
- package/migrations.json +31 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
- package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
- package/src/agentcore-harness/files/project/README.md.template +4 -5
- package/src/internal/test-matrix/generator.js +16 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
- package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/metadata.json +3 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js +146 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
- package/src/open-api/json-metadata/generator.d.ts +24 -0
- package/src/open-api/json-metadata/generator.js +37 -0
- package/src/open-api/json-metadata/generator.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.js +6 -0
- package/src/open-api/json-metadata/schema.d.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.ts +9 -0
- package/src/open-api/json-metadata/schema.json +20 -0
- package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1875 -623
- package/src/py/fast-api/generator.js +2 -1
- package/src/py/fast-api/generator.js.map +1 -1
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
- package/src/sdk/open-api.d.ts +2 -0
- package/src/sdk/open-api.js +2 -1
- package/src/sdk/open-api.js.map +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1756 -457
- package/src/smithy/ts/api/generator.js +2 -1
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/terraform/project/files/application/bootstrap/providers.tf.template +11 -0
- package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
- package/src/terraform/project/files/application/src/providers.tf.template +6 -0
- package/src/terraform/project/files/checkov/checkov.yml.template +8 -0
- package/src/terraform/project/files/library/src/main.tf.template +9 -0
- package/src/terraform/project/generator.js +47 -4
- package/src/terraform/project/generator.js.map +1 -1
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1848 -614
- package/src/trpc/backend/files-operations/scripts/generate-operations.ts.template +40 -0
- package/src/trpc/backend/generator.js +10 -0
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
- package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
- package/src/utils/api-constructs/api-constructs.js +11 -0
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +251 -3
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +437 -2
- package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
- package/src/utils/api-constructs/open-api-metadata.d.ts +20 -3
- package/src/utils/api-constructs/open-api-metadata.js +64 -8
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/api-constructs/trpc-operations-metadata.d.ts +21 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js +55 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -0
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
- package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
- package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
- package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
- package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
- package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
- package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
|
@@ -3420,10 +3420,18 @@ output "api_log_group_arn" {
|
|
|
3420
3420
|
required_version = ">= 1.0"
|
|
3421
3421
|
|
|
3422
3422
|
required_providers {
|
|
3423
|
+
archive = {
|
|
3424
|
+
source = "hashicorp/archive"
|
|
3425
|
+
version = "2.8.0"
|
|
3426
|
+
}
|
|
3423
3427
|
aws = {
|
|
3424
3428
|
source = "hashicorp/aws"
|
|
3425
3429
|
version = "6.61.0"
|
|
3426
3430
|
}
|
|
3431
|
+
random = {
|
|
3432
|
+
source = "hashicorp/random"
|
|
3433
|
+
version = "3.9.0"
|
|
3434
|
+
}
|
|
3427
3435
|
}
|
|
3428
3436
|
}
|
|
3429
3437
|
|
|
@@ -3543,6 +3551,49 @@ resource "random_string" "suffix" {
|
|
|
3543
3551
|
upper = false
|
|
3544
3552
|
}
|
|
3545
3553
|
|
|
3554
|
+
locals {
|
|
3555
|
+
# Generated at build time from the API definition
|
|
3556
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
3557
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
3558
|
+
|
|
3559
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
3560
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
3561
|
+
|
|
3562
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
3563
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
3564
|
+
}
|
|
3565
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
3566
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
operation_path = { for op, details in local.operations : op =>
|
|
3570
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
3571
|
+
}
|
|
3572
|
+
route_key = { for op, details in local.operations : op =>
|
|
3573
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
3577
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
resource "terraform_data" "operations_metadata" {
|
|
3582
|
+
# Fails the plan when the operations metadata has not been generated
|
|
3583
|
+
input = local.operations_file
|
|
3584
|
+
|
|
3585
|
+
lifecycle {
|
|
3586
|
+
precondition {
|
|
3587
|
+
condition = fileexists(local.operations_file)
|
|
3588
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
3589
|
+
}
|
|
3590
|
+
precondition {
|
|
3591
|
+
condition = length(local.operations) > 0
|
|
3592
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3546
3597
|
# Resources
|
|
3547
3598
|
|
|
3548
3599
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -3603,9 +3654,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
3603
3654
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
3604
3655
|
}
|
|
3605
3656
|
|
|
3606
|
-
# Lambda function
|
|
3607
|
-
# This configures a single "router" lambda to serve all requests
|
|
3608
3657
|
resource "aws_lambda_function" "api_lambda" {
|
|
3658
|
+
# One lambda function per operation, each serving just that operation
|
|
3659
|
+
for_each = local.operations
|
|
3660
|
+
|
|
3609
3661
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
3610
3662
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3611
3663
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -3614,8 +3666,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3614
3666
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3615
3667
|
s3_key = aws_s3_object.lambda_zip.key
|
|
3616
3668
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3617
|
-
function_name =
|
|
3618
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
3669
|
+
function_name = local.function_name[each.key]
|
|
3670
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
3619
3671
|
handler = "index.handler"
|
|
3620
3672
|
runtime = "nodejs22.x"
|
|
3621
3673
|
timeout = 30
|
|
@@ -3651,7 +3703,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3651
3703
|
|
|
3652
3704
|
# IAM role for Lambda execution
|
|
3653
3705
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
3654
|
-
|
|
3706
|
+
for_each = local.operations
|
|
3707
|
+
|
|
3708
|
+
name = local.role_name[each.key]
|
|
3655
3709
|
|
|
3656
3710
|
assume_role_policy = jsonencode({
|
|
3657
3711
|
Version = "2012-10-17"
|
|
@@ -3671,32 +3725,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
3671
3725
|
|
|
3672
3726
|
# Attach basic execution policy to Lambda role
|
|
3673
3727
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3728
|
+
for_each = local.operations
|
|
3729
|
+
|
|
3674
3730
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
3675
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3731
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3676
3732
|
}
|
|
3677
3733
|
|
|
3678
3734
|
# Attach X-Ray tracing policy to Lambda role
|
|
3679
3735
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3736
|
+
for_each = local.operations
|
|
3737
|
+
|
|
3680
3738
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
3681
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3739
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3682
3740
|
}
|
|
3683
3741
|
|
|
3684
3742
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
3685
3743
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
3686
|
-
|
|
3744
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
3745
|
+
|
|
3687
3746
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
3688
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3747
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3689
3748
|
}
|
|
3690
3749
|
|
|
3691
3750
|
# Additional IAM policies for Lambda (if provided)
|
|
3692
3751
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3752
|
+
for_each = local.operations
|
|
3753
|
+
|
|
3754
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
3755
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
3696
3756
|
|
|
3697
3757
|
policy = jsonencode({
|
|
3698
3758
|
Version = "2012-10-17"
|
|
3699
|
-
Statement = local.
|
|
3759
|
+
Statement = local.lambda_policy_document_statements
|
|
3700
3760
|
})
|
|
3701
3761
|
}
|
|
3702
3762
|
|
|
@@ -3712,6 +3772,15 @@ locals {
|
|
|
3712
3772
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
3713
3773
|
}
|
|
3714
3774
|
] : [], var.additional_iam_policy_statements)
|
|
3775
|
+
|
|
3776
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
3777
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
3778
|
+
{
|
|
3779
|
+
Effect = "Deny"
|
|
3780
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
3781
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
3782
|
+
}
|
|
3783
|
+
]
|
|
3715
3784
|
}
|
|
3716
3785
|
|
|
3717
3786
|
# CloudWatch Log Group for Lambda
|
|
@@ -3719,8 +3788,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
3719
3788
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3720
3789
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
3721
3790
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
3722
|
-
|
|
3723
|
-
|
|
3791
|
+
for_each = local.operations
|
|
3792
|
+
|
|
3793
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
3794
|
+
tags = var.tags
|
|
3724
3795
|
}
|
|
3725
3796
|
|
|
3726
3797
|
|
|
@@ -3739,9 +3810,11 @@ resource "aws_apigatewayv2_authorizer" "cognito_authorizer" {
|
|
|
3739
3810
|
|
|
3740
3811
|
# Lambda integration for HTTP API
|
|
3741
3812
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
3813
|
+
for_each = local.operations
|
|
3814
|
+
|
|
3742
3815
|
api_id = module.http_api.api_id
|
|
3743
3816
|
integration_type = "AWS_PROXY"
|
|
3744
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
3817
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
3745
3818
|
|
|
3746
3819
|
payload_format_version = "2.0"
|
|
3747
3820
|
timeout_milliseconds = 30000
|
|
@@ -3749,15 +3822,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
3749
3822
|
depends_on = [aws_lambda_function.api_lambda]
|
|
3750
3823
|
}
|
|
3751
3824
|
|
|
3752
|
-
#
|
|
3753
|
-
resource "aws_apigatewayv2_route" "
|
|
3754
|
-
|
|
3755
|
-
# when cors settings are configured
|
|
3756
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
3825
|
+
# One route per operation, each targeting that operation's integration
|
|
3826
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
3827
|
+
for_each = local.operations
|
|
3757
3828
|
|
|
3758
3829
|
api_id = module.http_api.api_id
|
|
3759
|
-
route_key =
|
|
3760
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
3830
|
+
route_key = local.route_key[each.key]
|
|
3831
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
3761
3832
|
|
|
3762
3833
|
authorization_type = "JWT"
|
|
3763
3834
|
authorizer_id = aws_apigatewayv2_authorizer.cognito_authorizer.id
|
|
@@ -3778,11 +3849,13 @@ module "add_url_to_runtime_config" {
|
|
|
3778
3849
|
|
|
3779
3850
|
# Lambda permission for API Gateway to invoke the function
|
|
3780
3851
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3852
|
+
for_each = local.operations
|
|
3853
|
+
|
|
3781
3854
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
3782
3855
|
action = "lambda:InvokeFunction"
|
|
3783
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3856
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3784
3857
|
principal = "apigateway.amazonaws.com"
|
|
3785
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
3858
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3786
3859
|
|
|
3787
3860
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
3788
3861
|
}
|
|
@@ -3825,73 +3898,73 @@ output "stage_execution_arn" {
|
|
|
3825
3898
|
value = module.http_api.stage_execution_arn
|
|
3826
3899
|
}
|
|
3827
3900
|
|
|
3828
|
-
# Lambda Function Outputs
|
|
3829
|
-
output "
|
|
3830
|
-
description = "
|
|
3831
|
-
value =
|
|
3901
|
+
# Lambda Function Outputs, keyed by operation name
|
|
3902
|
+
output "operations" {
|
|
3903
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
3904
|
+
value = keys(local.operations)
|
|
3832
3905
|
}
|
|
3833
3906
|
|
|
3834
|
-
output "
|
|
3835
|
-
description = "
|
|
3836
|
-
value = aws_lambda_function.api_lambda.
|
|
3907
|
+
output "lambda_function_names" {
|
|
3908
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
3909
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
3837
3910
|
}
|
|
3838
3911
|
|
|
3839
|
-
output "
|
|
3840
|
-
description = "
|
|
3841
|
-
value = aws_lambda_function.api_lambda.
|
|
3912
|
+
output "lambda_function_arns" {
|
|
3913
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
3914
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
3842
3915
|
}
|
|
3843
3916
|
|
|
3844
|
-
output "
|
|
3845
|
-
description = "
|
|
3846
|
-
value = aws_lambda_function.api_lambda.
|
|
3917
|
+
output "lambda_invoke_arns" {
|
|
3918
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
3919
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
3847
3920
|
}
|
|
3848
3921
|
|
|
3849
|
-
output "
|
|
3850
|
-
description = "
|
|
3851
|
-
value = aws_lambda_function.api_lambda.
|
|
3922
|
+
output "lambda_qualified_arns" {
|
|
3923
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
3924
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
3852
3925
|
}
|
|
3853
3926
|
|
|
3854
|
-
output "
|
|
3855
|
-
description = "
|
|
3856
|
-
value = aws_lambda_function.api_lambda.
|
|
3927
|
+
output "lambda_versions" {
|
|
3928
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
3929
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
3857
3930
|
}
|
|
3858
3931
|
|
|
3859
|
-
output "
|
|
3860
|
-
description = "
|
|
3861
|
-
value =
|
|
3932
|
+
output "lambda_source_code_hash" {
|
|
3933
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
3934
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
3862
3935
|
}
|
|
3863
3936
|
|
|
3864
|
-
# IAM Role Outputs
|
|
3865
|
-
output "
|
|
3866
|
-
description = "ARN of
|
|
3867
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
3937
|
+
# IAM Role Outputs, keyed by operation name
|
|
3938
|
+
output "lambda_execution_role_arns" {
|
|
3939
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
3940
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
3868
3941
|
}
|
|
3869
3942
|
|
|
3870
|
-
output "
|
|
3871
|
-
description = "Name of
|
|
3872
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
3943
|
+
output "lambda_execution_role_names" {
|
|
3944
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
3945
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
3873
3946
|
}
|
|
3874
3947
|
|
|
3875
3948
|
output "security_group_id" {
|
|
3876
|
-
description = "Security group ID
|
|
3949
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
3877
3950
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
3878
3951
|
}
|
|
3879
3952
|
|
|
3880
|
-
# Integration Outputs
|
|
3881
|
-
output "
|
|
3882
|
-
description = "ID of
|
|
3883
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
3953
|
+
# Integration Outputs, keyed by operation name
|
|
3954
|
+
output "integration_ids" {
|
|
3955
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
3956
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
3884
3957
|
}
|
|
3885
3958
|
|
|
3886
|
-
# CloudWatch Log Groups
|
|
3887
|
-
output "
|
|
3888
|
-
description = "Name of
|
|
3889
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
3959
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
3960
|
+
output "lambda_log_group_names" {
|
|
3961
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3962
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
3890
3963
|
}
|
|
3891
3964
|
|
|
3892
|
-
output "
|
|
3893
|
-
description = "ARN of
|
|
3894
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
3965
|
+
output "lambda_log_group_arns" {
|
|
3966
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3967
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
3895
3968
|
}
|
|
3896
3969
|
|
|
3897
3970
|
output "api_log_group_name" {
|
|
@@ -4162,10 +4235,18 @@ output "api_log_group_arn" {
|
|
|
4162
4235
|
required_version = ">= 1.0"
|
|
4163
4236
|
|
|
4164
4237
|
required_providers {
|
|
4238
|
+
archive = {
|
|
4239
|
+
source = "hashicorp/archive"
|
|
4240
|
+
version = "2.8.0"
|
|
4241
|
+
}
|
|
4165
4242
|
aws = {
|
|
4166
4243
|
source = "hashicorp/aws"
|
|
4167
4244
|
version = "6.61.0"
|
|
4168
4245
|
}
|
|
4246
|
+
random = {
|
|
4247
|
+
source = "hashicorp/random"
|
|
4248
|
+
version = "3.9.0"
|
|
4249
|
+
}
|
|
4169
4250
|
}
|
|
4170
4251
|
}
|
|
4171
4252
|
|
|
@@ -4275,6 +4356,49 @@ resource "random_string" "suffix" {
|
|
|
4275
4356
|
upper = false
|
|
4276
4357
|
}
|
|
4277
4358
|
|
|
4359
|
+
locals {
|
|
4360
|
+
# Generated at build time from the API definition
|
|
4361
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
4362
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
4363
|
+
|
|
4364
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
4365
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
4366
|
+
|
|
4367
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
4368
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
4369
|
+
}
|
|
4370
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
4371
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
operation_path = { for op, details in local.operations : op =>
|
|
4375
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
4376
|
+
}
|
|
4377
|
+
route_key = { for op, details in local.operations : op =>
|
|
4378
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
4382
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
resource "terraform_data" "operations_metadata" {
|
|
4387
|
+
# Fails the plan when the operations metadata has not been generated
|
|
4388
|
+
input = local.operations_file
|
|
4389
|
+
|
|
4390
|
+
lifecycle {
|
|
4391
|
+
precondition {
|
|
4392
|
+
condition = fileexists(local.operations_file)
|
|
4393
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
4394
|
+
}
|
|
4395
|
+
precondition {
|
|
4396
|
+
condition = length(local.operations) > 0
|
|
4397
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4278
4402
|
# Resources
|
|
4279
4403
|
|
|
4280
4404
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -4335,9 +4459,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
4335
4459
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
4336
4460
|
}
|
|
4337
4461
|
|
|
4338
|
-
# Lambda function
|
|
4339
|
-
# This configures a single "router" lambda to serve all requests
|
|
4340
4462
|
resource "aws_lambda_function" "api_lambda" {
|
|
4463
|
+
# One lambda function per operation, each serving just that operation
|
|
4464
|
+
for_each = local.operations
|
|
4465
|
+
|
|
4341
4466
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
4342
4467
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4343
4468
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -4346,8 +4471,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4346
4471
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4347
4472
|
s3_key = aws_s3_object.lambda_zip.key
|
|
4348
4473
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4349
|
-
function_name =
|
|
4350
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
4474
|
+
function_name = local.function_name[each.key]
|
|
4475
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
4351
4476
|
handler = "index.handler"
|
|
4352
4477
|
runtime = "nodejs22.x"
|
|
4353
4478
|
timeout = 30
|
|
@@ -4383,7 +4508,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4383
4508
|
|
|
4384
4509
|
# IAM role for Lambda execution
|
|
4385
4510
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
4386
|
-
|
|
4511
|
+
for_each = local.operations
|
|
4512
|
+
|
|
4513
|
+
name = local.role_name[each.key]
|
|
4387
4514
|
|
|
4388
4515
|
assume_role_policy = jsonencode({
|
|
4389
4516
|
Version = "2012-10-17"
|
|
@@ -4403,32 +4530,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
4403
4530
|
|
|
4404
4531
|
# Attach basic execution policy to Lambda role
|
|
4405
4532
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4533
|
+
for_each = local.operations
|
|
4534
|
+
|
|
4406
4535
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4407
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4536
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4408
4537
|
}
|
|
4409
4538
|
|
|
4410
4539
|
# Attach X-Ray tracing policy to Lambda role
|
|
4411
4540
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4541
|
+
for_each = local.operations
|
|
4542
|
+
|
|
4412
4543
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4413
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4544
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4414
4545
|
}
|
|
4415
4546
|
|
|
4416
4547
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
4417
4548
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
4418
|
-
|
|
4549
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
4550
|
+
|
|
4419
4551
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
4420
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4552
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4421
4553
|
}
|
|
4422
4554
|
|
|
4423
4555
|
# Additional IAM policies for Lambda (if provided)
|
|
4424
4556
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4557
|
+
for_each = local.operations
|
|
4558
|
+
|
|
4559
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
4560
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
4428
4561
|
|
|
4429
4562
|
policy = jsonencode({
|
|
4430
4563
|
Version = "2012-10-17"
|
|
4431
|
-
Statement = local.
|
|
4564
|
+
Statement = local.lambda_policy_document_statements
|
|
4432
4565
|
})
|
|
4433
4566
|
}
|
|
4434
4567
|
|
|
@@ -4444,6 +4577,15 @@ locals {
|
|
|
4444
4577
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
4445
4578
|
}
|
|
4446
4579
|
] : [], var.additional_iam_policy_statements)
|
|
4580
|
+
|
|
4581
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
4582
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
4583
|
+
{
|
|
4584
|
+
Effect = "Deny"
|
|
4585
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
4586
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
4587
|
+
}
|
|
4588
|
+
]
|
|
4447
4589
|
}
|
|
4448
4590
|
|
|
4449
4591
|
# CloudWatch Log Group for Lambda
|
|
@@ -4451,8 +4593,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
4451
4593
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4452
4594
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4453
4595
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4454
|
-
|
|
4455
|
-
|
|
4596
|
+
for_each = local.operations
|
|
4597
|
+
|
|
4598
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
4599
|
+
tags = var.tags
|
|
4456
4600
|
}
|
|
4457
4601
|
|
|
4458
4602
|
|
|
@@ -4538,7 +4682,8 @@ resource "aws_apigatewayv2_authorizer" "custom_authorizer" {
|
|
|
4538
4682
|
authorizer_type = "REQUEST"
|
|
4539
4683
|
authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
|
|
4540
4684
|
authorizer_payload_format_version = "2.0"
|
|
4541
|
-
authorizer_result_ttl_in_seconds =
|
|
4685
|
+
authorizer_result_ttl_in_seconds = 300
|
|
4686
|
+
identity_sources = ["$request.header.Authorization"]
|
|
4542
4687
|
enable_simple_responses = true
|
|
4543
4688
|
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
4544
4689
|
}
|
|
@@ -4553,9 +4698,11 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
4553
4698
|
|
|
4554
4699
|
# Lambda integration for HTTP API
|
|
4555
4700
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
4701
|
+
for_each = local.operations
|
|
4702
|
+
|
|
4556
4703
|
api_id = module.http_api.api_id
|
|
4557
4704
|
integration_type = "AWS_PROXY"
|
|
4558
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
4705
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
4559
4706
|
|
|
4560
4707
|
payload_format_version = "2.0"
|
|
4561
4708
|
timeout_milliseconds = 30000
|
|
@@ -4563,15 +4710,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
4563
4710
|
depends_on = [aws_lambda_function.api_lambda]
|
|
4564
4711
|
}
|
|
4565
4712
|
|
|
4566
|
-
#
|
|
4567
|
-
resource "aws_apigatewayv2_route" "
|
|
4568
|
-
|
|
4569
|
-
# when cors settings are configured
|
|
4570
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
4713
|
+
# One route per operation, each targeting that operation's integration
|
|
4714
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
4715
|
+
for_each = local.operations
|
|
4571
4716
|
|
|
4572
4717
|
api_id = module.http_api.api_id
|
|
4573
|
-
route_key =
|
|
4574
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
4718
|
+
route_key = local.route_key[each.key]
|
|
4719
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
4575
4720
|
|
|
4576
4721
|
authorization_type = "CUSTOM"
|
|
4577
4722
|
authorizer_id = aws_apigatewayv2_authorizer.custom_authorizer.id
|
|
@@ -4592,11 +4737,13 @@ module "add_url_to_runtime_config" {
|
|
|
4592
4737
|
|
|
4593
4738
|
# Lambda permission for API Gateway to invoke the function
|
|
4594
4739
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
4740
|
+
for_each = local.operations
|
|
4741
|
+
|
|
4595
4742
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
4596
4743
|
action = "lambda:InvokeFunction"
|
|
4597
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4744
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
4598
4745
|
principal = "apigateway.amazonaws.com"
|
|
4599
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
4746
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4600
4747
|
|
|
4601
4748
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
4602
4749
|
}
|
|
@@ -4639,73 +4786,73 @@ output "stage_execution_arn" {
|
|
|
4639
4786
|
value = module.http_api.stage_execution_arn
|
|
4640
4787
|
}
|
|
4641
4788
|
|
|
4642
|
-
# Lambda Function Outputs
|
|
4643
|
-
output "
|
|
4644
|
-
description = "
|
|
4645
|
-
value =
|
|
4789
|
+
# Lambda Function Outputs, keyed by operation name
|
|
4790
|
+
output "operations" {
|
|
4791
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
4792
|
+
value = keys(local.operations)
|
|
4646
4793
|
}
|
|
4647
4794
|
|
|
4648
|
-
output "
|
|
4649
|
-
description = "
|
|
4650
|
-
value = aws_lambda_function.api_lambda.
|
|
4795
|
+
output "lambda_function_names" {
|
|
4796
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
4797
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
4651
4798
|
}
|
|
4652
4799
|
|
|
4653
|
-
output "
|
|
4654
|
-
description = "
|
|
4655
|
-
value = aws_lambda_function.api_lambda.
|
|
4800
|
+
output "lambda_function_arns" {
|
|
4801
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
4802
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
4656
4803
|
}
|
|
4657
4804
|
|
|
4658
|
-
output "
|
|
4659
|
-
description = "
|
|
4660
|
-
value = aws_lambda_function.api_lambda.
|
|
4805
|
+
output "lambda_invoke_arns" {
|
|
4806
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
4807
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
4661
4808
|
}
|
|
4662
4809
|
|
|
4663
|
-
output "
|
|
4664
|
-
description = "
|
|
4665
|
-
value = aws_lambda_function.api_lambda.
|
|
4810
|
+
output "lambda_qualified_arns" {
|
|
4811
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
4812
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
4666
4813
|
}
|
|
4667
4814
|
|
|
4668
|
-
output "
|
|
4669
|
-
description = "
|
|
4670
|
-
value = aws_lambda_function.api_lambda.
|
|
4815
|
+
output "lambda_versions" {
|
|
4816
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
4817
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
4671
4818
|
}
|
|
4672
4819
|
|
|
4673
|
-
output "
|
|
4674
|
-
description = "
|
|
4675
|
-
value =
|
|
4820
|
+
output "lambda_source_code_hash" {
|
|
4821
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
4822
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
4676
4823
|
}
|
|
4677
4824
|
|
|
4678
|
-
# IAM Role Outputs
|
|
4679
|
-
output "
|
|
4680
|
-
description = "ARN of
|
|
4681
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
4825
|
+
# IAM Role Outputs, keyed by operation name
|
|
4826
|
+
output "lambda_execution_role_arns" {
|
|
4827
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
4828
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
4682
4829
|
}
|
|
4683
4830
|
|
|
4684
|
-
output "
|
|
4685
|
-
description = "Name of
|
|
4686
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
4831
|
+
output "lambda_execution_role_names" {
|
|
4832
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
4833
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
4687
4834
|
}
|
|
4688
4835
|
|
|
4689
4836
|
output "security_group_id" {
|
|
4690
|
-
description = "Security group ID
|
|
4837
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
4691
4838
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
4692
4839
|
}
|
|
4693
4840
|
|
|
4694
|
-
# Integration Outputs
|
|
4695
|
-
output "
|
|
4696
|
-
description = "ID of
|
|
4697
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
4841
|
+
# Integration Outputs, keyed by operation name
|
|
4842
|
+
output "integration_ids" {
|
|
4843
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
4844
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
4698
4845
|
}
|
|
4699
4846
|
|
|
4700
|
-
# CloudWatch Log Groups
|
|
4701
|
-
output "
|
|
4702
|
-
description = "Name of
|
|
4703
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
4847
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
4848
|
+
output "lambda_log_group_names" {
|
|
4849
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4850
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
4704
4851
|
}
|
|
4705
4852
|
|
|
4706
|
-
output "
|
|
4707
|
-
description = "ARN of
|
|
4708
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
4853
|
+
output "lambda_log_group_arns" {
|
|
4854
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4855
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
4709
4856
|
}
|
|
4710
4857
|
|
|
4711
4858
|
output "api_log_group_name" {
|
|
@@ -4976,10 +5123,18 @@ output "api_log_group_arn" {
|
|
|
4976
5123
|
required_version = ">= 1.0"
|
|
4977
5124
|
|
|
4978
5125
|
required_providers {
|
|
5126
|
+
archive = {
|
|
5127
|
+
source = "hashicorp/archive"
|
|
5128
|
+
version = "2.8.0"
|
|
5129
|
+
}
|
|
4979
5130
|
aws = {
|
|
4980
5131
|
source = "hashicorp/aws"
|
|
4981
5132
|
version = "6.61.0"
|
|
4982
5133
|
}
|
|
5134
|
+
random = {
|
|
5135
|
+
source = "hashicorp/random"
|
|
5136
|
+
version = "3.9.0"
|
|
5137
|
+
}
|
|
4983
5138
|
}
|
|
4984
5139
|
}
|
|
4985
5140
|
|
|
@@ -5089,6 +5244,49 @@ resource "random_string" "suffix" {
|
|
|
5089
5244
|
upper = false
|
|
5090
5245
|
}
|
|
5091
5246
|
|
|
5247
|
+
locals {
|
|
5248
|
+
# Generated at build time from the API definition
|
|
5249
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
5250
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
5251
|
+
|
|
5252
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
5253
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
5254
|
+
|
|
5255
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
5256
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
5257
|
+
}
|
|
5258
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
5259
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
operation_path = { for op, details in local.operations : op =>
|
|
5263
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
5264
|
+
}
|
|
5265
|
+
route_key = { for op, details in local.operations : op =>
|
|
5266
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
5270
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
resource "terraform_data" "operations_metadata" {
|
|
5275
|
+
# Fails the plan when the operations metadata has not been generated
|
|
5276
|
+
input = local.operations_file
|
|
5277
|
+
|
|
5278
|
+
lifecycle {
|
|
5279
|
+
precondition {
|
|
5280
|
+
condition = fileexists(local.operations_file)
|
|
5281
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
5282
|
+
}
|
|
5283
|
+
precondition {
|
|
5284
|
+
condition = length(local.operations) > 0
|
|
5285
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5092
5290
|
# Resources
|
|
5093
5291
|
|
|
5094
5292
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -5149,9 +5347,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
5149
5347
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
5150
5348
|
}
|
|
5151
5349
|
|
|
5152
|
-
# Lambda function
|
|
5153
|
-
# This configures a single "router" lambda to serve all requests
|
|
5154
5350
|
resource "aws_lambda_function" "api_lambda" {
|
|
5351
|
+
# One lambda function per operation, each serving just that operation
|
|
5352
|
+
for_each = local.operations
|
|
5353
|
+
|
|
5155
5354
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
5156
5355
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
5157
5356
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -5160,8 +5359,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5160
5359
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
5161
5360
|
s3_key = aws_s3_object.lambda_zip.key
|
|
5162
5361
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
5163
|
-
function_name =
|
|
5164
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
5362
|
+
function_name = local.function_name[each.key]
|
|
5363
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
5165
5364
|
handler = "index.handler"
|
|
5166
5365
|
runtime = "nodejs22.x"
|
|
5167
5366
|
timeout = 30
|
|
@@ -5197,7 +5396,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5197
5396
|
|
|
5198
5397
|
# IAM role for Lambda execution
|
|
5199
5398
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
5200
|
-
|
|
5399
|
+
for_each = local.operations
|
|
5400
|
+
|
|
5401
|
+
name = local.role_name[each.key]
|
|
5201
5402
|
|
|
5202
5403
|
assume_role_policy = jsonencode({
|
|
5203
5404
|
Version = "2012-10-17"
|
|
@@ -5217,32 +5418,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
5217
5418
|
|
|
5218
5419
|
# Attach basic execution policy to Lambda role
|
|
5219
5420
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
5421
|
+
for_each = local.operations
|
|
5422
|
+
|
|
5220
5423
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
5221
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5424
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5222
5425
|
}
|
|
5223
5426
|
|
|
5224
5427
|
# Attach X-Ray tracing policy to Lambda role
|
|
5225
5428
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
5429
|
+
for_each = local.operations
|
|
5430
|
+
|
|
5226
5431
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
5227
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5432
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5228
5433
|
}
|
|
5229
5434
|
|
|
5230
5435
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
5231
5436
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
5232
|
-
|
|
5437
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
5438
|
+
|
|
5233
5439
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
5234
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5440
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5235
5441
|
}
|
|
5236
5442
|
|
|
5237
5443
|
# Additional IAM policies for Lambda (if provided)
|
|
5238
5444
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5445
|
+
for_each = local.operations
|
|
5446
|
+
|
|
5447
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
5448
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
5242
5449
|
|
|
5243
5450
|
policy = jsonencode({
|
|
5244
5451
|
Version = "2012-10-17"
|
|
5245
|
-
Statement = local.
|
|
5452
|
+
Statement = local.lambda_policy_document_statements
|
|
5246
5453
|
})
|
|
5247
5454
|
}
|
|
5248
5455
|
|
|
@@ -5258,6 +5465,15 @@ locals {
|
|
|
5258
5465
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
5259
5466
|
}
|
|
5260
5467
|
] : [], var.additional_iam_policy_statements)
|
|
5468
|
+
|
|
5469
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
5470
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
5471
|
+
{
|
|
5472
|
+
Effect = "Deny"
|
|
5473
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
5474
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
5475
|
+
}
|
|
5476
|
+
]
|
|
5261
5477
|
}
|
|
5262
5478
|
|
|
5263
5479
|
# CloudWatch Log Group for Lambda
|
|
@@ -5265,17 +5481,21 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
5265
5481
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
5266
5482
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
5267
5483
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
5268
|
-
|
|
5269
|
-
|
|
5484
|
+
for_each = local.operations
|
|
5485
|
+
|
|
5486
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
5487
|
+
tags = var.tags
|
|
5270
5488
|
}
|
|
5271
5489
|
|
|
5272
5490
|
|
|
5273
5491
|
|
|
5274
5492
|
# Lambda integration for HTTP API
|
|
5275
5493
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
5494
|
+
for_each = local.operations
|
|
5495
|
+
|
|
5276
5496
|
api_id = module.http_api.api_id
|
|
5277
5497
|
integration_type = "AWS_PROXY"
|
|
5278
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
5498
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
5279
5499
|
|
|
5280
5500
|
payload_format_version = "2.0"
|
|
5281
5501
|
timeout_milliseconds = 30000
|
|
@@ -5283,15 +5503,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
5283
5503
|
depends_on = [aws_lambda_function.api_lambda]
|
|
5284
5504
|
}
|
|
5285
5505
|
|
|
5286
|
-
#
|
|
5287
|
-
resource "aws_apigatewayv2_route" "
|
|
5288
|
-
|
|
5289
|
-
# when cors settings are configured
|
|
5290
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
5506
|
+
# One route per operation, each targeting that operation's integration
|
|
5507
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
5508
|
+
for_each = local.operations
|
|
5291
5509
|
|
|
5292
5510
|
api_id = module.http_api.api_id
|
|
5293
|
-
route_key =
|
|
5294
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
5511
|
+
route_key = local.route_key[each.key]
|
|
5512
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
5295
5513
|
|
|
5296
5514
|
authorization_type = "AWS_IAM"
|
|
5297
5515
|
|
|
@@ -5311,11 +5529,13 @@ module "add_url_to_runtime_config" {
|
|
|
5311
5529
|
|
|
5312
5530
|
# Lambda permission for API Gateway to invoke the function
|
|
5313
5531
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
5532
|
+
for_each = local.operations
|
|
5533
|
+
|
|
5314
5534
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
5315
5535
|
action = "lambda:InvokeFunction"
|
|
5316
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5536
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
5317
5537
|
principal = "apigateway.amazonaws.com"
|
|
5318
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
5538
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
5319
5539
|
|
|
5320
5540
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
5321
5541
|
}
|
|
@@ -5358,73 +5578,73 @@ output "stage_execution_arn" {
|
|
|
5358
5578
|
value = module.http_api.stage_execution_arn
|
|
5359
5579
|
}
|
|
5360
5580
|
|
|
5361
|
-
# Lambda Function Outputs
|
|
5362
|
-
output "
|
|
5363
|
-
description = "
|
|
5364
|
-
value =
|
|
5581
|
+
# Lambda Function Outputs, keyed by operation name
|
|
5582
|
+
output "operations" {
|
|
5583
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
5584
|
+
value = keys(local.operations)
|
|
5365
5585
|
}
|
|
5366
5586
|
|
|
5367
|
-
output "
|
|
5368
|
-
description = "
|
|
5369
|
-
value = aws_lambda_function.api_lambda.
|
|
5587
|
+
output "lambda_function_names" {
|
|
5588
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
5589
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
5370
5590
|
}
|
|
5371
5591
|
|
|
5372
|
-
output "
|
|
5373
|
-
description = "
|
|
5374
|
-
value = aws_lambda_function.api_lambda.
|
|
5592
|
+
output "lambda_function_arns" {
|
|
5593
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
5594
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
5375
5595
|
}
|
|
5376
5596
|
|
|
5377
|
-
output "
|
|
5378
|
-
description = "
|
|
5379
|
-
value = aws_lambda_function.api_lambda.
|
|
5597
|
+
output "lambda_invoke_arns" {
|
|
5598
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
5599
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
5380
5600
|
}
|
|
5381
5601
|
|
|
5382
|
-
output "
|
|
5383
|
-
description = "
|
|
5384
|
-
value = aws_lambda_function.api_lambda.
|
|
5602
|
+
output "lambda_qualified_arns" {
|
|
5603
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
5604
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
5385
5605
|
}
|
|
5386
5606
|
|
|
5387
|
-
output "
|
|
5388
|
-
description = "
|
|
5389
|
-
value = aws_lambda_function.api_lambda.
|
|
5607
|
+
output "lambda_versions" {
|
|
5608
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
5609
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
5390
5610
|
}
|
|
5391
5611
|
|
|
5392
|
-
output "
|
|
5393
|
-
description = "
|
|
5394
|
-
value =
|
|
5612
|
+
output "lambda_source_code_hash" {
|
|
5613
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
5614
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
5395
5615
|
}
|
|
5396
5616
|
|
|
5397
|
-
# IAM Role Outputs
|
|
5398
|
-
output "
|
|
5399
|
-
description = "ARN of
|
|
5400
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
5617
|
+
# IAM Role Outputs, keyed by operation name
|
|
5618
|
+
output "lambda_execution_role_arns" {
|
|
5619
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
5620
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
5401
5621
|
}
|
|
5402
5622
|
|
|
5403
|
-
output "
|
|
5404
|
-
description = "Name of
|
|
5405
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
5623
|
+
output "lambda_execution_role_names" {
|
|
5624
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
5625
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
5406
5626
|
}
|
|
5407
5627
|
|
|
5408
5628
|
output "security_group_id" {
|
|
5409
|
-
description = "Security group ID
|
|
5629
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
5410
5630
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
5411
5631
|
}
|
|
5412
5632
|
|
|
5413
|
-
# Integration Outputs
|
|
5414
|
-
output "
|
|
5415
|
-
description = "ID of
|
|
5416
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
5633
|
+
# Integration Outputs, keyed by operation name
|
|
5634
|
+
output "integration_ids" {
|
|
5635
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
5636
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
5417
5637
|
}
|
|
5418
5638
|
|
|
5419
|
-
# CloudWatch Log Groups
|
|
5420
|
-
output "
|
|
5421
|
-
description = "Name of
|
|
5422
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
5639
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
5640
|
+
output "lambda_log_group_names" {
|
|
5641
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5642
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
5423
5643
|
}
|
|
5424
5644
|
|
|
5425
|
-
output "
|
|
5426
|
-
description = "ARN of
|
|
5427
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
5645
|
+
output "lambda_log_group_arns" {
|
|
5646
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5647
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
5428
5648
|
}
|
|
5429
5649
|
|
|
5430
5650
|
output "api_log_group_name" {
|
|
@@ -5643,22 +5863,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
5643
5863
|
}
|
|
5644
5864
|
}
|
|
5645
5865
|
|
|
5866
|
+
resource "random_id" "waf_log_suffix" {
|
|
5867
|
+
count = var.enable_waf ? 1 : 0
|
|
5868
|
+
byte_length = 4
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
locals {
|
|
5872
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
# KMS key for encrypting WAF request logs at rest
|
|
5876
|
+
resource "aws_kms_key" "waf_logs" {
|
|
5877
|
+
count = var.enable_waf ? 1 : 0
|
|
5878
|
+
|
|
5879
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
5880
|
+
deletion_window_in_days = 7
|
|
5881
|
+
enable_key_rotation = true
|
|
5882
|
+
|
|
5883
|
+
policy = jsonencode({
|
|
5884
|
+
Version = "2012-10-17"
|
|
5885
|
+
Statement = [
|
|
5886
|
+
{
|
|
5887
|
+
Sid = "Enable IAM User Permissions"
|
|
5888
|
+
Effect = "Allow"
|
|
5889
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
5890
|
+
Action = "kms:*"
|
|
5891
|
+
Resource = "*"
|
|
5892
|
+
},
|
|
5893
|
+
{
|
|
5894
|
+
Sid = "Allow CloudWatch Logs"
|
|
5895
|
+
Effect = "Allow"
|
|
5896
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
5897
|
+
Action = [
|
|
5898
|
+
"kms:Encrypt",
|
|
5899
|
+
"kms:Decrypt",
|
|
5900
|
+
"kms:ReEncrypt*",
|
|
5901
|
+
"kms:GenerateDataKey*",
|
|
5902
|
+
"kms:DescribeKey"
|
|
5903
|
+
]
|
|
5904
|
+
Resource = "*"
|
|
5905
|
+
Condition = {
|
|
5906
|
+
ArnEquals = {
|
|
5907
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:\${local.waf_log_group_name}"
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
]
|
|
5912
|
+
})
|
|
5913
|
+
|
|
5914
|
+
tags = var.tags
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5917
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
5918
|
+
count = var.enable_waf ? 1 : 0
|
|
5919
|
+
|
|
5920
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
5921
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5646
5924
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
5647
5925
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
5648
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
5649
5926
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
5650
5927
|
count = var.enable_waf ? 1 : 0
|
|
5651
5928
|
|
|
5652
|
-
name =
|
|
5929
|
+
name = local.waf_log_group_name
|
|
5653
5930
|
retention_in_days = 30
|
|
5931
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
5654
5932
|
tags = var.tags
|
|
5655
5933
|
}
|
|
5656
5934
|
|
|
5657
|
-
resource "random_id" "waf_log_suffix" {
|
|
5658
|
-
count = var.enable_waf ? 1 : 0
|
|
5659
|
-
byte_length = 4
|
|
5660
|
-
}
|
|
5661
|
-
|
|
5662
5935
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
5663
5936
|
count = var.enable_waf ? 1 : 0
|
|
5664
5937
|
|
|
@@ -5704,10 +5977,18 @@ output "waf_web_acl_arn" {
|
|
|
5704
5977
|
required_version = ">= 1.0"
|
|
5705
5978
|
|
|
5706
5979
|
required_providers {
|
|
5980
|
+
archive = {
|
|
5981
|
+
source = "hashicorp/archive"
|
|
5982
|
+
version = "2.8.0"
|
|
5983
|
+
}
|
|
5707
5984
|
aws = {
|
|
5708
5985
|
source = "hashicorp/aws"
|
|
5709
5986
|
version = "6.61.0"
|
|
5710
5987
|
}
|
|
5988
|
+
random = {
|
|
5989
|
+
source = "hashicorp/random"
|
|
5990
|
+
version = "3.9.0"
|
|
5991
|
+
}
|
|
5711
5992
|
}
|
|
5712
5993
|
}
|
|
5713
5994
|
|
|
@@ -5823,73 +6104,302 @@ resource "random_string" "suffix" {
|
|
|
5823
6104
|
upper = false
|
|
5824
6105
|
}
|
|
5825
6106
|
|
|
5826
|
-
|
|
6107
|
+
locals {
|
|
6108
|
+
# Generated at build time from the API definition
|
|
6109
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
6110
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
5827
6111
|
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
type = "zip"
|
|
5831
|
-
source_dir = "\${path.module}/../../../../../../../dist/apps/test-api/bundle"
|
|
5832
|
-
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
5833
|
-
}
|
|
6112
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
6113
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
5834
6114
|
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
}
|
|
6115
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
6116
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
6117
|
+
}
|
|
6118
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
6119
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
6120
|
+
}
|
|
5842
6121
|
|
|
5843
|
-
#
|
|
5844
|
-
#
|
|
5845
|
-
|
|
5846
|
-
|
|
6122
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
6123
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
6124
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
6125
|
+
|
|
6126
|
+
path_nodes = merge([
|
|
6127
|
+
for op, segments in local.operation_segments : {
|
|
6128
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
6129
|
+
depth = i
|
|
6130
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
6131
|
+
part = segment
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
]...)
|
|
6135
|
+
|
|
6136
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
6137
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
6138
|
+
max_path_depth = 16
|
|
6139
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
6140
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
6141
|
+
} }
|
|
6142
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
6143
|
+
|
|
6144
|
+
resource_ids = merge(
|
|
6145
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
6146
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
6147
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
6148
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
6149
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
6150
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
6151
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
6152
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
6153
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
6154
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
6155
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
6156
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
6157
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
6158
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
6159
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
6160
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
6161
|
+
)
|
|
6162
|
+
|
|
6163
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
6164
|
+
local.resource_ids[join("/", segments)]
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
6168
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
resource "terraform_data" "operations_metadata" {
|
|
6173
|
+
# Fails the plan when the operations metadata has not been generated
|
|
6174
|
+
input = local.operations_file
|
|
6175
|
+
|
|
6176
|
+
lifecycle {
|
|
6177
|
+
precondition {
|
|
6178
|
+
condition = fileexists(local.operations_file)
|
|
6179
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
6180
|
+
}
|
|
6181
|
+
precondition {
|
|
6182
|
+
condition = length(local.operations) > 0
|
|
6183
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
6184
|
+
}
|
|
6185
|
+
precondition {
|
|
6186
|
+
condition = length(local.paths_too_deep) == 0
|
|
6187
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
5847
6190
|
}
|
|
5848
6191
|
|
|
5849
|
-
|
|
5850
|
-
|
|
6192
|
+
# API Gateway resources at path depth 0
|
|
6193
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
6194
|
+
for_each = local.path_nodes_by_depth[0]
|
|
5851
6195
|
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
6196
|
+
rest_api_id = module.rest_api.api_id
|
|
6197
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
6198
|
+
path_part = each.value.part
|
|
6199
|
+
}
|
|
5856
6200
|
|
|
5857
|
-
|
|
5858
|
-
|
|
6201
|
+
# API Gateway resources at path depth 1
|
|
6202
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
6203
|
+
for_each = local.path_nodes_by_depth[1]
|
|
5859
6204
|
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
6205
|
+
rest_api_id = module.rest_api.api_id
|
|
6206
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
6207
|
+
path_part = each.value.part
|
|
6208
|
+
}
|
|
5864
6209
|
|
|
5865
|
-
|
|
5866
|
-
|
|
6210
|
+
# API Gateway resources at path depth 2
|
|
6211
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
6212
|
+
for_each = local.path_nodes_by_depth[2]
|
|
6213
|
+
|
|
6214
|
+
rest_api_id = module.rest_api.api_id
|
|
6215
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
6216
|
+
path_part = each.value.part
|
|
5867
6217
|
}
|
|
5868
6218
|
|
|
5869
|
-
|
|
5870
|
-
|
|
6219
|
+
# API Gateway resources at path depth 3
|
|
6220
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
6221
|
+
for_each = local.path_nodes_by_depth[3]
|
|
5871
6222
|
|
|
5872
|
-
|
|
5873
|
-
|
|
6223
|
+
rest_api_id = module.rest_api.api_id
|
|
6224
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
6225
|
+
path_part = each.value.part
|
|
6226
|
+
}
|
|
5874
6227
|
|
|
5875
|
-
|
|
6228
|
+
# API Gateway resources at path depth 4
|
|
6229
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
6230
|
+
for_each = local.path_nodes_by_depth[4]
|
|
6231
|
+
|
|
6232
|
+
rest_api_id = module.rest_api.api_id
|
|
6233
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
6234
|
+
path_part = each.value.part
|
|
5876
6235
|
}
|
|
5877
6236
|
|
|
5878
|
-
#
|
|
5879
|
-
resource "
|
|
5880
|
-
|
|
6237
|
+
# API Gateway resources at path depth 5
|
|
6238
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
6239
|
+
for_each = local.path_nodes_by_depth[5]
|
|
5881
6240
|
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
vpc_id = var.vpc_id
|
|
5886
|
-
tags = var.tags
|
|
6241
|
+
rest_api_id = module.rest_api.api_id
|
|
6242
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
6243
|
+
path_part = each.value.part
|
|
5887
6244
|
}
|
|
5888
6245
|
|
|
5889
|
-
|
|
5890
|
-
|
|
6246
|
+
# API Gateway resources at path depth 6
|
|
6247
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
6248
|
+
for_each = local.path_nodes_by_depth[6]
|
|
5891
6249
|
|
|
5892
|
-
|
|
6250
|
+
rest_api_id = module.rest_api.api_id
|
|
6251
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
6252
|
+
path_part = each.value.part
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
# API Gateway resources at path depth 7
|
|
6256
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
6257
|
+
for_each = local.path_nodes_by_depth[7]
|
|
6258
|
+
|
|
6259
|
+
rest_api_id = module.rest_api.api_id
|
|
6260
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
6261
|
+
path_part = each.value.part
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
# API Gateway resources at path depth 8
|
|
6265
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
6266
|
+
for_each = local.path_nodes_by_depth[8]
|
|
6267
|
+
|
|
6268
|
+
rest_api_id = module.rest_api.api_id
|
|
6269
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
6270
|
+
path_part = each.value.part
|
|
6271
|
+
}
|
|
6272
|
+
|
|
6273
|
+
# API Gateway resources at path depth 9
|
|
6274
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
6275
|
+
for_each = local.path_nodes_by_depth[9]
|
|
6276
|
+
|
|
6277
|
+
rest_api_id = module.rest_api.api_id
|
|
6278
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
6279
|
+
path_part = each.value.part
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6282
|
+
# API Gateway resources at path depth 10
|
|
6283
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
6284
|
+
for_each = local.path_nodes_by_depth[10]
|
|
6285
|
+
|
|
6286
|
+
rest_api_id = module.rest_api.api_id
|
|
6287
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
6288
|
+
path_part = each.value.part
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
# API Gateway resources at path depth 11
|
|
6292
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
6293
|
+
for_each = local.path_nodes_by_depth[11]
|
|
6294
|
+
|
|
6295
|
+
rest_api_id = module.rest_api.api_id
|
|
6296
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
6297
|
+
path_part = each.value.part
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
# API Gateway resources at path depth 12
|
|
6301
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
6302
|
+
for_each = local.path_nodes_by_depth[12]
|
|
6303
|
+
|
|
6304
|
+
rest_api_id = module.rest_api.api_id
|
|
6305
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
6306
|
+
path_part = each.value.part
|
|
6307
|
+
}
|
|
6308
|
+
|
|
6309
|
+
# API Gateway resources at path depth 13
|
|
6310
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
6311
|
+
for_each = local.path_nodes_by_depth[13]
|
|
6312
|
+
|
|
6313
|
+
rest_api_id = module.rest_api.api_id
|
|
6314
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
6315
|
+
path_part = each.value.part
|
|
6316
|
+
}
|
|
6317
|
+
|
|
6318
|
+
# API Gateway resources at path depth 14
|
|
6319
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
6320
|
+
for_each = local.path_nodes_by_depth[14]
|
|
6321
|
+
|
|
6322
|
+
rest_api_id = module.rest_api.api_id
|
|
6323
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
6324
|
+
path_part = each.value.part
|
|
6325
|
+
}
|
|
6326
|
+
|
|
6327
|
+
# API Gateway resources at path depth 15
|
|
6328
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
6329
|
+
for_each = local.path_nodes_by_depth[15]
|
|
6330
|
+
|
|
6331
|
+
rest_api_id = module.rest_api.api_id
|
|
6332
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
6333
|
+
path_part = each.value.part
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
# Resources
|
|
6337
|
+
|
|
6338
|
+
# Create Lambda ZIP file from the bundle directory
|
|
6339
|
+
data "archive_file" "lambda_zip" {
|
|
6340
|
+
type = "zip"
|
|
6341
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test-api/bundle"
|
|
6342
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
6343
|
+
}
|
|
6344
|
+
|
|
6345
|
+
resource "aws_s3_object" "lambda_zip" {
|
|
6346
|
+
bucket = var.asset_bucket_name
|
|
6347
|
+
key = "apis/test-api/\${data.archive_file.lambda_zip.output_sha256}.zip"
|
|
6348
|
+
source = data.archive_file.lambda_zip.output_path
|
|
6349
|
+
source_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
6350
|
+
etag = data.archive_file.lambda_zip.output_md5
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6353
|
+
# Use the core REST API module
|
|
6354
|
+
# Account-level CloudWatch role required for REST API access logging
|
|
6355
|
+
module "account" {
|
|
6356
|
+
source = "../../../core/api/api-gateway-account"
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
module "rest_api" {
|
|
6360
|
+
source = "../../../core/api/rest-api"
|
|
6361
|
+
|
|
6362
|
+
api_name = "TestApi-\${random_string.suffix.result}"
|
|
6363
|
+
api_description = "TestApi REST API"
|
|
6364
|
+
stage_name = "prod"
|
|
6365
|
+
stage_auto_deploy = true
|
|
6366
|
+
|
|
6367
|
+
# WAF Configuration
|
|
6368
|
+
enable_waf = var.enable_waf
|
|
6369
|
+
|
|
6370
|
+
# CORS Configuration
|
|
6371
|
+
cors_allow_headers = var.cors_allow_headers
|
|
6372
|
+
cors_allow_methods = var.cors_allow_methods
|
|
6373
|
+
cors_allow_origins = var.cors_allow_origins
|
|
6374
|
+
|
|
6375
|
+
# Tags
|
|
6376
|
+
tags = var.tags
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
resource "aws_wafv2_web_acl_association" "api_waf_association" {
|
|
6380
|
+
count = var.enable_waf ? 1 : 0
|
|
6381
|
+
|
|
6382
|
+
resource_arn = aws_api_gateway_stage.api_stage.arn
|
|
6383
|
+
web_acl_arn = module.rest_api.waf_web_acl_arn
|
|
6384
|
+
|
|
6385
|
+
depends_on = [aws_api_gateway_stage.api_stage]
|
|
6386
|
+
}
|
|
6387
|
+
|
|
6388
|
+
# Security group for the API Lambda function, used when deployed into a VPC
|
|
6389
|
+
resource "aws_security_group" "api_lambda" {
|
|
6390
|
+
count = var.enable_vpc ? 1 : 0
|
|
6391
|
+
|
|
6392
|
+
#checkov:skip=CKV2_AWS_5:Attached to api_lambda via vpc_config block; Checkov cannot resolve this reference
|
|
6393
|
+
name_prefix = "test-api-lambda-"
|
|
6394
|
+
description = "Security group for the TestApi API Lambda function"
|
|
6395
|
+
vpc_id = var.vpc_id
|
|
6396
|
+
tags = var.tags
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
6400
|
+
count = var.enable_vpc ? 1 : 0
|
|
6401
|
+
|
|
6402
|
+
security_group_id = aws_security_group.api_lambda[0].id
|
|
5893
6403
|
cidr_ipv4 = "0.0.0.0/0"
|
|
5894
6404
|
from_port = 443
|
|
5895
6405
|
to_port = 443
|
|
@@ -5897,8 +6407,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
5897
6407
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
5898
6408
|
}
|
|
5899
6409
|
|
|
5900
|
-
# Lambda function
|
|
5901
6410
|
resource "aws_lambda_function" "api_lambda" {
|
|
6411
|
+
# One lambda function per operation, each serving just that operation
|
|
6412
|
+
for_each = local.operations
|
|
6413
|
+
|
|
5902
6414
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
5903
6415
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
5904
6416
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -5907,8 +6419,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5907
6419
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
5908
6420
|
s3_key = aws_s3_object.lambda_zip.key
|
|
5909
6421
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
5910
|
-
function_name =
|
|
5911
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
6422
|
+
function_name = local.function_name[each.key]
|
|
6423
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
5912
6424
|
handler = "index.handler"
|
|
5913
6425
|
runtime = "nodejs22.x"
|
|
5914
6426
|
timeout = 30
|
|
@@ -5944,7 +6456,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5944
6456
|
|
|
5945
6457
|
# IAM role for Lambda execution
|
|
5946
6458
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
5947
|
-
|
|
6459
|
+
for_each = local.operations
|
|
6460
|
+
|
|
6461
|
+
name = local.role_name[each.key]
|
|
5948
6462
|
|
|
5949
6463
|
assume_role_policy = jsonencode({
|
|
5950
6464
|
Version = "2012-10-17"
|
|
@@ -5964,32 +6478,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
5964
6478
|
|
|
5965
6479
|
# Attach basic execution policy to Lambda role
|
|
5966
6480
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
6481
|
+
for_each = local.operations
|
|
6482
|
+
|
|
5967
6483
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
5968
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6484
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5969
6485
|
}
|
|
5970
6486
|
|
|
5971
6487
|
# Attach X-Ray tracing policy to Lambda role
|
|
5972
6488
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
6489
|
+
for_each = local.operations
|
|
6490
|
+
|
|
5973
6491
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
5974
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6492
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5975
6493
|
}
|
|
5976
6494
|
|
|
5977
6495
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
5978
6496
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
5979
|
-
|
|
6497
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
6498
|
+
|
|
5980
6499
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
5981
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6500
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5982
6501
|
}
|
|
5983
6502
|
|
|
5984
6503
|
# Additional IAM policies for Lambda (if provided)
|
|
5985
6504
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
6505
|
+
for_each = local.operations
|
|
6506
|
+
|
|
6507
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
6508
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
5989
6509
|
|
|
5990
6510
|
policy = jsonencode({
|
|
5991
6511
|
Version = "2012-10-17"
|
|
5992
|
-
Statement = local.
|
|
6512
|
+
Statement = local.lambda_policy_document_statements
|
|
5993
6513
|
})
|
|
5994
6514
|
}
|
|
5995
6515
|
|
|
@@ -6005,6 +6525,15 @@ locals {
|
|
|
6005
6525
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
6006
6526
|
}
|
|
6007
6527
|
] : [], var.additional_iam_policy_statements)
|
|
6528
|
+
|
|
6529
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
6530
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
6531
|
+
{
|
|
6532
|
+
Effect = "Deny"
|
|
6533
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
6534
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
6535
|
+
}
|
|
6536
|
+
]
|
|
6008
6537
|
}
|
|
6009
6538
|
|
|
6010
6539
|
# CloudWatch Log Group for Lambda
|
|
@@ -6012,8 +6541,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
6012
6541
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
6013
6542
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
6014
6543
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
6015
|
-
|
|
6016
|
-
|
|
6544
|
+
for_each = local.operations
|
|
6545
|
+
|
|
6546
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
6547
|
+
tags = var.tags
|
|
6017
6548
|
}
|
|
6018
6549
|
|
|
6019
6550
|
|
|
@@ -6026,33 +6557,14 @@ resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
|
6026
6557
|
identity_source = "method.request.header.Authorization"
|
|
6027
6558
|
}
|
|
6028
6559
|
|
|
6029
|
-
#
|
|
6030
|
-
resource "
|
|
6031
|
-
rest_api_id = module.rest_api.api_id
|
|
6032
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
6033
|
-
path_part = "{proxy+}"
|
|
6034
|
-
}
|
|
6035
|
-
|
|
6036
|
-
# Lambda integration for REST API
|
|
6037
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6038
|
-
rest_api_id = module.rest_api.api_id
|
|
6039
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
6040
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
6041
|
-
|
|
6042
|
-
integration_http_method = "POST"
|
|
6043
|
-
type = "AWS_PROXY"
|
|
6044
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
6045
|
-
response_transfer_mode = "STREAM"
|
|
6046
|
-
|
|
6047
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
6048
|
-
}
|
|
6049
|
-
|
|
6050
|
-
# Method for proxy integration
|
|
6051
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
6560
|
+
# Method per operation
|
|
6561
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
6052
6562
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
6563
|
+
for_each = local.operations
|
|
6564
|
+
|
|
6053
6565
|
rest_api_id = module.rest_api.api_id
|
|
6054
|
-
resource_id =
|
|
6055
|
-
http_method =
|
|
6566
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6567
|
+
http_method = upper(each.value.method)
|
|
6056
6568
|
|
|
6057
6569
|
authorization = "COGNITO_USER_POOLS"
|
|
6058
6570
|
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
@@ -6060,28 +6572,51 @@ resource "aws_api_gateway_method" "proxy_method" {
|
|
|
6060
6572
|
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
6061
6573
|
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
6062
6574
|
|
|
6575
|
+
# Path parameters must be declared on the method
|
|
6063
6576
|
request_parameters = {
|
|
6064
|
-
|
|
6577
|
+
for segment in local.operation_segments[each.key] :
|
|
6578
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
6579
|
+
if startswith(segment, "{")
|
|
6065
6580
|
}
|
|
6066
6581
|
|
|
6067
6582
|
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
6068
6583
|
}
|
|
6069
6584
|
|
|
6070
|
-
#
|
|
6585
|
+
# Lambda integration per operation
|
|
6586
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6587
|
+
for_each = local.operations
|
|
6588
|
+
|
|
6589
|
+
rest_api_id = module.rest_api.api_id
|
|
6590
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6591
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
6592
|
+
|
|
6593
|
+
integration_http_method = "POST"
|
|
6594
|
+
type = "AWS_PROXY"
|
|
6595
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
6596
|
+
response_transfer_mode = "STREAM"
|
|
6597
|
+
|
|
6598
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
6071
6602
|
resource "aws_api_gateway_method" "options_method" {
|
|
6072
6603
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
6073
6604
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
6605
|
+
for_each = local.resource_ids
|
|
6606
|
+
|
|
6074
6607
|
rest_api_id = module.rest_api.api_id
|
|
6075
|
-
resource_id =
|
|
6608
|
+
resource_id = each.value
|
|
6076
6609
|
http_method = "OPTIONS"
|
|
6077
6610
|
authorization = "NONE"
|
|
6078
6611
|
}
|
|
6079
6612
|
|
|
6080
|
-
# CORS integration for OPTIONS
|
|
6613
|
+
# CORS integration for OPTIONS methods
|
|
6081
6614
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
6615
|
+
for_each = local.resource_ids
|
|
6616
|
+
|
|
6082
6617
|
rest_api_id = module.rest_api.api_id
|
|
6083
|
-
resource_id =
|
|
6084
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6618
|
+
resource_id = each.value
|
|
6619
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6085
6620
|
|
|
6086
6621
|
type = "MOCK"
|
|
6087
6622
|
request_templates = {
|
|
@@ -6089,11 +6624,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
6089
6624
|
}
|
|
6090
6625
|
}
|
|
6091
6626
|
|
|
6092
|
-
# OPTIONS method
|
|
6627
|
+
# OPTIONS method responses
|
|
6093
6628
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
6629
|
+
for_each = local.resource_ids
|
|
6630
|
+
|
|
6094
6631
|
rest_api_id = module.rest_api.api_id
|
|
6095
|
-
resource_id =
|
|
6096
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6632
|
+
resource_id = each.value
|
|
6633
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6097
6634
|
status_code = "204"
|
|
6098
6635
|
|
|
6099
6636
|
response_parameters = {
|
|
@@ -6103,18 +6640,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
6103
6640
|
}
|
|
6104
6641
|
}
|
|
6105
6642
|
|
|
6106
|
-
# OPTIONS integration
|
|
6643
|
+
# OPTIONS integration responses
|
|
6107
6644
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
6645
|
+
for_each = local.resource_ids
|
|
6646
|
+
|
|
6108
6647
|
rest_api_id = module.rest_api.api_id
|
|
6109
|
-
resource_id =
|
|
6110
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6111
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
6648
|
+
resource_id = each.value
|
|
6649
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6650
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
6112
6651
|
|
|
6113
6652
|
response_parameters = {
|
|
6114
6653
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
6115
6654
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
6116
6655
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
6117
6656
|
}
|
|
6657
|
+
|
|
6658
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
6118
6659
|
}
|
|
6119
6660
|
|
|
6120
6661
|
# API Gateway deployment
|
|
@@ -6122,13 +6663,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
6122
6663
|
rest_api_id = module.rest_api.api_id
|
|
6123
6664
|
|
|
6124
6665
|
triggers = {
|
|
6125
|
-
redeployment = sha1(jsonencode(
|
|
6126
|
-
aws_api_gateway_resource.
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6666
|
+
redeployment = sha1(jsonencode(concat(
|
|
6667
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
6668
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
6669
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
6670
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
6671
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
6672
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
6673
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
6674
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
6675
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
6676
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
6677
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
6678
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
6679
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
6680
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
6681
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
6682
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
6683
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
6684
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
6685
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
6686
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
6687
|
+
)))
|
|
6132
6688
|
}
|
|
6133
6689
|
|
|
6134
6690
|
lifecycle {
|
|
@@ -6136,7 +6692,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
6136
6692
|
}
|
|
6137
6693
|
|
|
6138
6694
|
depends_on = [
|
|
6139
|
-
aws_api_gateway_method.
|
|
6695
|
+
aws_api_gateway_method.operation_methods,
|
|
6140
6696
|
aws_api_gateway_integration.lambda_integration,
|
|
6141
6697
|
aws_api_gateway_method.options_method,
|
|
6142
6698
|
aws_api_gateway_integration.options_integration,
|
|
@@ -6264,11 +6820,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
6264
6820
|
|
|
6265
6821
|
# Lambda permission for API Gateway to invoke the function
|
|
6266
6822
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
6823
|
+
for_each = local.operations
|
|
6824
|
+
|
|
6267
6825
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
6268
6826
|
action = "lambda:InvokeFunction"
|
|
6269
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
6827
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
6270
6828
|
principal = "apigateway.amazonaws.com"
|
|
6271
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
6829
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
6272
6830
|
|
|
6273
6831
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
6274
6832
|
}
|
|
@@ -6280,7 +6838,7 @@ module "add_url_to_runtime_config" {
|
|
|
6280
6838
|
|
|
6281
6839
|
namespace = "connection"
|
|
6282
6840
|
key = "apis"
|
|
6283
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
6841
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
6284
6842
|
|
|
6285
6843
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
6286
6844
|
}
|
|
@@ -6333,83 +6891,83 @@ output "stage_id" {
|
|
|
6333
6891
|
value = aws_api_gateway_stage.api_stage.id
|
|
6334
6892
|
}
|
|
6335
6893
|
|
|
6336
|
-
# Lambda Function Outputs
|
|
6337
|
-
output "
|
|
6338
|
-
description = "
|
|
6339
|
-
value =
|
|
6894
|
+
# Lambda Function Outputs, keyed by operation name
|
|
6895
|
+
output "operations" {
|
|
6896
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
6897
|
+
value = keys(local.operations)
|
|
6340
6898
|
}
|
|
6341
6899
|
|
|
6342
|
-
output "
|
|
6343
|
-
description = "
|
|
6344
|
-
value = aws_lambda_function.api_lambda.
|
|
6900
|
+
output "lambda_function_names" {
|
|
6901
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
6902
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
6345
6903
|
}
|
|
6346
6904
|
|
|
6347
|
-
output "
|
|
6348
|
-
description = "
|
|
6349
|
-
value = aws_lambda_function.api_lambda.
|
|
6905
|
+
output "lambda_function_arns" {
|
|
6906
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
6907
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
6350
6908
|
}
|
|
6351
6909
|
|
|
6352
|
-
output "
|
|
6353
|
-
description = "
|
|
6354
|
-
value = aws_lambda_function.api_lambda.
|
|
6910
|
+
output "lambda_invoke_arns" {
|
|
6911
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
6912
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
6355
6913
|
}
|
|
6356
6914
|
|
|
6357
|
-
output "
|
|
6358
|
-
description = "
|
|
6359
|
-
value = aws_lambda_function.api_lambda.
|
|
6915
|
+
output "lambda_qualified_arns" {
|
|
6916
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
6917
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
6360
6918
|
}
|
|
6361
6919
|
|
|
6362
|
-
output "
|
|
6363
|
-
description = "
|
|
6364
|
-
value = aws_lambda_function.api_lambda.
|
|
6920
|
+
output "lambda_versions" {
|
|
6921
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
6922
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
6365
6923
|
}
|
|
6366
6924
|
|
|
6367
|
-
output "
|
|
6368
|
-
description = "
|
|
6369
|
-
value =
|
|
6925
|
+
output "lambda_source_code_hash" {
|
|
6926
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
6927
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
6370
6928
|
}
|
|
6371
6929
|
|
|
6372
|
-
# IAM Role Outputs
|
|
6373
|
-
output "
|
|
6374
|
-
description = "ARN of
|
|
6375
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
6930
|
+
# IAM Role Outputs, keyed by operation name
|
|
6931
|
+
output "lambda_execution_role_arns" {
|
|
6932
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
6933
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
6376
6934
|
}
|
|
6377
6935
|
|
|
6378
|
-
output "
|
|
6379
|
-
description = "Name of
|
|
6380
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
6936
|
+
output "lambda_execution_role_names" {
|
|
6937
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
6938
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
6381
6939
|
}
|
|
6382
6940
|
|
|
6383
6941
|
output "security_group_id" {
|
|
6384
|
-
description = "Security group ID
|
|
6942
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
6385
6943
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
6386
6944
|
}
|
|
6387
6945
|
|
|
6388
|
-
# Integration Outputs
|
|
6389
|
-
output "
|
|
6390
|
-
description = "ID of
|
|
6391
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
6946
|
+
# Integration Outputs, keyed by operation name
|
|
6947
|
+
output "integration_ids" {
|
|
6948
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
6949
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
6392
6950
|
}
|
|
6393
6951
|
|
|
6394
|
-
output "
|
|
6395
|
-
description = "ID of
|
|
6396
|
-
value =
|
|
6952
|
+
output "method_ids" {
|
|
6953
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
6954
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
6397
6955
|
}
|
|
6398
6956
|
|
|
6399
|
-
output "
|
|
6400
|
-
description = "ID of
|
|
6401
|
-
value =
|
|
6957
|
+
output "resource_ids" {
|
|
6958
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
6959
|
+
value = local.resource_ids
|
|
6402
6960
|
}
|
|
6403
6961
|
|
|
6404
|
-
# CloudWatch Log Groups
|
|
6405
|
-
output "
|
|
6406
|
-
description = "Name of
|
|
6407
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
6962
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
6963
|
+
output "lambda_log_group_names" {
|
|
6964
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6965
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
6408
6966
|
}
|
|
6409
6967
|
|
|
6410
|
-
output "
|
|
6411
|
-
description = "ARN of
|
|
6412
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
6968
|
+
output "lambda_log_group_arns" {
|
|
6969
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6970
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
6413
6971
|
}
|
|
6414
6972
|
",
|
|
6415
6973
|
}
|
|
@@ -6619,22 +7177,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
6619
7177
|
}
|
|
6620
7178
|
}
|
|
6621
7179
|
|
|
7180
|
+
resource "random_id" "waf_log_suffix" {
|
|
7181
|
+
count = var.enable_waf ? 1 : 0
|
|
7182
|
+
byte_length = 4
|
|
7183
|
+
}
|
|
7184
|
+
|
|
7185
|
+
locals {
|
|
7186
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
7187
|
+
}
|
|
7188
|
+
|
|
7189
|
+
# KMS key for encrypting WAF request logs at rest
|
|
7190
|
+
resource "aws_kms_key" "waf_logs" {
|
|
7191
|
+
count = var.enable_waf ? 1 : 0
|
|
7192
|
+
|
|
7193
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
7194
|
+
deletion_window_in_days = 7
|
|
7195
|
+
enable_key_rotation = true
|
|
7196
|
+
|
|
7197
|
+
policy = jsonencode({
|
|
7198
|
+
Version = "2012-10-17"
|
|
7199
|
+
Statement = [
|
|
7200
|
+
{
|
|
7201
|
+
Sid = "Enable IAM User Permissions"
|
|
7202
|
+
Effect = "Allow"
|
|
7203
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
7204
|
+
Action = "kms:*"
|
|
7205
|
+
Resource = "*"
|
|
7206
|
+
},
|
|
7207
|
+
{
|
|
7208
|
+
Sid = "Allow CloudWatch Logs"
|
|
7209
|
+
Effect = "Allow"
|
|
7210
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
7211
|
+
Action = [
|
|
7212
|
+
"kms:Encrypt",
|
|
7213
|
+
"kms:Decrypt",
|
|
7214
|
+
"kms:ReEncrypt*",
|
|
7215
|
+
"kms:GenerateDataKey*",
|
|
7216
|
+
"kms:DescribeKey"
|
|
7217
|
+
]
|
|
7218
|
+
Resource = "*"
|
|
7219
|
+
Condition = {
|
|
7220
|
+
ArnEquals = {
|
|
7221
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:\${local.waf_log_group_name}"
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
7224
|
+
}
|
|
7225
|
+
]
|
|
7226
|
+
})
|
|
7227
|
+
|
|
7228
|
+
tags = var.tags
|
|
7229
|
+
}
|
|
7230
|
+
|
|
7231
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
7232
|
+
count = var.enable_waf ? 1 : 0
|
|
7233
|
+
|
|
7234
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
7235
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
7236
|
+
}
|
|
7237
|
+
|
|
6622
7238
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
6623
7239
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
6624
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
6625
7240
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
6626
7241
|
count = var.enable_waf ? 1 : 0
|
|
6627
7242
|
|
|
6628
|
-
name =
|
|
7243
|
+
name = local.waf_log_group_name
|
|
6629
7244
|
retention_in_days = 30
|
|
7245
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
6630
7246
|
tags = var.tags
|
|
6631
7247
|
}
|
|
6632
7248
|
|
|
6633
|
-
resource "random_id" "waf_log_suffix" {
|
|
6634
|
-
count = var.enable_waf ? 1 : 0
|
|
6635
|
-
byte_length = 4
|
|
6636
|
-
}
|
|
6637
|
-
|
|
6638
7249
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
6639
7250
|
count = var.enable_waf ? 1 : 0
|
|
6640
7251
|
|
|
@@ -6680,10 +7291,18 @@ output "waf_web_acl_arn" {
|
|
|
6680
7291
|
required_version = ">= 1.0"
|
|
6681
7292
|
|
|
6682
7293
|
required_providers {
|
|
7294
|
+
archive = {
|
|
7295
|
+
source = "hashicorp/archive"
|
|
7296
|
+
version = "2.8.0"
|
|
7297
|
+
}
|
|
6683
7298
|
aws = {
|
|
6684
7299
|
source = "hashicorp/aws"
|
|
6685
7300
|
version = "6.61.0"
|
|
6686
7301
|
}
|
|
7302
|
+
random = {
|
|
7303
|
+
source = "hashicorp/random"
|
|
7304
|
+
version = "3.9.0"
|
|
7305
|
+
}
|
|
6687
7306
|
}
|
|
6688
7307
|
}
|
|
6689
7308
|
|
|
@@ -6765,28 +7384,257 @@ variable "cors_allow_origins" {
|
|
|
6765
7384
|
default = ["*"]
|
|
6766
7385
|
}
|
|
6767
7386
|
|
|
6768
|
-
# WAF Configuration
|
|
6769
|
-
variable "enable_waf" {
|
|
6770
|
-
description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
|
|
6771
|
-
type = bool
|
|
6772
|
-
default = true
|
|
7387
|
+
# WAF Configuration
|
|
7388
|
+
variable "enable_waf" {
|
|
7389
|
+
description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
|
|
7390
|
+
type = bool
|
|
7391
|
+
default = true
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
# Tags
|
|
7395
|
+
variable "tags" {
|
|
7396
|
+
description = "Tags to apply to all resources"
|
|
7397
|
+
type = map(string)
|
|
7398
|
+
default = {}
|
|
7399
|
+
}
|
|
7400
|
+
|
|
7401
|
+
# Get current AWS region and account ID
|
|
7402
|
+
data "aws_region" "current" {}
|
|
7403
|
+
data "aws_caller_identity" "current" {}
|
|
7404
|
+
|
|
7405
|
+
resource "random_string" "suffix" {
|
|
7406
|
+
length = 8
|
|
7407
|
+
special = false
|
|
7408
|
+
upper = false
|
|
7409
|
+
}
|
|
7410
|
+
|
|
7411
|
+
locals {
|
|
7412
|
+
# Generated at build time from the API definition
|
|
7413
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
7414
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
7415
|
+
|
|
7416
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
7417
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
7418
|
+
|
|
7419
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
7420
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
7421
|
+
}
|
|
7422
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
7423
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7426
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
7427
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
7428
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
7429
|
+
|
|
7430
|
+
path_nodes = merge([
|
|
7431
|
+
for op, segments in local.operation_segments : {
|
|
7432
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
7433
|
+
depth = i
|
|
7434
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
7435
|
+
part = segment
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
]...)
|
|
7439
|
+
|
|
7440
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
7441
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
7442
|
+
max_path_depth = 16
|
|
7443
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
7444
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
7445
|
+
} }
|
|
7446
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
7447
|
+
|
|
7448
|
+
resource_ids = merge(
|
|
7449
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
7450
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
7451
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
7452
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
7453
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
7454
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
7455
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
7456
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
7457
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
7458
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
7459
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
7460
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
7461
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
7462
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
7463
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
7464
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
7465
|
+
)
|
|
7466
|
+
|
|
7467
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
7468
|
+
local.resource_ids[join("/", segments)]
|
|
7469
|
+
}
|
|
7470
|
+
|
|
7471
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
7472
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
|
|
7476
|
+
resource "terraform_data" "operations_metadata" {
|
|
7477
|
+
# Fails the plan when the operations metadata has not been generated
|
|
7478
|
+
input = local.operations_file
|
|
7479
|
+
|
|
7480
|
+
lifecycle {
|
|
7481
|
+
precondition {
|
|
7482
|
+
condition = fileexists(local.operations_file)
|
|
7483
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
7484
|
+
}
|
|
7485
|
+
precondition {
|
|
7486
|
+
condition = length(local.operations) > 0
|
|
7487
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
7488
|
+
}
|
|
7489
|
+
precondition {
|
|
7490
|
+
condition = length(local.paths_too_deep) == 0
|
|
7491
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
# API Gateway resources at path depth 0
|
|
7497
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
7498
|
+
for_each = local.path_nodes_by_depth[0]
|
|
7499
|
+
|
|
7500
|
+
rest_api_id = module.rest_api.api_id
|
|
7501
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
7502
|
+
path_part = each.value.part
|
|
7503
|
+
}
|
|
7504
|
+
|
|
7505
|
+
# API Gateway resources at path depth 1
|
|
7506
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
7507
|
+
for_each = local.path_nodes_by_depth[1]
|
|
7508
|
+
|
|
7509
|
+
rest_api_id = module.rest_api.api_id
|
|
7510
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
7511
|
+
path_part = each.value.part
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
# API Gateway resources at path depth 2
|
|
7515
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
7516
|
+
for_each = local.path_nodes_by_depth[2]
|
|
7517
|
+
|
|
7518
|
+
rest_api_id = module.rest_api.api_id
|
|
7519
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
7520
|
+
path_part = each.value.part
|
|
7521
|
+
}
|
|
7522
|
+
|
|
7523
|
+
# API Gateway resources at path depth 3
|
|
7524
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
7525
|
+
for_each = local.path_nodes_by_depth[3]
|
|
7526
|
+
|
|
7527
|
+
rest_api_id = module.rest_api.api_id
|
|
7528
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
7529
|
+
path_part = each.value.part
|
|
7530
|
+
}
|
|
7531
|
+
|
|
7532
|
+
# API Gateway resources at path depth 4
|
|
7533
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
7534
|
+
for_each = local.path_nodes_by_depth[4]
|
|
7535
|
+
|
|
7536
|
+
rest_api_id = module.rest_api.api_id
|
|
7537
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
7538
|
+
path_part = each.value.part
|
|
7539
|
+
}
|
|
7540
|
+
|
|
7541
|
+
# API Gateway resources at path depth 5
|
|
7542
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
7543
|
+
for_each = local.path_nodes_by_depth[5]
|
|
7544
|
+
|
|
7545
|
+
rest_api_id = module.rest_api.api_id
|
|
7546
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
7547
|
+
path_part = each.value.part
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
# API Gateway resources at path depth 6
|
|
7551
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
7552
|
+
for_each = local.path_nodes_by_depth[6]
|
|
7553
|
+
|
|
7554
|
+
rest_api_id = module.rest_api.api_id
|
|
7555
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
7556
|
+
path_part = each.value.part
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
# API Gateway resources at path depth 7
|
|
7560
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
7561
|
+
for_each = local.path_nodes_by_depth[7]
|
|
7562
|
+
|
|
7563
|
+
rest_api_id = module.rest_api.api_id
|
|
7564
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
7565
|
+
path_part = each.value.part
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
# API Gateway resources at path depth 8
|
|
7569
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
7570
|
+
for_each = local.path_nodes_by_depth[8]
|
|
7571
|
+
|
|
7572
|
+
rest_api_id = module.rest_api.api_id
|
|
7573
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
7574
|
+
path_part = each.value.part
|
|
7575
|
+
}
|
|
7576
|
+
|
|
7577
|
+
# API Gateway resources at path depth 9
|
|
7578
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
7579
|
+
for_each = local.path_nodes_by_depth[9]
|
|
7580
|
+
|
|
7581
|
+
rest_api_id = module.rest_api.api_id
|
|
7582
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
7583
|
+
path_part = each.value.part
|
|
7584
|
+
}
|
|
7585
|
+
|
|
7586
|
+
# API Gateway resources at path depth 10
|
|
7587
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
7588
|
+
for_each = local.path_nodes_by_depth[10]
|
|
7589
|
+
|
|
7590
|
+
rest_api_id = module.rest_api.api_id
|
|
7591
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
7592
|
+
path_part = each.value.part
|
|
7593
|
+
}
|
|
7594
|
+
|
|
7595
|
+
# API Gateway resources at path depth 11
|
|
7596
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
7597
|
+
for_each = local.path_nodes_by_depth[11]
|
|
7598
|
+
|
|
7599
|
+
rest_api_id = module.rest_api.api_id
|
|
7600
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
7601
|
+
path_part = each.value.part
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
# API Gateway resources at path depth 12
|
|
7605
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
7606
|
+
for_each = local.path_nodes_by_depth[12]
|
|
7607
|
+
|
|
7608
|
+
rest_api_id = module.rest_api.api_id
|
|
7609
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
7610
|
+
path_part = each.value.part
|
|
7611
|
+
}
|
|
7612
|
+
|
|
7613
|
+
# API Gateway resources at path depth 13
|
|
7614
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
7615
|
+
for_each = local.path_nodes_by_depth[13]
|
|
7616
|
+
|
|
7617
|
+
rest_api_id = module.rest_api.api_id
|
|
7618
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
7619
|
+
path_part = each.value.part
|
|
6773
7620
|
}
|
|
6774
7621
|
|
|
6775
|
-
#
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
7622
|
+
# API Gateway resources at path depth 14
|
|
7623
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
7624
|
+
for_each = local.path_nodes_by_depth[14]
|
|
7625
|
+
|
|
7626
|
+
rest_api_id = module.rest_api.api_id
|
|
7627
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
7628
|
+
path_part = each.value.part
|
|
6780
7629
|
}
|
|
6781
7630
|
|
|
6782
|
-
#
|
|
6783
|
-
|
|
6784
|
-
|
|
7631
|
+
# API Gateway resources at path depth 15
|
|
7632
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
7633
|
+
for_each = local.path_nodes_by_depth[15]
|
|
6785
7634
|
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
upper = false
|
|
7635
|
+
rest_api_id = module.rest_api.api_id
|
|
7636
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
7637
|
+
path_part = each.value.part
|
|
6790
7638
|
}
|
|
6791
7639
|
|
|
6792
7640
|
# Resources
|
|
@@ -6863,8 +7711,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
6863
7711
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
6864
7712
|
}
|
|
6865
7713
|
|
|
6866
|
-
# Lambda function
|
|
6867
7714
|
resource "aws_lambda_function" "api_lambda" {
|
|
7715
|
+
# One lambda function per operation, each serving just that operation
|
|
7716
|
+
for_each = local.operations
|
|
7717
|
+
|
|
6868
7718
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
6869
7719
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
6870
7720
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -6873,8 +7723,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
6873
7723
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
6874
7724
|
s3_key = aws_s3_object.lambda_zip.key
|
|
6875
7725
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
6876
|
-
function_name =
|
|
6877
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
7726
|
+
function_name = local.function_name[each.key]
|
|
7727
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
6878
7728
|
handler = "index.handler"
|
|
6879
7729
|
runtime = "nodejs22.x"
|
|
6880
7730
|
timeout = 30
|
|
@@ -6910,7 +7760,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
6910
7760
|
|
|
6911
7761
|
# IAM role for Lambda execution
|
|
6912
7762
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
6913
|
-
|
|
7763
|
+
for_each = local.operations
|
|
7764
|
+
|
|
7765
|
+
name = local.role_name[each.key]
|
|
6914
7766
|
|
|
6915
7767
|
assume_role_policy = jsonencode({
|
|
6916
7768
|
Version = "2012-10-17"
|
|
@@ -6930,32 +7782,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
6930
7782
|
|
|
6931
7783
|
# Attach basic execution policy to Lambda role
|
|
6932
7784
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
7785
|
+
for_each = local.operations
|
|
7786
|
+
|
|
6933
7787
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
6934
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7788
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6935
7789
|
}
|
|
6936
7790
|
|
|
6937
7791
|
# Attach X-Ray tracing policy to Lambda role
|
|
6938
7792
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
7793
|
+
for_each = local.operations
|
|
7794
|
+
|
|
6939
7795
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
6940
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7796
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6941
7797
|
}
|
|
6942
7798
|
|
|
6943
7799
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
6944
7800
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
6945
|
-
|
|
7801
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
7802
|
+
|
|
6946
7803
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
6947
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7804
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6948
7805
|
}
|
|
6949
7806
|
|
|
6950
7807
|
# Additional IAM policies for Lambda (if provided)
|
|
6951
7808
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
7809
|
+
for_each = local.operations
|
|
7810
|
+
|
|
7811
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
7812
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
6955
7813
|
|
|
6956
7814
|
policy = jsonencode({
|
|
6957
7815
|
Version = "2012-10-17"
|
|
6958
|
-
Statement = local.
|
|
7816
|
+
Statement = local.lambda_policy_document_statements
|
|
6959
7817
|
})
|
|
6960
7818
|
}
|
|
6961
7819
|
|
|
@@ -6971,6 +7829,15 @@ locals {
|
|
|
6971
7829
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
6972
7830
|
}
|
|
6973
7831
|
] : [], var.additional_iam_policy_statements)
|
|
7832
|
+
|
|
7833
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
7834
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
7835
|
+
{
|
|
7836
|
+
Effect = "Deny"
|
|
7837
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
7838
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
7839
|
+
}
|
|
7840
|
+
]
|
|
6974
7841
|
}
|
|
6975
7842
|
|
|
6976
7843
|
# CloudWatch Log Group for Lambda
|
|
@@ -6978,64 +7845,70 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
6978
7845
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
6979
7846
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
6980
7847
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
6981
|
-
|
|
6982
|
-
tags = var.tags
|
|
6983
|
-
}
|
|
6984
|
-
|
|
7848
|
+
for_each = local.operations
|
|
6985
7849
|
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
6989
|
-
rest_api_id = module.rest_api.api_id
|
|
6990
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
6991
|
-
path_part = "{proxy+}"
|
|
7850
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
7851
|
+
tags = var.tags
|
|
6992
7852
|
}
|
|
6993
7853
|
|
|
6994
|
-
# Lambda integration for REST API
|
|
6995
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6996
|
-
rest_api_id = module.rest_api.api_id
|
|
6997
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
6998
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
6999
|
-
|
|
7000
|
-
integration_http_method = "POST"
|
|
7001
|
-
type = "AWS_PROXY"
|
|
7002
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
7003
|
-
response_transfer_mode = "STREAM"
|
|
7004
7854
|
|
|
7005
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
7006
|
-
}
|
|
7007
7855
|
|
|
7008
|
-
# Method
|
|
7009
|
-
resource "aws_api_gateway_method" "
|
|
7856
|
+
# Method per operation
|
|
7857
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
7010
7858
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
7859
|
+
for_each = local.operations
|
|
7860
|
+
|
|
7011
7861
|
rest_api_id = module.rest_api.api_id
|
|
7012
|
-
resource_id =
|
|
7013
|
-
http_method =
|
|
7862
|
+
resource_id = local.operation_resource_id[each.key]
|
|
7863
|
+
http_method = upper(each.value.method)
|
|
7014
7864
|
|
|
7015
7865
|
authorization = "AWS_IAM"
|
|
7016
7866
|
|
|
7867
|
+
# Path parameters must be declared on the method
|
|
7017
7868
|
request_parameters = {
|
|
7018
|
-
|
|
7869
|
+
for segment in local.operation_segments[each.key] :
|
|
7870
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
7871
|
+
if startswith(segment, "{")
|
|
7019
7872
|
}
|
|
7020
7873
|
|
|
7021
7874
|
depends_on = []
|
|
7022
7875
|
}
|
|
7023
7876
|
|
|
7024
|
-
#
|
|
7877
|
+
# Lambda integration per operation
|
|
7878
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
7879
|
+
for_each = local.operations
|
|
7880
|
+
|
|
7881
|
+
rest_api_id = module.rest_api.api_id
|
|
7882
|
+
resource_id = local.operation_resource_id[each.key]
|
|
7883
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
7884
|
+
|
|
7885
|
+
integration_http_method = "POST"
|
|
7886
|
+
type = "AWS_PROXY"
|
|
7887
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
7888
|
+
response_transfer_mode = "STREAM"
|
|
7889
|
+
|
|
7890
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7893
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
7025
7894
|
resource "aws_api_gateway_method" "options_method" {
|
|
7026
7895
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
7027
7896
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
7897
|
+
for_each = local.resource_ids
|
|
7898
|
+
|
|
7028
7899
|
rest_api_id = module.rest_api.api_id
|
|
7029
|
-
resource_id =
|
|
7900
|
+
resource_id = each.value
|
|
7030
7901
|
http_method = "OPTIONS"
|
|
7031
7902
|
authorization = "NONE"
|
|
7032
7903
|
}
|
|
7033
7904
|
|
|
7034
|
-
# CORS integration for OPTIONS
|
|
7905
|
+
# CORS integration for OPTIONS methods
|
|
7035
7906
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
7907
|
+
for_each = local.resource_ids
|
|
7908
|
+
|
|
7036
7909
|
rest_api_id = module.rest_api.api_id
|
|
7037
|
-
resource_id =
|
|
7038
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7910
|
+
resource_id = each.value
|
|
7911
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7039
7912
|
|
|
7040
7913
|
type = "MOCK"
|
|
7041
7914
|
request_templates = {
|
|
@@ -7043,11 +7916,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
7043
7916
|
}
|
|
7044
7917
|
}
|
|
7045
7918
|
|
|
7046
|
-
# OPTIONS method
|
|
7919
|
+
# OPTIONS method responses
|
|
7047
7920
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
7921
|
+
for_each = local.resource_ids
|
|
7922
|
+
|
|
7048
7923
|
rest_api_id = module.rest_api.api_id
|
|
7049
|
-
resource_id =
|
|
7050
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7924
|
+
resource_id = each.value
|
|
7925
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7051
7926
|
status_code = "204"
|
|
7052
7927
|
|
|
7053
7928
|
response_parameters = {
|
|
@@ -7057,18 +7932,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
7057
7932
|
}
|
|
7058
7933
|
}
|
|
7059
7934
|
|
|
7060
|
-
# OPTIONS integration
|
|
7935
|
+
# OPTIONS integration responses
|
|
7061
7936
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
7937
|
+
for_each = local.resource_ids
|
|
7938
|
+
|
|
7062
7939
|
rest_api_id = module.rest_api.api_id
|
|
7063
|
-
resource_id =
|
|
7064
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7065
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
7940
|
+
resource_id = each.value
|
|
7941
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7942
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
7066
7943
|
|
|
7067
7944
|
response_parameters = {
|
|
7068
7945
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
7069
7946
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
7070
7947
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
7071
7948
|
}
|
|
7949
|
+
|
|
7950
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
7072
7951
|
}
|
|
7073
7952
|
|
|
7074
7953
|
# API Gateway deployment
|
|
@@ -7076,13 +7955,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
7076
7955
|
rest_api_id = module.rest_api.api_id
|
|
7077
7956
|
|
|
7078
7957
|
triggers = {
|
|
7079
|
-
redeployment = sha1(jsonencode(
|
|
7080
|
-
aws_api_gateway_resource.
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7958
|
+
redeployment = sha1(jsonencode(concat(
|
|
7959
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
7960
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
7961
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
7962
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
7963
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
7964
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
7965
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
7966
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
7967
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
7968
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
7969
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
7970
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
7971
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
7972
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
7973
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
7974
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
7975
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
7976
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
7977
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
7978
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
7979
|
+
)))
|
|
7086
7980
|
}
|
|
7087
7981
|
|
|
7088
7982
|
lifecycle {
|
|
@@ -7090,7 +7984,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
7090
7984
|
}
|
|
7091
7985
|
|
|
7092
7986
|
depends_on = [
|
|
7093
|
-
aws_api_gateway_method.
|
|
7987
|
+
aws_api_gateway_method.operation_methods,
|
|
7094
7988
|
aws_api_gateway_integration.lambda_integration,
|
|
7095
7989
|
aws_api_gateway_method.options_method,
|
|
7096
7990
|
aws_api_gateway_integration.options_integration,
|
|
@@ -7229,11 +8123,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
7229
8123
|
|
|
7230
8124
|
# Lambda permission for API Gateway to invoke the function
|
|
7231
8125
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
8126
|
+
for_each = local.operations
|
|
8127
|
+
|
|
7232
8128
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
7233
8129
|
action = "lambda:InvokeFunction"
|
|
7234
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
8130
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
7235
8131
|
principal = "apigateway.amazonaws.com"
|
|
7236
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
8132
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
7237
8133
|
|
|
7238
8134
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
7239
8135
|
}
|
|
@@ -7245,7 +8141,7 @@ module "add_url_to_runtime_config" {
|
|
|
7245
8141
|
|
|
7246
8142
|
namespace = "connection"
|
|
7247
8143
|
key = "apis"
|
|
7248
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
8144
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
7249
8145
|
|
|
7250
8146
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
7251
8147
|
}
|
|
@@ -7298,83 +8194,83 @@ output "stage_id" {
|
|
|
7298
8194
|
value = aws_api_gateway_stage.api_stage.id
|
|
7299
8195
|
}
|
|
7300
8196
|
|
|
7301
|
-
# Lambda Function Outputs
|
|
7302
|
-
output "
|
|
7303
|
-
description = "
|
|
7304
|
-
value =
|
|
8197
|
+
# Lambda Function Outputs, keyed by operation name
|
|
8198
|
+
output "operations" {
|
|
8199
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
8200
|
+
value = keys(local.operations)
|
|
7305
8201
|
}
|
|
7306
8202
|
|
|
7307
|
-
output "
|
|
7308
|
-
description = "
|
|
7309
|
-
value = aws_lambda_function.api_lambda.
|
|
8203
|
+
output "lambda_function_names" {
|
|
8204
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
8205
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
7310
8206
|
}
|
|
7311
8207
|
|
|
7312
|
-
output "
|
|
7313
|
-
description = "
|
|
7314
|
-
value = aws_lambda_function.api_lambda.
|
|
8208
|
+
output "lambda_function_arns" {
|
|
8209
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
8210
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
7315
8211
|
}
|
|
7316
8212
|
|
|
7317
|
-
output "
|
|
7318
|
-
description = "
|
|
7319
|
-
value = aws_lambda_function.api_lambda.
|
|
8213
|
+
output "lambda_invoke_arns" {
|
|
8214
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
8215
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
7320
8216
|
}
|
|
7321
8217
|
|
|
7322
|
-
output "
|
|
7323
|
-
description = "
|
|
7324
|
-
value = aws_lambda_function.api_lambda.
|
|
8218
|
+
output "lambda_qualified_arns" {
|
|
8219
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
8220
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
7325
8221
|
}
|
|
7326
8222
|
|
|
7327
|
-
output "
|
|
7328
|
-
description = "
|
|
7329
|
-
value = aws_lambda_function.api_lambda.
|
|
8223
|
+
output "lambda_versions" {
|
|
8224
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
8225
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
7330
8226
|
}
|
|
7331
8227
|
|
|
7332
|
-
output "
|
|
7333
|
-
description = "
|
|
7334
|
-
value =
|
|
8228
|
+
output "lambda_source_code_hash" {
|
|
8229
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
8230
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
7335
8231
|
}
|
|
7336
8232
|
|
|
7337
|
-
# IAM Role Outputs
|
|
7338
|
-
output "
|
|
7339
|
-
description = "ARN of
|
|
7340
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
8233
|
+
# IAM Role Outputs, keyed by operation name
|
|
8234
|
+
output "lambda_execution_role_arns" {
|
|
8235
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
8236
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
7341
8237
|
}
|
|
7342
8238
|
|
|
7343
|
-
output "
|
|
7344
|
-
description = "Name of
|
|
7345
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
8239
|
+
output "lambda_execution_role_names" {
|
|
8240
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
8241
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
7346
8242
|
}
|
|
7347
8243
|
|
|
7348
8244
|
output "security_group_id" {
|
|
7349
|
-
description = "Security group ID
|
|
8245
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
7350
8246
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
7351
8247
|
}
|
|
7352
8248
|
|
|
7353
|
-
# Integration Outputs
|
|
7354
|
-
output "
|
|
7355
|
-
description = "ID of
|
|
7356
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
8249
|
+
# Integration Outputs, keyed by operation name
|
|
8250
|
+
output "integration_ids" {
|
|
8251
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
8252
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
7357
8253
|
}
|
|
7358
8254
|
|
|
7359
|
-
output "
|
|
7360
|
-
description = "ID of
|
|
7361
|
-
value =
|
|
8255
|
+
output "method_ids" {
|
|
8256
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
8257
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
7362
8258
|
}
|
|
7363
8259
|
|
|
7364
|
-
output "
|
|
7365
|
-
description = "ID of
|
|
7366
|
-
value =
|
|
8260
|
+
output "resource_ids" {
|
|
8261
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
8262
|
+
value = local.resource_ids
|
|
7367
8263
|
}
|
|
7368
8264
|
|
|
7369
|
-
# CloudWatch Log Groups
|
|
7370
|
-
output "
|
|
7371
|
-
description = "Name of
|
|
7372
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
8265
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
8266
|
+
output "lambda_log_group_names" {
|
|
8267
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
8268
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
7373
8269
|
}
|
|
7374
8270
|
|
|
7375
|
-
output "
|
|
7376
|
-
description = "ARN of
|
|
7377
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
8271
|
+
output "lambda_log_group_arns" {
|
|
8272
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
8273
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
7378
8274
|
}
|
|
7379
8275
|
",
|
|
7380
8276
|
}
|
|
@@ -7584,22 +8480,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
7584
8480
|
}
|
|
7585
8481
|
}
|
|
7586
8482
|
|
|
8483
|
+
resource "random_id" "waf_log_suffix" {
|
|
8484
|
+
count = var.enable_waf ? 1 : 0
|
|
8485
|
+
byte_length = 4
|
|
8486
|
+
}
|
|
8487
|
+
|
|
8488
|
+
locals {
|
|
8489
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
8490
|
+
}
|
|
8491
|
+
|
|
8492
|
+
# KMS key for encrypting WAF request logs at rest
|
|
8493
|
+
resource "aws_kms_key" "waf_logs" {
|
|
8494
|
+
count = var.enable_waf ? 1 : 0
|
|
8495
|
+
|
|
8496
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
8497
|
+
deletion_window_in_days = 7
|
|
8498
|
+
enable_key_rotation = true
|
|
8499
|
+
|
|
8500
|
+
policy = jsonencode({
|
|
8501
|
+
Version = "2012-10-17"
|
|
8502
|
+
Statement = [
|
|
8503
|
+
{
|
|
8504
|
+
Sid = "Enable IAM User Permissions"
|
|
8505
|
+
Effect = "Allow"
|
|
8506
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
8507
|
+
Action = "kms:*"
|
|
8508
|
+
Resource = "*"
|
|
8509
|
+
},
|
|
8510
|
+
{
|
|
8511
|
+
Sid = "Allow CloudWatch Logs"
|
|
8512
|
+
Effect = "Allow"
|
|
8513
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
8514
|
+
Action = [
|
|
8515
|
+
"kms:Encrypt",
|
|
8516
|
+
"kms:Decrypt",
|
|
8517
|
+
"kms:ReEncrypt*",
|
|
8518
|
+
"kms:GenerateDataKey*",
|
|
8519
|
+
"kms:DescribeKey"
|
|
8520
|
+
]
|
|
8521
|
+
Resource = "*"
|
|
8522
|
+
Condition = {
|
|
8523
|
+
ArnEquals = {
|
|
8524
|
+
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:log-group:\${local.waf_log_group_name}"
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
]
|
|
8529
|
+
})
|
|
8530
|
+
|
|
8531
|
+
tags = var.tags
|
|
8532
|
+
}
|
|
8533
|
+
|
|
8534
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
8535
|
+
count = var.enable_waf ? 1 : 0
|
|
8536
|
+
|
|
8537
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
8538
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
8539
|
+
}
|
|
8540
|
+
|
|
7587
8541
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
7588
8542
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
7589
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
7590
8543
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
7591
8544
|
count = var.enable_waf ? 1 : 0
|
|
7592
8545
|
|
|
7593
|
-
name =
|
|
8546
|
+
name = local.waf_log_group_name
|
|
7594
8547
|
retention_in_days = 30
|
|
8548
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
7595
8549
|
tags = var.tags
|
|
7596
8550
|
}
|
|
7597
8551
|
|
|
7598
|
-
resource "random_id" "waf_log_suffix" {
|
|
7599
|
-
count = var.enable_waf ? 1 : 0
|
|
7600
|
-
byte_length = 4
|
|
7601
|
-
}
|
|
7602
|
-
|
|
7603
8552
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
7604
8553
|
count = var.enable_waf ? 1 : 0
|
|
7605
8554
|
|
|
@@ -7645,10 +8594,18 @@ output "waf_web_acl_arn" {
|
|
|
7645
8594
|
required_version = ">= 1.0"
|
|
7646
8595
|
|
|
7647
8596
|
required_providers {
|
|
8597
|
+
archive = {
|
|
8598
|
+
source = "hashicorp/archive"
|
|
8599
|
+
version = "2.8.0"
|
|
8600
|
+
}
|
|
7648
8601
|
aws = {
|
|
7649
8602
|
source = "hashicorp/aws"
|
|
7650
8603
|
version = "6.61.0"
|
|
7651
8604
|
}
|
|
8605
|
+
random = {
|
|
8606
|
+
source = "hashicorp/random"
|
|
8607
|
+
version = "3.9.0"
|
|
8608
|
+
}
|
|
7652
8609
|
}
|
|
7653
8610
|
}
|
|
7654
8611
|
|
|
@@ -7754,6 +8711,235 @@ resource "random_string" "suffix" {
|
|
|
7754
8711
|
upper = false
|
|
7755
8712
|
}
|
|
7756
8713
|
|
|
8714
|
+
locals {
|
|
8715
|
+
# Generated at build time from the API definition
|
|
8716
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
8717
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
8718
|
+
|
|
8719
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
8720
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
8721
|
+
|
|
8722
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
8723
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
8724
|
+
}
|
|
8725
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
8726
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
8727
|
+
}
|
|
8728
|
+
|
|
8729
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
8730
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
8731
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
8732
|
+
|
|
8733
|
+
path_nodes = merge([
|
|
8734
|
+
for op, segments in local.operation_segments : {
|
|
8735
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
8736
|
+
depth = i
|
|
8737
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
8738
|
+
part = segment
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
]...)
|
|
8742
|
+
|
|
8743
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
8744
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
8745
|
+
max_path_depth = 16
|
|
8746
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
8747
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
8748
|
+
} }
|
|
8749
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
8750
|
+
|
|
8751
|
+
resource_ids = merge(
|
|
8752
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
8753
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
8754
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
8755
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
8756
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
8757
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
8758
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
8759
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
8760
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
8761
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
8762
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
8763
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
8764
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
8765
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
8766
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
8767
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
8768
|
+
)
|
|
8769
|
+
|
|
8770
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
8771
|
+
local.resource_ids[join("/", segments)]
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
8775
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8779
|
+
resource "terraform_data" "operations_metadata" {
|
|
8780
|
+
# Fails the plan when the operations metadata has not been generated
|
|
8781
|
+
input = local.operations_file
|
|
8782
|
+
|
|
8783
|
+
lifecycle {
|
|
8784
|
+
precondition {
|
|
8785
|
+
condition = fileexists(local.operations_file)
|
|
8786
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
8787
|
+
}
|
|
8788
|
+
precondition {
|
|
8789
|
+
condition = length(local.operations) > 0
|
|
8790
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
8791
|
+
}
|
|
8792
|
+
precondition {
|
|
8793
|
+
condition = length(local.paths_too_deep) == 0
|
|
8794
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
|
|
8799
|
+
# API Gateway resources at path depth 0
|
|
8800
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
8801
|
+
for_each = local.path_nodes_by_depth[0]
|
|
8802
|
+
|
|
8803
|
+
rest_api_id = module.rest_api.api_id
|
|
8804
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
8805
|
+
path_part = each.value.part
|
|
8806
|
+
}
|
|
8807
|
+
|
|
8808
|
+
# API Gateway resources at path depth 1
|
|
8809
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
8810
|
+
for_each = local.path_nodes_by_depth[1]
|
|
8811
|
+
|
|
8812
|
+
rest_api_id = module.rest_api.api_id
|
|
8813
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
8814
|
+
path_part = each.value.part
|
|
8815
|
+
}
|
|
8816
|
+
|
|
8817
|
+
# API Gateway resources at path depth 2
|
|
8818
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
8819
|
+
for_each = local.path_nodes_by_depth[2]
|
|
8820
|
+
|
|
8821
|
+
rest_api_id = module.rest_api.api_id
|
|
8822
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
8823
|
+
path_part = each.value.part
|
|
8824
|
+
}
|
|
8825
|
+
|
|
8826
|
+
# API Gateway resources at path depth 3
|
|
8827
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
8828
|
+
for_each = local.path_nodes_by_depth[3]
|
|
8829
|
+
|
|
8830
|
+
rest_api_id = module.rest_api.api_id
|
|
8831
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
8832
|
+
path_part = each.value.part
|
|
8833
|
+
}
|
|
8834
|
+
|
|
8835
|
+
# API Gateway resources at path depth 4
|
|
8836
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
8837
|
+
for_each = local.path_nodes_by_depth[4]
|
|
8838
|
+
|
|
8839
|
+
rest_api_id = module.rest_api.api_id
|
|
8840
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
8841
|
+
path_part = each.value.part
|
|
8842
|
+
}
|
|
8843
|
+
|
|
8844
|
+
# API Gateway resources at path depth 5
|
|
8845
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
8846
|
+
for_each = local.path_nodes_by_depth[5]
|
|
8847
|
+
|
|
8848
|
+
rest_api_id = module.rest_api.api_id
|
|
8849
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
8850
|
+
path_part = each.value.part
|
|
8851
|
+
}
|
|
8852
|
+
|
|
8853
|
+
# API Gateway resources at path depth 6
|
|
8854
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
8855
|
+
for_each = local.path_nodes_by_depth[6]
|
|
8856
|
+
|
|
8857
|
+
rest_api_id = module.rest_api.api_id
|
|
8858
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
8859
|
+
path_part = each.value.part
|
|
8860
|
+
}
|
|
8861
|
+
|
|
8862
|
+
# API Gateway resources at path depth 7
|
|
8863
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
8864
|
+
for_each = local.path_nodes_by_depth[7]
|
|
8865
|
+
|
|
8866
|
+
rest_api_id = module.rest_api.api_id
|
|
8867
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
8868
|
+
path_part = each.value.part
|
|
8869
|
+
}
|
|
8870
|
+
|
|
8871
|
+
# API Gateway resources at path depth 8
|
|
8872
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
8873
|
+
for_each = local.path_nodes_by_depth[8]
|
|
8874
|
+
|
|
8875
|
+
rest_api_id = module.rest_api.api_id
|
|
8876
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
8877
|
+
path_part = each.value.part
|
|
8878
|
+
}
|
|
8879
|
+
|
|
8880
|
+
# API Gateway resources at path depth 9
|
|
8881
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
8882
|
+
for_each = local.path_nodes_by_depth[9]
|
|
8883
|
+
|
|
8884
|
+
rest_api_id = module.rest_api.api_id
|
|
8885
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
8886
|
+
path_part = each.value.part
|
|
8887
|
+
}
|
|
8888
|
+
|
|
8889
|
+
# API Gateway resources at path depth 10
|
|
8890
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
8891
|
+
for_each = local.path_nodes_by_depth[10]
|
|
8892
|
+
|
|
8893
|
+
rest_api_id = module.rest_api.api_id
|
|
8894
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
8895
|
+
path_part = each.value.part
|
|
8896
|
+
}
|
|
8897
|
+
|
|
8898
|
+
# API Gateway resources at path depth 11
|
|
8899
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
8900
|
+
for_each = local.path_nodes_by_depth[11]
|
|
8901
|
+
|
|
8902
|
+
rest_api_id = module.rest_api.api_id
|
|
8903
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
8904
|
+
path_part = each.value.part
|
|
8905
|
+
}
|
|
8906
|
+
|
|
8907
|
+
# API Gateway resources at path depth 12
|
|
8908
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
8909
|
+
for_each = local.path_nodes_by_depth[12]
|
|
8910
|
+
|
|
8911
|
+
rest_api_id = module.rest_api.api_id
|
|
8912
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
8913
|
+
path_part = each.value.part
|
|
8914
|
+
}
|
|
8915
|
+
|
|
8916
|
+
# API Gateway resources at path depth 13
|
|
8917
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
8918
|
+
for_each = local.path_nodes_by_depth[13]
|
|
8919
|
+
|
|
8920
|
+
rest_api_id = module.rest_api.api_id
|
|
8921
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
8922
|
+
path_part = each.value.part
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8925
|
+
# API Gateway resources at path depth 14
|
|
8926
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
8927
|
+
for_each = local.path_nodes_by_depth[14]
|
|
8928
|
+
|
|
8929
|
+
rest_api_id = module.rest_api.api_id
|
|
8930
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
8931
|
+
path_part = each.value.part
|
|
8932
|
+
}
|
|
8933
|
+
|
|
8934
|
+
# API Gateway resources at path depth 15
|
|
8935
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
8936
|
+
for_each = local.path_nodes_by_depth[15]
|
|
8937
|
+
|
|
8938
|
+
rest_api_id = module.rest_api.api_id
|
|
8939
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
8940
|
+
path_part = each.value.part
|
|
8941
|
+
}
|
|
8942
|
+
|
|
7757
8943
|
# Resources
|
|
7758
8944
|
|
|
7759
8945
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -7828,8 +9014,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
7828
9014
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
7829
9015
|
}
|
|
7830
9016
|
|
|
7831
|
-
# Lambda function
|
|
7832
9017
|
resource "aws_lambda_function" "api_lambda" {
|
|
9018
|
+
# One lambda function per operation, each serving just that operation
|
|
9019
|
+
for_each = local.operations
|
|
9020
|
+
|
|
7833
9021
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
7834
9022
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
7835
9023
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -7838,8 +9026,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
7838
9026
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
7839
9027
|
s3_key = aws_s3_object.lambda_zip.key
|
|
7840
9028
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
7841
|
-
function_name =
|
|
7842
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
9029
|
+
function_name = local.function_name[each.key]
|
|
9030
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
7843
9031
|
handler = "index.handler"
|
|
7844
9032
|
runtime = "nodejs22.x"
|
|
7845
9033
|
timeout = 30
|
|
@@ -7875,7 +9063,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
7875
9063
|
|
|
7876
9064
|
# IAM role for Lambda execution
|
|
7877
9065
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
7878
|
-
|
|
9066
|
+
for_each = local.operations
|
|
9067
|
+
|
|
9068
|
+
name = local.role_name[each.key]
|
|
7879
9069
|
|
|
7880
9070
|
assume_role_policy = jsonencode({
|
|
7881
9071
|
Version = "2012-10-17"
|
|
@@ -7895,32 +9085,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
7895
9085
|
|
|
7896
9086
|
# Attach basic execution policy to Lambda role
|
|
7897
9087
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
9088
|
+
for_each = local.operations
|
|
9089
|
+
|
|
7898
9090
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
7899
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9091
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7900
9092
|
}
|
|
7901
9093
|
|
|
7902
9094
|
# Attach X-Ray tracing policy to Lambda role
|
|
7903
9095
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
9096
|
+
for_each = local.operations
|
|
9097
|
+
|
|
7904
9098
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
7905
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9099
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7906
9100
|
}
|
|
7907
9101
|
|
|
7908
9102
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
7909
9103
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
7910
|
-
|
|
9104
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
9105
|
+
|
|
7911
9106
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
7912
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9107
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7913
9108
|
}
|
|
7914
9109
|
|
|
7915
9110
|
# Additional IAM policies for Lambda (if provided)
|
|
7916
9111
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
9112
|
+
for_each = local.operations
|
|
9113
|
+
|
|
9114
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
9115
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
7920
9116
|
|
|
7921
9117
|
policy = jsonencode({
|
|
7922
9118
|
Version = "2012-10-17"
|
|
7923
|
-
Statement = local.
|
|
9119
|
+
Statement = local.lambda_policy_document_statements
|
|
7924
9120
|
})
|
|
7925
9121
|
}
|
|
7926
9122
|
|
|
@@ -7936,6 +9132,15 @@ locals {
|
|
|
7936
9132
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
7937
9133
|
}
|
|
7938
9134
|
] : [], var.additional_iam_policy_statements)
|
|
9135
|
+
|
|
9136
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
9137
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
9138
|
+
{
|
|
9139
|
+
Effect = "Deny"
|
|
9140
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
9141
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
9142
|
+
}
|
|
9143
|
+
]
|
|
7939
9144
|
}
|
|
7940
9145
|
|
|
7941
9146
|
# CloudWatch Log Group for Lambda
|
|
@@ -7943,8 +9148,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
7943
9148
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
7944
9149
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
7945
9150
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
7946
|
-
|
|
7947
|
-
|
|
9151
|
+
for_each = local.operations
|
|
9152
|
+
|
|
9153
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
9154
|
+
tags = var.tags
|
|
7948
9155
|
}
|
|
7949
9156
|
|
|
7950
9157
|
|
|
@@ -8041,59 +9248,63 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
8041
9248
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
8042
9249
|
}
|
|
8043
9250
|
|
|
8044
|
-
#
|
|
8045
|
-
resource "
|
|
8046
|
-
rest_api_id = module.rest_api.api_id
|
|
8047
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
8048
|
-
path_part = "{proxy+}"
|
|
8049
|
-
}
|
|
8050
|
-
|
|
8051
|
-
# Lambda integration for REST API
|
|
8052
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
8053
|
-
rest_api_id = module.rest_api.api_id
|
|
8054
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
8055
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
8056
|
-
|
|
8057
|
-
integration_http_method = "POST"
|
|
8058
|
-
type = "AWS_PROXY"
|
|
8059
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
8060
|
-
response_transfer_mode = "STREAM"
|
|
8061
|
-
|
|
8062
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
8063
|
-
}
|
|
8064
|
-
|
|
8065
|
-
# Method for proxy integration
|
|
8066
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
9251
|
+
# Method per operation
|
|
9252
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
8067
9253
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
9254
|
+
for_each = local.operations
|
|
9255
|
+
|
|
8068
9256
|
rest_api_id = module.rest_api.api_id
|
|
8069
|
-
resource_id =
|
|
8070
|
-
http_method =
|
|
9257
|
+
resource_id = local.operation_resource_id[each.key]
|
|
9258
|
+
http_method = upper(each.value.method)
|
|
8071
9259
|
|
|
8072
9260
|
authorization = "CUSTOM"
|
|
8073
9261
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
8074
9262
|
|
|
9263
|
+
# Path parameters must be declared on the method
|
|
8075
9264
|
request_parameters = {
|
|
8076
|
-
|
|
9265
|
+
for segment in local.operation_segments[each.key] :
|
|
9266
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
9267
|
+
if startswith(segment, "{")
|
|
8077
9268
|
}
|
|
8078
9269
|
|
|
8079
9270
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
8080
9271
|
}
|
|
8081
9272
|
|
|
8082
|
-
#
|
|
9273
|
+
# Lambda integration per operation
|
|
9274
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
9275
|
+
for_each = local.operations
|
|
9276
|
+
|
|
9277
|
+
rest_api_id = module.rest_api.api_id
|
|
9278
|
+
resource_id = local.operation_resource_id[each.key]
|
|
9279
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
9280
|
+
|
|
9281
|
+
integration_http_method = "POST"
|
|
9282
|
+
type = "AWS_PROXY"
|
|
9283
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
9284
|
+
response_transfer_mode = "STREAM"
|
|
9285
|
+
|
|
9286
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
9287
|
+
}
|
|
9288
|
+
|
|
9289
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
8083
9290
|
resource "aws_api_gateway_method" "options_method" {
|
|
8084
9291
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
8085
9292
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
9293
|
+
for_each = local.resource_ids
|
|
9294
|
+
|
|
8086
9295
|
rest_api_id = module.rest_api.api_id
|
|
8087
|
-
resource_id =
|
|
9296
|
+
resource_id = each.value
|
|
8088
9297
|
http_method = "OPTIONS"
|
|
8089
9298
|
authorization = "NONE"
|
|
8090
9299
|
}
|
|
8091
9300
|
|
|
8092
|
-
# CORS integration for OPTIONS
|
|
9301
|
+
# CORS integration for OPTIONS methods
|
|
8093
9302
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
9303
|
+
for_each = local.resource_ids
|
|
9304
|
+
|
|
8094
9305
|
rest_api_id = module.rest_api.api_id
|
|
8095
|
-
resource_id =
|
|
8096
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
9306
|
+
resource_id = each.value
|
|
9307
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
8097
9308
|
|
|
8098
9309
|
type = "MOCK"
|
|
8099
9310
|
request_templates = {
|
|
@@ -8101,11 +9312,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
8101
9312
|
}
|
|
8102
9313
|
}
|
|
8103
9314
|
|
|
8104
|
-
# OPTIONS method
|
|
9315
|
+
# OPTIONS method responses
|
|
8105
9316
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
9317
|
+
for_each = local.resource_ids
|
|
9318
|
+
|
|
8106
9319
|
rest_api_id = module.rest_api.api_id
|
|
8107
|
-
resource_id =
|
|
8108
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
9320
|
+
resource_id = each.value
|
|
9321
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
8109
9322
|
status_code = "204"
|
|
8110
9323
|
|
|
8111
9324
|
response_parameters = {
|
|
@@ -8115,18 +9328,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
8115
9328
|
}
|
|
8116
9329
|
}
|
|
8117
9330
|
|
|
8118
|
-
# OPTIONS integration
|
|
9331
|
+
# OPTIONS integration responses
|
|
8119
9332
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
9333
|
+
for_each = local.resource_ids
|
|
9334
|
+
|
|
8120
9335
|
rest_api_id = module.rest_api.api_id
|
|
8121
|
-
resource_id =
|
|
8122
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
8123
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
9336
|
+
resource_id = each.value
|
|
9337
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
9338
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
8124
9339
|
|
|
8125
9340
|
response_parameters = {
|
|
8126
9341
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
8127
9342
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
8128
9343
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
8129
9344
|
}
|
|
9345
|
+
|
|
9346
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
8130
9347
|
}
|
|
8131
9348
|
|
|
8132
9349
|
# API Gateway deployment
|
|
@@ -8134,13 +9351,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
8134
9351
|
rest_api_id = module.rest_api.api_id
|
|
8135
9352
|
|
|
8136
9353
|
triggers = {
|
|
8137
|
-
redeployment = sha1(jsonencode(
|
|
8138
|
-
aws_api_gateway_resource.
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
9354
|
+
redeployment = sha1(jsonencode(concat(
|
|
9355
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
9356
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
9357
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
9358
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
9359
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
9360
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
9361
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
9362
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
9363
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
9364
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
9365
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
9366
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
9367
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
9368
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
9369
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
9370
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
9371
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
9372
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
9373
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
9374
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
9375
|
+
)))
|
|
8144
9376
|
}
|
|
8145
9377
|
|
|
8146
9378
|
lifecycle {
|
|
@@ -8148,7 +9380,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
8148
9380
|
}
|
|
8149
9381
|
|
|
8150
9382
|
depends_on = [
|
|
8151
|
-
aws_api_gateway_method.
|
|
9383
|
+
aws_api_gateway_method.operation_methods,
|
|
8152
9384
|
aws_api_gateway_integration.lambda_integration,
|
|
8153
9385
|
aws_api_gateway_method.options_method,
|
|
8154
9386
|
aws_api_gateway_integration.options_integration,
|
|
@@ -8277,11 +9509,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
8277
9509
|
|
|
8278
9510
|
# Lambda permission for API Gateway to invoke the function
|
|
8279
9511
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
9512
|
+
for_each = local.operations
|
|
9513
|
+
|
|
8280
9514
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
8281
9515
|
action = "lambda:InvokeFunction"
|
|
8282
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
9516
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
8283
9517
|
principal = "apigateway.amazonaws.com"
|
|
8284
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
9518
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
8285
9519
|
|
|
8286
9520
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
8287
9521
|
}
|
|
@@ -8293,7 +9527,7 @@ module "add_url_to_runtime_config" {
|
|
|
8293
9527
|
|
|
8294
9528
|
namespace = "connection"
|
|
8295
9529
|
key = "apis"
|
|
8296
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
9530
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
8297
9531
|
|
|
8298
9532
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
8299
9533
|
}
|
|
@@ -8346,83 +9580,83 @@ output "stage_id" {
|
|
|
8346
9580
|
value = aws_api_gateway_stage.api_stage.id
|
|
8347
9581
|
}
|
|
8348
9582
|
|
|
8349
|
-
# Lambda Function Outputs
|
|
8350
|
-
output "
|
|
8351
|
-
description = "
|
|
8352
|
-
value =
|
|
9583
|
+
# Lambda Function Outputs, keyed by operation name
|
|
9584
|
+
output "operations" {
|
|
9585
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
9586
|
+
value = keys(local.operations)
|
|
8353
9587
|
}
|
|
8354
9588
|
|
|
8355
|
-
output "
|
|
8356
|
-
description = "
|
|
8357
|
-
value = aws_lambda_function.api_lambda.
|
|
9589
|
+
output "lambda_function_names" {
|
|
9590
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
9591
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
8358
9592
|
}
|
|
8359
9593
|
|
|
8360
|
-
output "
|
|
8361
|
-
description = "
|
|
8362
|
-
value = aws_lambda_function.api_lambda.
|
|
9594
|
+
output "lambda_function_arns" {
|
|
9595
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
9596
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
8363
9597
|
}
|
|
8364
9598
|
|
|
8365
|
-
output "
|
|
8366
|
-
description = "
|
|
8367
|
-
value = aws_lambda_function.api_lambda.
|
|
9599
|
+
output "lambda_invoke_arns" {
|
|
9600
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
9601
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
8368
9602
|
}
|
|
8369
9603
|
|
|
8370
|
-
output "
|
|
8371
|
-
description = "
|
|
8372
|
-
value = aws_lambda_function.api_lambda.
|
|
9604
|
+
output "lambda_qualified_arns" {
|
|
9605
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
9606
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
8373
9607
|
}
|
|
8374
9608
|
|
|
8375
|
-
output "
|
|
8376
|
-
description = "
|
|
8377
|
-
value = aws_lambda_function.api_lambda.
|
|
9609
|
+
output "lambda_versions" {
|
|
9610
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
9611
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
8378
9612
|
}
|
|
8379
9613
|
|
|
8380
|
-
output "
|
|
8381
|
-
description = "
|
|
8382
|
-
value =
|
|
9614
|
+
output "lambda_source_code_hash" {
|
|
9615
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
9616
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
8383
9617
|
}
|
|
8384
9618
|
|
|
8385
|
-
# IAM Role Outputs
|
|
8386
|
-
output "
|
|
8387
|
-
description = "ARN of
|
|
8388
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
9619
|
+
# IAM Role Outputs, keyed by operation name
|
|
9620
|
+
output "lambda_execution_role_arns" {
|
|
9621
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
9622
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
8389
9623
|
}
|
|
8390
9624
|
|
|
8391
|
-
output "
|
|
8392
|
-
description = "Name of
|
|
8393
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
9625
|
+
output "lambda_execution_role_names" {
|
|
9626
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
9627
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
8394
9628
|
}
|
|
8395
9629
|
|
|
8396
9630
|
output "security_group_id" {
|
|
8397
|
-
description = "Security group ID
|
|
9631
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
8398
9632
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
8399
9633
|
}
|
|
8400
9634
|
|
|
8401
|
-
# Integration Outputs
|
|
8402
|
-
output "
|
|
8403
|
-
description = "ID of
|
|
8404
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
9635
|
+
# Integration Outputs, keyed by operation name
|
|
9636
|
+
output "integration_ids" {
|
|
9637
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
9638
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
8405
9639
|
}
|
|
8406
9640
|
|
|
8407
|
-
output "
|
|
8408
|
-
description = "ID of
|
|
8409
|
-
value =
|
|
9641
|
+
output "method_ids" {
|
|
9642
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
9643
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
8410
9644
|
}
|
|
8411
9645
|
|
|
8412
|
-
output "
|
|
8413
|
-
description = "ID of
|
|
8414
|
-
value =
|
|
9646
|
+
output "resource_ids" {
|
|
9647
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
9648
|
+
value = local.resource_ids
|
|
8415
9649
|
}
|
|
8416
9650
|
|
|
8417
|
-
# CloudWatch Log Groups
|
|
8418
|
-
output "
|
|
8419
|
-
description = "Name of
|
|
8420
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
9651
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
9652
|
+
output "lambda_log_group_names" {
|
|
9653
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
9654
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
8421
9655
|
}
|
|
8422
9656
|
|
|
8423
|
-
output "
|
|
8424
|
-
description = "ARN of
|
|
8425
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
9657
|
+
output "lambda_log_group_arns" {
|
|
9658
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
9659
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
8426
9660
|
}
|
|
8427
9661
|
",
|
|
8428
9662
|
}
|