@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
|
@@ -606,22 +606,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
resource "random_id" "waf_log_suffix" {
|
|
610
|
+
count = var.enable_waf ? 1 : 0
|
|
611
|
+
byte_length = 4
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
locals {
|
|
615
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
# KMS key for encrypting WAF request logs at rest
|
|
619
|
+
resource "aws_kms_key" "waf_logs" {
|
|
620
|
+
count = var.enable_waf ? 1 : 0
|
|
621
|
+
|
|
622
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
623
|
+
deletion_window_in_days = 7
|
|
624
|
+
enable_key_rotation = true
|
|
625
|
+
|
|
626
|
+
policy = jsonencode({
|
|
627
|
+
Version = "2012-10-17"
|
|
628
|
+
Statement = [
|
|
629
|
+
{
|
|
630
|
+
Sid = "Enable IAM User Permissions"
|
|
631
|
+
Effect = "Allow"
|
|
632
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
633
|
+
Action = "kms:*"
|
|
634
|
+
Resource = "*"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
Sid = "Allow CloudWatch Logs"
|
|
638
|
+
Effect = "Allow"
|
|
639
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
640
|
+
Action = [
|
|
641
|
+
"kms:Encrypt",
|
|
642
|
+
"kms:Decrypt",
|
|
643
|
+
"kms:ReEncrypt*",
|
|
644
|
+
"kms:GenerateDataKey*",
|
|
645
|
+
"kms:DescribeKey"
|
|
646
|
+
]
|
|
647
|
+
Resource = "*"
|
|
648
|
+
Condition = {
|
|
649
|
+
ArnEquals = {
|
|
650
|
+
"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}"
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
tags = var.tags
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
661
|
+
count = var.enable_waf ? 1 : 0
|
|
662
|
+
|
|
663
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
664
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
665
|
+
}
|
|
666
|
+
|
|
609
667
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
610
668
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
611
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
612
669
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
613
670
|
count = var.enable_waf ? 1 : 0
|
|
614
671
|
|
|
615
|
-
name =
|
|
672
|
+
name = local.waf_log_group_name
|
|
616
673
|
retention_in_days = 30
|
|
674
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
617
675
|
tags = var.tags
|
|
618
676
|
}
|
|
619
677
|
|
|
620
|
-
resource "random_id" "waf_log_suffix" {
|
|
621
|
-
count = var.enable_waf ? 1 : 0
|
|
622
|
-
byte_length = 4
|
|
623
|
-
}
|
|
624
|
-
|
|
625
678
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
626
679
|
count = var.enable_waf ? 1 : 0
|
|
627
680
|
|
|
@@ -670,10 +723,18 @@ exports[`tsSmithyApiGenerator > should generate smithy ts api with Terraform pro
|
|
|
670
723
|
required_version = ">= 1.0"
|
|
671
724
|
|
|
672
725
|
required_providers {
|
|
726
|
+
archive = {
|
|
727
|
+
source = "hashicorp/archive"
|
|
728
|
+
version = "2.8.0"
|
|
729
|
+
}
|
|
673
730
|
aws = {
|
|
674
731
|
source = "hashicorp/aws"
|
|
675
732
|
version = "6.61.0"
|
|
676
733
|
}
|
|
734
|
+
random = {
|
|
735
|
+
source = "hashicorp/random"
|
|
736
|
+
version = "3.9.0"
|
|
737
|
+
}
|
|
677
738
|
}
|
|
678
739
|
}
|
|
679
740
|
|
|
@@ -779,6 +840,235 @@ resource "random_string" "suffix" {
|
|
|
779
840
|
upper = false
|
|
780
841
|
}
|
|
781
842
|
|
|
843
|
+
locals {
|
|
844
|
+
# Generated at build time from the API definition
|
|
845
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
846
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
847
|
+
|
|
848
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
849
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
850
|
+
|
|
851
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
852
|
+
"\${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}"
|
|
853
|
+
}
|
|
854
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
855
|
+
"\${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}"
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
859
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
860
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
861
|
+
|
|
862
|
+
path_nodes = merge([
|
|
863
|
+
for op, segments in local.operation_segments : {
|
|
864
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
865
|
+
depth = i
|
|
866
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
867
|
+
part = segment
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
]...)
|
|
871
|
+
|
|
872
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
873
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
874
|
+
max_path_depth = 16
|
|
875
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
876
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
877
|
+
} }
|
|
878
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
879
|
+
|
|
880
|
+
resource_ids = merge(
|
|
881
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
882
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
883
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
884
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
885
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
886
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
887
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
888
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
889
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
890
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
891
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
892
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
893
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
894
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
895
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
896
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
900
|
+
local.resource_ids[join("/", segments)]
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
904
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
resource "terraform_data" "operations_metadata" {
|
|
909
|
+
# Fails the plan when the operations metadata has not been generated
|
|
910
|
+
input = local.operations_file
|
|
911
|
+
|
|
912
|
+
lifecycle {
|
|
913
|
+
precondition {
|
|
914
|
+
condition = fileexists(local.operations_file)
|
|
915
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
916
|
+
}
|
|
917
|
+
precondition {
|
|
918
|
+
condition = length(local.operations) > 0
|
|
919
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
920
|
+
}
|
|
921
|
+
precondition {
|
|
922
|
+
condition = length(local.paths_too_deep) == 0
|
|
923
|
+
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."
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
# API Gateway resources at path depth 0
|
|
929
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
930
|
+
for_each = local.path_nodes_by_depth[0]
|
|
931
|
+
|
|
932
|
+
rest_api_id = module.rest_api.api_id
|
|
933
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
934
|
+
path_part = each.value.part
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
# API Gateway resources at path depth 1
|
|
938
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
939
|
+
for_each = local.path_nodes_by_depth[1]
|
|
940
|
+
|
|
941
|
+
rest_api_id = module.rest_api.api_id
|
|
942
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
943
|
+
path_part = each.value.part
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
# API Gateway resources at path depth 2
|
|
947
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
948
|
+
for_each = local.path_nodes_by_depth[2]
|
|
949
|
+
|
|
950
|
+
rest_api_id = module.rest_api.api_id
|
|
951
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
952
|
+
path_part = each.value.part
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
# API Gateway resources at path depth 3
|
|
956
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
957
|
+
for_each = local.path_nodes_by_depth[3]
|
|
958
|
+
|
|
959
|
+
rest_api_id = module.rest_api.api_id
|
|
960
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
961
|
+
path_part = each.value.part
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
# API Gateway resources at path depth 4
|
|
965
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
966
|
+
for_each = local.path_nodes_by_depth[4]
|
|
967
|
+
|
|
968
|
+
rest_api_id = module.rest_api.api_id
|
|
969
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
970
|
+
path_part = each.value.part
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
# API Gateway resources at path depth 5
|
|
974
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
975
|
+
for_each = local.path_nodes_by_depth[5]
|
|
976
|
+
|
|
977
|
+
rest_api_id = module.rest_api.api_id
|
|
978
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
979
|
+
path_part = each.value.part
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
# API Gateway resources at path depth 6
|
|
983
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
984
|
+
for_each = local.path_nodes_by_depth[6]
|
|
985
|
+
|
|
986
|
+
rest_api_id = module.rest_api.api_id
|
|
987
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
988
|
+
path_part = each.value.part
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
# API Gateway resources at path depth 7
|
|
992
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
993
|
+
for_each = local.path_nodes_by_depth[7]
|
|
994
|
+
|
|
995
|
+
rest_api_id = module.rest_api.api_id
|
|
996
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
997
|
+
path_part = each.value.part
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
# API Gateway resources at path depth 8
|
|
1001
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
1002
|
+
for_each = local.path_nodes_by_depth[8]
|
|
1003
|
+
|
|
1004
|
+
rest_api_id = module.rest_api.api_id
|
|
1005
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
1006
|
+
path_part = each.value.part
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
# API Gateway resources at path depth 9
|
|
1010
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
1011
|
+
for_each = local.path_nodes_by_depth[9]
|
|
1012
|
+
|
|
1013
|
+
rest_api_id = module.rest_api.api_id
|
|
1014
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
1015
|
+
path_part = each.value.part
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
# API Gateway resources at path depth 10
|
|
1019
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
1020
|
+
for_each = local.path_nodes_by_depth[10]
|
|
1021
|
+
|
|
1022
|
+
rest_api_id = module.rest_api.api_id
|
|
1023
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
1024
|
+
path_part = each.value.part
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
# API Gateway resources at path depth 11
|
|
1028
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
1029
|
+
for_each = local.path_nodes_by_depth[11]
|
|
1030
|
+
|
|
1031
|
+
rest_api_id = module.rest_api.api_id
|
|
1032
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
1033
|
+
path_part = each.value.part
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
# API Gateway resources at path depth 12
|
|
1037
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
1038
|
+
for_each = local.path_nodes_by_depth[12]
|
|
1039
|
+
|
|
1040
|
+
rest_api_id = module.rest_api.api_id
|
|
1041
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
1042
|
+
path_part = each.value.part
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
# API Gateway resources at path depth 13
|
|
1046
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
1047
|
+
for_each = local.path_nodes_by_depth[13]
|
|
1048
|
+
|
|
1049
|
+
rest_api_id = module.rest_api.api_id
|
|
1050
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
1051
|
+
path_part = each.value.part
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
# API Gateway resources at path depth 14
|
|
1055
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
1056
|
+
for_each = local.path_nodes_by_depth[14]
|
|
1057
|
+
|
|
1058
|
+
rest_api_id = module.rest_api.api_id
|
|
1059
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
1060
|
+
path_part = each.value.part
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
# API Gateway resources at path depth 15
|
|
1064
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
1065
|
+
for_each = local.path_nodes_by_depth[15]
|
|
1066
|
+
|
|
1067
|
+
rest_api_id = module.rest_api.api_id
|
|
1068
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
1069
|
+
path_part = each.value.part
|
|
1070
|
+
}
|
|
1071
|
+
|
|
782
1072
|
# Resources
|
|
783
1073
|
|
|
784
1074
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -853,8 +1143,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
853
1143
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
854
1144
|
}
|
|
855
1145
|
|
|
856
|
-
# Lambda function
|
|
857
1146
|
resource "aws_lambda_function" "api_lambda" {
|
|
1147
|
+
# One lambda function per operation, each serving just that operation
|
|
1148
|
+
for_each = local.operations
|
|
1149
|
+
|
|
858
1150
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
859
1151
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
860
1152
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -863,8 +1155,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
863
1155
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
864
1156
|
s3_key = aws_s3_object.lambda_zip.key
|
|
865
1157
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
866
|
-
function_name =
|
|
867
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
1158
|
+
function_name = local.function_name[each.key]
|
|
1159
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
868
1160
|
handler = "index.handler"
|
|
869
1161
|
runtime = "nodejs22.x"
|
|
870
1162
|
timeout = 30
|
|
@@ -900,7 +1192,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
900
1192
|
|
|
901
1193
|
# IAM role for Lambda execution
|
|
902
1194
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
903
|
-
|
|
1195
|
+
for_each = local.operations
|
|
1196
|
+
|
|
1197
|
+
name = local.role_name[each.key]
|
|
904
1198
|
|
|
905
1199
|
assume_role_policy = jsonencode({
|
|
906
1200
|
Version = "2012-10-17"
|
|
@@ -920,32 +1214,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
920
1214
|
|
|
921
1215
|
# Attach basic execution policy to Lambda role
|
|
922
1216
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
1217
|
+
for_each = local.operations
|
|
1218
|
+
|
|
923
1219
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
924
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1220
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
925
1221
|
}
|
|
926
1222
|
|
|
927
1223
|
# Attach X-Ray tracing policy to Lambda role
|
|
928
1224
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
1225
|
+
for_each = local.operations
|
|
1226
|
+
|
|
929
1227
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
930
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1228
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
931
1229
|
}
|
|
932
1230
|
|
|
933
1231
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
934
1232
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
935
|
-
|
|
1233
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
1234
|
+
|
|
936
1235
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
937
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1236
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
938
1237
|
}
|
|
939
1238
|
|
|
940
1239
|
# Additional IAM policies for Lambda (if provided)
|
|
941
1240
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1241
|
+
for_each = local.operations
|
|
1242
|
+
|
|
1243
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
1244
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
945
1245
|
|
|
946
1246
|
policy = jsonencode({
|
|
947
1247
|
Version = "2012-10-17"
|
|
948
|
-
Statement = local.
|
|
1248
|
+
Statement = local.lambda_policy_document_statements
|
|
949
1249
|
})
|
|
950
1250
|
}
|
|
951
1251
|
|
|
@@ -961,6 +1261,15 @@ locals {
|
|
|
961
1261
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
962
1262
|
}
|
|
963
1263
|
] : [], var.additional_iam_policy_statements)
|
|
1264
|
+
|
|
1265
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
1266
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
1267
|
+
{
|
|
1268
|
+
Effect = "Deny"
|
|
1269
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
1270
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
964
1273
|
}
|
|
965
1274
|
|
|
966
1275
|
# CloudWatch Log Group for Lambda
|
|
@@ -968,8 +1277,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
968
1277
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
969
1278
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
970
1279
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
971
|
-
|
|
972
|
-
|
|
1280
|
+
for_each = local.operations
|
|
1281
|
+
|
|
1282
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
1283
|
+
tags = var.tags
|
|
973
1284
|
}
|
|
974
1285
|
|
|
975
1286
|
|
|
@@ -1066,58 +1377,62 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
1066
1377
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
1067
1378
|
}
|
|
1068
1379
|
|
|
1069
|
-
#
|
|
1070
|
-
resource "
|
|
1071
|
-
rest_api_id = module.rest_api.api_id
|
|
1072
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
1073
|
-
path_part = "{proxy+}"
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
# Lambda integration for REST API
|
|
1077
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
1078
|
-
rest_api_id = module.rest_api.api_id
|
|
1079
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
1080
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
1081
|
-
|
|
1082
|
-
integration_http_method = "POST"
|
|
1083
|
-
type = "AWS_PROXY"
|
|
1084
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
1085
|
-
|
|
1086
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
# Method for proxy integration
|
|
1090
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
1380
|
+
# Method per operation
|
|
1381
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
1091
1382
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
1383
|
+
for_each = local.operations
|
|
1384
|
+
|
|
1092
1385
|
rest_api_id = module.rest_api.api_id
|
|
1093
|
-
resource_id =
|
|
1094
|
-
http_method =
|
|
1386
|
+
resource_id = local.operation_resource_id[each.key]
|
|
1387
|
+
http_method = upper(each.value.method)
|
|
1095
1388
|
|
|
1096
1389
|
authorization = "CUSTOM"
|
|
1097
1390
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
1098
1391
|
|
|
1392
|
+
# Path parameters must be declared on the method
|
|
1099
1393
|
request_parameters = {
|
|
1100
|
-
|
|
1394
|
+
for segment in local.operation_segments[each.key] :
|
|
1395
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
1396
|
+
if startswith(segment, "{")
|
|
1101
1397
|
}
|
|
1102
1398
|
|
|
1103
1399
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
1104
1400
|
}
|
|
1105
1401
|
|
|
1106
|
-
#
|
|
1402
|
+
# Lambda integration per operation
|
|
1403
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
1404
|
+
for_each = local.operations
|
|
1405
|
+
|
|
1406
|
+
rest_api_id = module.rest_api.api_id
|
|
1407
|
+
resource_id = local.operation_resource_id[each.key]
|
|
1408
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
1409
|
+
|
|
1410
|
+
integration_http_method = "POST"
|
|
1411
|
+
type = "AWS_PROXY"
|
|
1412
|
+
uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
1413
|
+
|
|
1414
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
1107
1418
|
resource "aws_api_gateway_method" "options_method" {
|
|
1108
1419
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
1109
1420
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
1421
|
+
for_each = local.resource_ids
|
|
1422
|
+
|
|
1110
1423
|
rest_api_id = module.rest_api.api_id
|
|
1111
|
-
resource_id =
|
|
1424
|
+
resource_id = each.value
|
|
1112
1425
|
http_method = "OPTIONS"
|
|
1113
1426
|
authorization = "NONE"
|
|
1114
1427
|
}
|
|
1115
1428
|
|
|
1116
|
-
# CORS integration for OPTIONS
|
|
1429
|
+
# CORS integration for OPTIONS methods
|
|
1117
1430
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
1431
|
+
for_each = local.resource_ids
|
|
1432
|
+
|
|
1118
1433
|
rest_api_id = module.rest_api.api_id
|
|
1119
|
-
resource_id =
|
|
1120
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
1434
|
+
resource_id = each.value
|
|
1435
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
1121
1436
|
|
|
1122
1437
|
type = "MOCK"
|
|
1123
1438
|
request_templates = {
|
|
@@ -1125,11 +1440,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
1125
1440
|
}
|
|
1126
1441
|
}
|
|
1127
1442
|
|
|
1128
|
-
# OPTIONS method
|
|
1443
|
+
# OPTIONS method responses
|
|
1129
1444
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
1445
|
+
for_each = local.resource_ids
|
|
1446
|
+
|
|
1130
1447
|
rest_api_id = module.rest_api.api_id
|
|
1131
|
-
resource_id =
|
|
1132
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
1448
|
+
resource_id = each.value
|
|
1449
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
1133
1450
|
status_code = "204"
|
|
1134
1451
|
|
|
1135
1452
|
response_parameters = {
|
|
@@ -1139,18 +1456,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
1139
1456
|
}
|
|
1140
1457
|
}
|
|
1141
1458
|
|
|
1142
|
-
# OPTIONS integration
|
|
1459
|
+
# OPTIONS integration responses
|
|
1143
1460
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
1461
|
+
for_each = local.resource_ids
|
|
1462
|
+
|
|
1144
1463
|
rest_api_id = module.rest_api.api_id
|
|
1145
|
-
resource_id =
|
|
1146
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
1147
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
1464
|
+
resource_id = each.value
|
|
1465
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
1466
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
1148
1467
|
|
|
1149
1468
|
response_parameters = {
|
|
1150
1469
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
1151
1470
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
1152
1471
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
1153
1472
|
}
|
|
1473
|
+
|
|
1474
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
1154
1475
|
}
|
|
1155
1476
|
|
|
1156
1477
|
# API Gateway deployment
|
|
@@ -1158,13 +1479,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
1158
1479
|
rest_api_id = module.rest_api.api_id
|
|
1159
1480
|
|
|
1160
1481
|
triggers = {
|
|
1161
|
-
redeployment = sha1(jsonencode(
|
|
1162
|
-
aws_api_gateway_resource.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1482
|
+
redeployment = sha1(jsonencode(concat(
|
|
1483
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
1484
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
1485
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
1486
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
1487
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
1488
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
1489
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
1490
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
1491
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
1492
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
1493
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
1494
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
1495
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
1496
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
1497
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
1498
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
1499
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
1500
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
1501
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
1502
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
1503
|
+
)))
|
|
1168
1504
|
}
|
|
1169
1505
|
|
|
1170
1506
|
lifecycle {
|
|
@@ -1172,7 +1508,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
1172
1508
|
}
|
|
1173
1509
|
|
|
1174
1510
|
depends_on = [
|
|
1175
|
-
aws_api_gateway_method.
|
|
1511
|
+
aws_api_gateway_method.operation_methods,
|
|
1176
1512
|
aws_api_gateway_integration.lambda_integration,
|
|
1177
1513
|
aws_api_gateway_method.options_method,
|
|
1178
1514
|
aws_api_gateway_integration.options_integration,
|
|
@@ -1301,11 +1637,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
1301
1637
|
|
|
1302
1638
|
# Lambda permission for API Gateway to invoke the function
|
|
1303
1639
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
1640
|
+
for_each = local.operations
|
|
1641
|
+
|
|
1304
1642
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
1305
1643
|
action = "lambda:InvokeFunction"
|
|
1306
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
1644
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
1307
1645
|
principal = "apigateway.amazonaws.com"
|
|
1308
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
1646
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
1309
1647
|
|
|
1310
1648
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
1311
1649
|
}
|
|
@@ -1317,7 +1655,7 @@ module "add_url_to_runtime_config" {
|
|
|
1317
1655
|
|
|
1318
1656
|
namespace = "connection"
|
|
1319
1657
|
key = "apis"
|
|
1320
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
1658
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
1321
1659
|
|
|
1322
1660
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
1323
1661
|
}
|
|
@@ -1370,83 +1708,83 @@ output "stage_id" {
|
|
|
1370
1708
|
value = aws_api_gateway_stage.api_stage.id
|
|
1371
1709
|
}
|
|
1372
1710
|
|
|
1373
|
-
# Lambda Function Outputs
|
|
1374
|
-
output "
|
|
1375
|
-
description = "
|
|
1376
|
-
value =
|
|
1711
|
+
# Lambda Function Outputs, keyed by operation name
|
|
1712
|
+
output "operations" {
|
|
1713
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
1714
|
+
value = keys(local.operations)
|
|
1377
1715
|
}
|
|
1378
1716
|
|
|
1379
|
-
output "
|
|
1380
|
-
description = "
|
|
1381
|
-
value = aws_lambda_function.api_lambda.
|
|
1717
|
+
output "lambda_function_names" {
|
|
1718
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
1719
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
1382
1720
|
}
|
|
1383
1721
|
|
|
1384
|
-
output "
|
|
1385
|
-
description = "
|
|
1386
|
-
value = aws_lambda_function.api_lambda.
|
|
1722
|
+
output "lambda_function_arns" {
|
|
1723
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
1724
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
1387
1725
|
}
|
|
1388
1726
|
|
|
1389
|
-
output "
|
|
1390
|
-
description = "
|
|
1391
|
-
value = aws_lambda_function.api_lambda.
|
|
1727
|
+
output "lambda_invoke_arns" {
|
|
1728
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
1729
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
1392
1730
|
}
|
|
1393
1731
|
|
|
1394
|
-
output "
|
|
1395
|
-
description = "
|
|
1396
|
-
value = aws_lambda_function.api_lambda.
|
|
1732
|
+
output "lambda_qualified_arns" {
|
|
1733
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
1734
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
1397
1735
|
}
|
|
1398
1736
|
|
|
1399
|
-
output "
|
|
1400
|
-
description = "
|
|
1401
|
-
value = aws_lambda_function.api_lambda.
|
|
1737
|
+
output "lambda_versions" {
|
|
1738
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
1739
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
1402
1740
|
}
|
|
1403
1741
|
|
|
1404
|
-
output "
|
|
1405
|
-
description = "
|
|
1406
|
-
value =
|
|
1742
|
+
output "lambda_source_code_hash" {
|
|
1743
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
1744
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
1407
1745
|
}
|
|
1408
1746
|
|
|
1409
|
-
# IAM Role Outputs
|
|
1410
|
-
output "
|
|
1411
|
-
description = "ARN of
|
|
1412
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
1747
|
+
# IAM Role Outputs, keyed by operation name
|
|
1748
|
+
output "lambda_execution_role_arns" {
|
|
1749
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
1750
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
1413
1751
|
}
|
|
1414
1752
|
|
|
1415
|
-
output "
|
|
1416
|
-
description = "Name of
|
|
1417
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
1753
|
+
output "lambda_execution_role_names" {
|
|
1754
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
1755
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
1418
1756
|
}
|
|
1419
1757
|
|
|
1420
1758
|
output "security_group_id" {
|
|
1421
|
-
description = "Security group ID
|
|
1759
|
+
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."
|
|
1422
1760
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
1423
1761
|
}
|
|
1424
1762
|
|
|
1425
|
-
# Integration Outputs
|
|
1426
|
-
output "
|
|
1427
|
-
description = "ID of
|
|
1428
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
1763
|
+
# Integration Outputs, keyed by operation name
|
|
1764
|
+
output "integration_ids" {
|
|
1765
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
1766
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
1429
1767
|
}
|
|
1430
1768
|
|
|
1431
|
-
output "
|
|
1432
|
-
description = "ID of
|
|
1433
|
-
value =
|
|
1769
|
+
output "method_ids" {
|
|
1770
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
1771
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
1434
1772
|
}
|
|
1435
1773
|
|
|
1436
|
-
output "
|
|
1437
|
-
description = "ID of
|
|
1438
|
-
value =
|
|
1774
|
+
output "resource_ids" {
|
|
1775
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
1776
|
+
value = local.resource_ids
|
|
1439
1777
|
}
|
|
1440
1778
|
|
|
1441
|
-
# CloudWatch Log Groups
|
|
1442
|
-
output "
|
|
1443
|
-
description = "Name of
|
|
1444
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
1779
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
1780
|
+
output "lambda_log_group_names" {
|
|
1781
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
1782
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
1445
1783
|
}
|
|
1446
1784
|
|
|
1447
|
-
output "
|
|
1448
|
-
description = "ARN of
|
|
1449
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
1785
|
+
output "lambda_log_group_arns" {
|
|
1786
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
1787
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
1450
1788
|
}
|
|
1451
1789
|
"
|
|
1452
1790
|
`;
|
|
@@ -2097,22 +2435,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
2097
2435
|
}
|
|
2098
2436
|
}
|
|
2099
2437
|
|
|
2438
|
+
resource "random_id" "waf_log_suffix" {
|
|
2439
|
+
count = var.enable_waf ? 1 : 0
|
|
2440
|
+
byte_length = 4
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
locals {
|
|
2444
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
# KMS key for encrypting WAF request logs at rest
|
|
2448
|
+
resource "aws_kms_key" "waf_logs" {
|
|
2449
|
+
count = var.enable_waf ? 1 : 0
|
|
2450
|
+
|
|
2451
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
2452
|
+
deletion_window_in_days = 7
|
|
2453
|
+
enable_key_rotation = true
|
|
2454
|
+
|
|
2455
|
+
policy = jsonencode({
|
|
2456
|
+
Version = "2012-10-17"
|
|
2457
|
+
Statement = [
|
|
2458
|
+
{
|
|
2459
|
+
Sid = "Enable IAM User Permissions"
|
|
2460
|
+
Effect = "Allow"
|
|
2461
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
2462
|
+
Action = "kms:*"
|
|
2463
|
+
Resource = "*"
|
|
2464
|
+
},
|
|
2465
|
+
{
|
|
2466
|
+
Sid = "Allow CloudWatch Logs"
|
|
2467
|
+
Effect = "Allow"
|
|
2468
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
2469
|
+
Action = [
|
|
2470
|
+
"kms:Encrypt",
|
|
2471
|
+
"kms:Decrypt",
|
|
2472
|
+
"kms:ReEncrypt*",
|
|
2473
|
+
"kms:GenerateDataKey*",
|
|
2474
|
+
"kms:DescribeKey"
|
|
2475
|
+
]
|
|
2476
|
+
Resource = "*"
|
|
2477
|
+
Condition = {
|
|
2478
|
+
ArnEquals = {
|
|
2479
|
+
"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}"
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
]
|
|
2484
|
+
})
|
|
2485
|
+
|
|
2486
|
+
tags = var.tags
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
2490
|
+
count = var.enable_waf ? 1 : 0
|
|
2491
|
+
|
|
2492
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
2493
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2100
2496
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
2101
2497
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
2102
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2103
2498
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
2104
2499
|
count = var.enable_waf ? 1 : 0
|
|
2105
2500
|
|
|
2106
|
-
name =
|
|
2501
|
+
name = local.waf_log_group_name
|
|
2107
2502
|
retention_in_days = 30
|
|
2503
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
2108
2504
|
tags = var.tags
|
|
2109
2505
|
}
|
|
2110
2506
|
|
|
2111
|
-
resource "random_id" "waf_log_suffix" {
|
|
2112
|
-
count = var.enable_waf ? 1 : 0
|
|
2113
|
-
byte_length = 4
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
2507
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
2117
2508
|
count = var.enable_waf ? 1 : 0
|
|
2118
2509
|
|
|
@@ -2158,10 +2549,18 @@ output "waf_web_acl_arn" {
|
|
|
2158
2549
|
required_version = ">= 1.0"
|
|
2159
2550
|
|
|
2160
2551
|
required_providers {
|
|
2552
|
+
archive = {
|
|
2553
|
+
source = "hashicorp/archive"
|
|
2554
|
+
version = "2.8.0"
|
|
2555
|
+
}
|
|
2161
2556
|
aws = {
|
|
2162
2557
|
source = "hashicorp/aws"
|
|
2163
2558
|
version = "6.61.0"
|
|
2164
2559
|
}
|
|
2560
|
+
random = {
|
|
2561
|
+
source = "hashicorp/random"
|
|
2562
|
+
version = "3.9.0"
|
|
2563
|
+
}
|
|
2165
2564
|
}
|
|
2166
2565
|
}
|
|
2167
2566
|
|
|
@@ -2277,7 +2676,236 @@ resource "random_string" "suffix" {
|
|
|
2277
2676
|
upper = false
|
|
2278
2677
|
}
|
|
2279
2678
|
|
|
2280
|
-
|
|
2679
|
+
locals {
|
|
2680
|
+
# Generated at build time from the API definition
|
|
2681
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
2682
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
2683
|
+
|
|
2684
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
2685
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
2686
|
+
|
|
2687
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
2688
|
+
"\${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}"
|
|
2689
|
+
}
|
|
2690
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
2691
|
+
"\${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}"
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
2695
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
2696
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
2697
|
+
|
|
2698
|
+
path_nodes = merge([
|
|
2699
|
+
for op, segments in local.operation_segments : {
|
|
2700
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
2701
|
+
depth = i
|
|
2702
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
2703
|
+
part = segment
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
]...)
|
|
2707
|
+
|
|
2708
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
2709
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
2710
|
+
max_path_depth = 16
|
|
2711
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
2712
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
2713
|
+
} }
|
|
2714
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
2715
|
+
|
|
2716
|
+
resource_ids = merge(
|
|
2717
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
2718
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
2719
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
2720
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
2721
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
2722
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
2723
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
2724
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
2725
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
2726
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
2727
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
2728
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
2729
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
2730
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
2731
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
2732
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
2733
|
+
)
|
|
2734
|
+
|
|
2735
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
2736
|
+
local.resource_ids[join("/", segments)]
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
2740
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
resource "terraform_data" "operations_metadata" {
|
|
2745
|
+
# Fails the plan when the operations metadata has not been generated
|
|
2746
|
+
input = local.operations_file
|
|
2747
|
+
|
|
2748
|
+
lifecycle {
|
|
2749
|
+
precondition {
|
|
2750
|
+
condition = fileexists(local.operations_file)
|
|
2751
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
2752
|
+
}
|
|
2753
|
+
precondition {
|
|
2754
|
+
condition = length(local.operations) > 0
|
|
2755
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
2756
|
+
}
|
|
2757
|
+
precondition {
|
|
2758
|
+
condition = length(local.paths_too_deep) == 0
|
|
2759
|
+
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."
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
# API Gateway resources at path depth 0
|
|
2765
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
2766
|
+
for_each = local.path_nodes_by_depth[0]
|
|
2767
|
+
|
|
2768
|
+
rest_api_id = module.rest_api.api_id
|
|
2769
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
2770
|
+
path_part = each.value.part
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
# API Gateway resources at path depth 1
|
|
2774
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
2775
|
+
for_each = local.path_nodes_by_depth[1]
|
|
2776
|
+
|
|
2777
|
+
rest_api_id = module.rest_api.api_id
|
|
2778
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
2779
|
+
path_part = each.value.part
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
# API Gateway resources at path depth 2
|
|
2783
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
2784
|
+
for_each = local.path_nodes_by_depth[2]
|
|
2785
|
+
|
|
2786
|
+
rest_api_id = module.rest_api.api_id
|
|
2787
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
2788
|
+
path_part = each.value.part
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
# API Gateway resources at path depth 3
|
|
2792
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
2793
|
+
for_each = local.path_nodes_by_depth[3]
|
|
2794
|
+
|
|
2795
|
+
rest_api_id = module.rest_api.api_id
|
|
2796
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
2797
|
+
path_part = each.value.part
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
# API Gateway resources at path depth 4
|
|
2801
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
2802
|
+
for_each = local.path_nodes_by_depth[4]
|
|
2803
|
+
|
|
2804
|
+
rest_api_id = module.rest_api.api_id
|
|
2805
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
2806
|
+
path_part = each.value.part
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
# API Gateway resources at path depth 5
|
|
2810
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
2811
|
+
for_each = local.path_nodes_by_depth[5]
|
|
2812
|
+
|
|
2813
|
+
rest_api_id = module.rest_api.api_id
|
|
2814
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
2815
|
+
path_part = each.value.part
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
# API Gateway resources at path depth 6
|
|
2819
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
2820
|
+
for_each = local.path_nodes_by_depth[6]
|
|
2821
|
+
|
|
2822
|
+
rest_api_id = module.rest_api.api_id
|
|
2823
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
2824
|
+
path_part = each.value.part
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
# API Gateway resources at path depth 7
|
|
2828
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
2829
|
+
for_each = local.path_nodes_by_depth[7]
|
|
2830
|
+
|
|
2831
|
+
rest_api_id = module.rest_api.api_id
|
|
2832
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
2833
|
+
path_part = each.value.part
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
# API Gateway resources at path depth 8
|
|
2837
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
2838
|
+
for_each = local.path_nodes_by_depth[8]
|
|
2839
|
+
|
|
2840
|
+
rest_api_id = module.rest_api.api_id
|
|
2841
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
2842
|
+
path_part = each.value.part
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
# API Gateway resources at path depth 9
|
|
2846
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
2847
|
+
for_each = local.path_nodes_by_depth[9]
|
|
2848
|
+
|
|
2849
|
+
rest_api_id = module.rest_api.api_id
|
|
2850
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
2851
|
+
path_part = each.value.part
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
# API Gateway resources at path depth 10
|
|
2855
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
2856
|
+
for_each = local.path_nodes_by_depth[10]
|
|
2857
|
+
|
|
2858
|
+
rest_api_id = module.rest_api.api_id
|
|
2859
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
2860
|
+
path_part = each.value.part
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
# API Gateway resources at path depth 11
|
|
2864
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
2865
|
+
for_each = local.path_nodes_by_depth[11]
|
|
2866
|
+
|
|
2867
|
+
rest_api_id = module.rest_api.api_id
|
|
2868
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
2869
|
+
path_part = each.value.part
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
# API Gateway resources at path depth 12
|
|
2873
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
2874
|
+
for_each = local.path_nodes_by_depth[12]
|
|
2875
|
+
|
|
2876
|
+
rest_api_id = module.rest_api.api_id
|
|
2877
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
2878
|
+
path_part = each.value.part
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
# API Gateway resources at path depth 13
|
|
2882
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
2883
|
+
for_each = local.path_nodes_by_depth[13]
|
|
2884
|
+
|
|
2885
|
+
rest_api_id = module.rest_api.api_id
|
|
2886
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
2887
|
+
path_part = each.value.part
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
# API Gateway resources at path depth 14
|
|
2891
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
2892
|
+
for_each = local.path_nodes_by_depth[14]
|
|
2893
|
+
|
|
2894
|
+
rest_api_id = module.rest_api.api_id
|
|
2895
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
2896
|
+
path_part = each.value.part
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
# API Gateway resources at path depth 15
|
|
2900
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
2901
|
+
for_each = local.path_nodes_by_depth[15]
|
|
2902
|
+
|
|
2903
|
+
rest_api_id = module.rest_api.api_id
|
|
2904
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
2905
|
+
path_part = each.value.part
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
# Resources
|
|
2281
2909
|
|
|
2282
2910
|
# Create Lambda ZIP file from the bundle directory
|
|
2283
2911
|
data "archive_file" "lambda_zip" {
|
|
@@ -2351,8 +2979,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
2351
2979
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
2352
2980
|
}
|
|
2353
2981
|
|
|
2354
|
-
# Lambda function
|
|
2355
2982
|
resource "aws_lambda_function" "api_lambda" {
|
|
2983
|
+
# One lambda function per operation, each serving just that operation
|
|
2984
|
+
for_each = local.operations
|
|
2985
|
+
|
|
2356
2986
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
2357
2987
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
2358
2988
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -2361,8 +2991,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2361
2991
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
2362
2992
|
s3_key = aws_s3_object.lambda_zip.key
|
|
2363
2993
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
2364
|
-
function_name =
|
|
2365
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
2994
|
+
function_name = local.function_name[each.key]
|
|
2995
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
2366
2996
|
handler = "index.handler"
|
|
2367
2997
|
runtime = "nodejs22.x"
|
|
2368
2998
|
timeout = 30
|
|
@@ -2398,7 +3028,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2398
3028
|
|
|
2399
3029
|
# IAM role for Lambda execution
|
|
2400
3030
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
2401
|
-
|
|
3031
|
+
for_each = local.operations
|
|
3032
|
+
|
|
3033
|
+
name = local.role_name[each.key]
|
|
2402
3034
|
|
|
2403
3035
|
assume_role_policy = jsonencode({
|
|
2404
3036
|
Version = "2012-10-17"
|
|
@@ -2418,32 +3050,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
2418
3050
|
|
|
2419
3051
|
# Attach basic execution policy to Lambda role
|
|
2420
3052
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3053
|
+
for_each = local.operations
|
|
3054
|
+
|
|
2421
3055
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
2422
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3056
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2423
3057
|
}
|
|
2424
3058
|
|
|
2425
3059
|
# Attach X-Ray tracing policy to Lambda role
|
|
2426
3060
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3061
|
+
for_each = local.operations
|
|
3062
|
+
|
|
2427
3063
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
2428
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3064
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2429
3065
|
}
|
|
2430
3066
|
|
|
2431
3067
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
2432
3068
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
2433
|
-
|
|
3069
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
3070
|
+
|
|
2434
3071
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
2435
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3072
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2436
3073
|
}
|
|
2437
3074
|
|
|
2438
3075
|
# Additional IAM policies for Lambda (if provided)
|
|
2439
3076
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
3077
|
+
for_each = local.operations
|
|
3078
|
+
|
|
3079
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
3080
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
2443
3081
|
|
|
2444
3082
|
policy = jsonencode({
|
|
2445
3083
|
Version = "2012-10-17"
|
|
2446
|
-
Statement = local.
|
|
3084
|
+
Statement = local.lambda_policy_document_statements
|
|
2447
3085
|
})
|
|
2448
3086
|
}
|
|
2449
3087
|
|
|
@@ -2459,6 +3097,15 @@ locals {
|
|
|
2459
3097
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
2460
3098
|
}
|
|
2461
3099
|
] : [], var.additional_iam_policy_statements)
|
|
3100
|
+
|
|
3101
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
3102
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
3103
|
+
{
|
|
3104
|
+
Effect = "Deny"
|
|
3105
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
3106
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
3107
|
+
}
|
|
3108
|
+
]
|
|
2462
3109
|
}
|
|
2463
3110
|
|
|
2464
3111
|
# CloudWatch Log Group for Lambda
|
|
@@ -2466,8 +3113,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
2466
3113
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2467
3114
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
2468
3115
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
2469
|
-
|
|
2470
|
-
|
|
3116
|
+
for_each = local.operations
|
|
3117
|
+
|
|
3118
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
3119
|
+
tags = var.tags
|
|
2471
3120
|
}
|
|
2472
3121
|
|
|
2473
3122
|
|
|
@@ -2480,32 +3129,14 @@ resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
|
2480
3129
|
identity_source = "method.request.header.Authorization"
|
|
2481
3130
|
}
|
|
2482
3131
|
|
|
2483
|
-
#
|
|
2484
|
-
resource "
|
|
2485
|
-
rest_api_id = module.rest_api.api_id
|
|
2486
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
2487
|
-
path_part = "{proxy+}"
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
# Lambda integration for REST API
|
|
2491
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
2492
|
-
rest_api_id = module.rest_api.api_id
|
|
2493
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2494
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
2495
|
-
|
|
2496
|
-
integration_http_method = "POST"
|
|
2497
|
-
type = "AWS_PROXY"
|
|
2498
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
2499
|
-
|
|
2500
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
# Method for proxy integration
|
|
2504
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
3132
|
+
# Method per operation
|
|
3133
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
2505
3134
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
3135
|
+
for_each = local.operations
|
|
3136
|
+
|
|
2506
3137
|
rest_api_id = module.rest_api.api_id
|
|
2507
|
-
resource_id =
|
|
2508
|
-
http_method =
|
|
3138
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3139
|
+
http_method = upper(each.value.method)
|
|
2509
3140
|
|
|
2510
3141
|
authorization = "COGNITO_USER_POOLS"
|
|
2511
3142
|
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
@@ -2513,28 +3144,50 @@ resource "aws_api_gateway_method" "proxy_method" {
|
|
|
2513
3144
|
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
2514
3145
|
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
2515
3146
|
|
|
3147
|
+
# Path parameters must be declared on the method
|
|
2516
3148
|
request_parameters = {
|
|
2517
|
-
|
|
3149
|
+
for segment in local.operation_segments[each.key] :
|
|
3150
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
3151
|
+
if startswith(segment, "{")
|
|
2518
3152
|
}
|
|
2519
3153
|
|
|
2520
3154
|
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
2521
3155
|
}
|
|
2522
3156
|
|
|
2523
|
-
#
|
|
3157
|
+
# Lambda integration per operation
|
|
3158
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3159
|
+
for_each = local.operations
|
|
3160
|
+
|
|
3161
|
+
rest_api_id = module.rest_api.api_id
|
|
3162
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3163
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
3164
|
+
|
|
3165
|
+
integration_http_method = "POST"
|
|
3166
|
+
type = "AWS_PROXY"
|
|
3167
|
+
uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
3168
|
+
|
|
3169
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
2524
3173
|
resource "aws_api_gateway_method" "options_method" {
|
|
2525
3174
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
2526
3175
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
3176
|
+
for_each = local.resource_ids
|
|
3177
|
+
|
|
2527
3178
|
rest_api_id = module.rest_api.api_id
|
|
2528
|
-
resource_id =
|
|
3179
|
+
resource_id = each.value
|
|
2529
3180
|
http_method = "OPTIONS"
|
|
2530
3181
|
authorization = "NONE"
|
|
2531
3182
|
}
|
|
2532
3183
|
|
|
2533
|
-
# CORS integration for OPTIONS
|
|
3184
|
+
# CORS integration for OPTIONS methods
|
|
2534
3185
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
3186
|
+
for_each = local.resource_ids
|
|
3187
|
+
|
|
2535
3188
|
rest_api_id = module.rest_api.api_id
|
|
2536
|
-
resource_id =
|
|
2537
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3189
|
+
resource_id = each.value
|
|
3190
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
2538
3191
|
|
|
2539
3192
|
type = "MOCK"
|
|
2540
3193
|
request_templates = {
|
|
@@ -2542,11 +3195,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
2542
3195
|
}
|
|
2543
3196
|
}
|
|
2544
3197
|
|
|
2545
|
-
# OPTIONS method
|
|
3198
|
+
# OPTIONS method responses
|
|
2546
3199
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
3200
|
+
for_each = local.resource_ids
|
|
3201
|
+
|
|
2547
3202
|
rest_api_id = module.rest_api.api_id
|
|
2548
|
-
resource_id =
|
|
2549
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3203
|
+
resource_id = each.value
|
|
3204
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
2550
3205
|
status_code = "204"
|
|
2551
3206
|
|
|
2552
3207
|
response_parameters = {
|
|
@@ -2556,18 +3211,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
2556
3211
|
}
|
|
2557
3212
|
}
|
|
2558
3213
|
|
|
2559
|
-
# OPTIONS integration
|
|
3214
|
+
# OPTIONS integration responses
|
|
2560
3215
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
3216
|
+
for_each = local.resource_ids
|
|
3217
|
+
|
|
2561
3218
|
rest_api_id = module.rest_api.api_id
|
|
2562
|
-
resource_id =
|
|
2563
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
2564
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
3219
|
+
resource_id = each.value
|
|
3220
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3221
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
2565
3222
|
|
|
2566
3223
|
response_parameters = {
|
|
2567
3224
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
2568
3225
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
2569
3226
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
2570
3227
|
}
|
|
3228
|
+
|
|
3229
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
2571
3230
|
}
|
|
2572
3231
|
|
|
2573
3232
|
# API Gateway deployment
|
|
@@ -2575,13 +3234,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
2575
3234
|
rest_api_id = module.rest_api.api_id
|
|
2576
3235
|
|
|
2577
3236
|
triggers = {
|
|
2578
|
-
redeployment = sha1(jsonencode(
|
|
2579
|
-
aws_api_gateway_resource.
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
3237
|
+
redeployment = sha1(jsonencode(concat(
|
|
3238
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
3239
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
3240
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
3241
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
3242
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
3243
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
3244
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
3245
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
3246
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
3247
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
3248
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
3249
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
3250
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
3251
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
3252
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
3253
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
3254
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
3255
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
3256
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
3257
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
3258
|
+
)))
|
|
2585
3259
|
}
|
|
2586
3260
|
|
|
2587
3261
|
lifecycle {
|
|
@@ -2589,7 +3263,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
2589
3263
|
}
|
|
2590
3264
|
|
|
2591
3265
|
depends_on = [
|
|
2592
|
-
aws_api_gateway_method.
|
|
3266
|
+
aws_api_gateway_method.operation_methods,
|
|
2593
3267
|
aws_api_gateway_integration.lambda_integration,
|
|
2594
3268
|
aws_api_gateway_method.options_method,
|
|
2595
3269
|
aws_api_gateway_integration.options_integration,
|
|
@@ -2717,11 +3391,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
2717
3391
|
|
|
2718
3392
|
# Lambda permission for API Gateway to invoke the function
|
|
2719
3393
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3394
|
+
for_each = local.operations
|
|
3395
|
+
|
|
2720
3396
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
2721
3397
|
action = "lambda:InvokeFunction"
|
|
2722
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3398
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
2723
3399
|
principal = "apigateway.amazonaws.com"
|
|
2724
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
3400
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
2725
3401
|
|
|
2726
3402
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
2727
3403
|
}
|
|
@@ -2733,7 +3409,7 @@ module "add_url_to_runtime_config" {
|
|
|
2733
3409
|
|
|
2734
3410
|
namespace = "connection"
|
|
2735
3411
|
key = "apis"
|
|
2736
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
3412
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
2737
3413
|
|
|
2738
3414
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
2739
3415
|
}
|
|
@@ -2786,83 +3462,83 @@ output "stage_id" {
|
|
|
2786
3462
|
value = aws_api_gateway_stage.api_stage.id
|
|
2787
3463
|
}
|
|
2788
3464
|
|
|
2789
|
-
# Lambda Function Outputs
|
|
2790
|
-
output "
|
|
2791
|
-
description = "
|
|
2792
|
-
value =
|
|
3465
|
+
# Lambda Function Outputs, keyed by operation name
|
|
3466
|
+
output "operations" {
|
|
3467
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
3468
|
+
value = keys(local.operations)
|
|
2793
3469
|
}
|
|
2794
3470
|
|
|
2795
|
-
output "
|
|
2796
|
-
description = "
|
|
2797
|
-
value = aws_lambda_function.api_lambda.
|
|
3471
|
+
output "lambda_function_names" {
|
|
3472
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
3473
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
2798
3474
|
}
|
|
2799
3475
|
|
|
2800
|
-
output "
|
|
2801
|
-
description = "
|
|
2802
|
-
value = aws_lambda_function.api_lambda.
|
|
3476
|
+
output "lambda_function_arns" {
|
|
3477
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
3478
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
2803
3479
|
}
|
|
2804
3480
|
|
|
2805
|
-
output "
|
|
2806
|
-
description = "
|
|
2807
|
-
value = aws_lambda_function.api_lambda.
|
|
3481
|
+
output "lambda_invoke_arns" {
|
|
3482
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
3483
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
2808
3484
|
}
|
|
2809
3485
|
|
|
2810
|
-
output "
|
|
2811
|
-
description = "
|
|
2812
|
-
value = aws_lambda_function.api_lambda.
|
|
3486
|
+
output "lambda_qualified_arns" {
|
|
3487
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
3488
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
2813
3489
|
}
|
|
2814
3490
|
|
|
2815
|
-
output "
|
|
2816
|
-
description = "
|
|
2817
|
-
value = aws_lambda_function.api_lambda.
|
|
3491
|
+
output "lambda_versions" {
|
|
3492
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
3493
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
2818
3494
|
}
|
|
2819
3495
|
|
|
2820
|
-
output "
|
|
2821
|
-
description = "
|
|
2822
|
-
value =
|
|
3496
|
+
output "lambda_source_code_hash" {
|
|
3497
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
3498
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
2823
3499
|
}
|
|
2824
3500
|
|
|
2825
|
-
# IAM Role Outputs
|
|
2826
|
-
output "
|
|
2827
|
-
description = "ARN of
|
|
2828
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
3501
|
+
# IAM Role Outputs, keyed by operation name
|
|
3502
|
+
output "lambda_execution_role_arns" {
|
|
3503
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
3504
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
2829
3505
|
}
|
|
2830
3506
|
|
|
2831
|
-
output "
|
|
2832
|
-
description = "Name of
|
|
2833
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
3507
|
+
output "lambda_execution_role_names" {
|
|
3508
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
3509
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
2834
3510
|
}
|
|
2835
3511
|
|
|
2836
3512
|
output "security_group_id" {
|
|
2837
|
-
description = "Security group ID
|
|
3513
|
+
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."
|
|
2838
3514
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
2839
3515
|
}
|
|
2840
3516
|
|
|
2841
|
-
# Integration Outputs
|
|
2842
|
-
output "
|
|
2843
|
-
description = "ID of
|
|
2844
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
3517
|
+
# Integration Outputs, keyed by operation name
|
|
3518
|
+
output "integration_ids" {
|
|
3519
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
3520
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
2845
3521
|
}
|
|
2846
3522
|
|
|
2847
|
-
output "
|
|
2848
|
-
description = "ID of
|
|
2849
|
-
value =
|
|
3523
|
+
output "method_ids" {
|
|
3524
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
3525
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
2850
3526
|
}
|
|
2851
3527
|
|
|
2852
|
-
output "
|
|
2853
|
-
description = "ID of
|
|
2854
|
-
value =
|
|
3528
|
+
output "resource_ids" {
|
|
3529
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
3530
|
+
value = local.resource_ids
|
|
2855
3531
|
}
|
|
2856
3532
|
|
|
2857
|
-
# CloudWatch Log Groups
|
|
2858
|
-
output "
|
|
2859
|
-
description = "Name of
|
|
2860
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
3533
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
3534
|
+
output "lambda_log_group_names" {
|
|
3535
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3536
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
2861
3537
|
}
|
|
2862
3538
|
|
|
2863
|
-
output "
|
|
2864
|
-
description = "ARN of
|
|
2865
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
3539
|
+
output "lambda_log_group_arns" {
|
|
3540
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3541
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
2866
3542
|
}
|
|
2867
3543
|
",
|
|
2868
3544
|
}
|
|
@@ -3072,22 +3748,75 @@ resource "aws_wafv2_web_acl" "api_waf" {
|
|
|
3072
3748
|
}
|
|
3073
3749
|
}
|
|
3074
3750
|
|
|
3751
|
+
resource "random_id" "waf_log_suffix" {
|
|
3752
|
+
count = var.enable_waf ? 1 : 0
|
|
3753
|
+
byte_length = 4
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
locals {
|
|
3757
|
+
waf_log_group_name = var.enable_waf ? "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}" : null
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
# KMS key for encrypting WAF request logs at rest
|
|
3761
|
+
resource "aws_kms_key" "waf_logs" {
|
|
3762
|
+
count = var.enable_waf ? 1 : 0
|
|
3763
|
+
|
|
3764
|
+
description = "\${var.api_name} API WAF log encryption"
|
|
3765
|
+
deletion_window_in_days = 7
|
|
3766
|
+
enable_key_rotation = true
|
|
3767
|
+
|
|
3768
|
+
policy = jsonencode({
|
|
3769
|
+
Version = "2012-10-17"
|
|
3770
|
+
Statement = [
|
|
3771
|
+
{
|
|
3772
|
+
Sid = "Enable IAM User Permissions"
|
|
3773
|
+
Effect = "Allow"
|
|
3774
|
+
Principal = { AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root" }
|
|
3775
|
+
Action = "kms:*"
|
|
3776
|
+
Resource = "*"
|
|
3777
|
+
},
|
|
3778
|
+
{
|
|
3779
|
+
Sid = "Allow CloudWatch Logs"
|
|
3780
|
+
Effect = "Allow"
|
|
3781
|
+
Principal = { Service = "logs.\${data.aws_region.current.region}.amazonaws.com" }
|
|
3782
|
+
Action = [
|
|
3783
|
+
"kms:Encrypt",
|
|
3784
|
+
"kms:Decrypt",
|
|
3785
|
+
"kms:ReEncrypt*",
|
|
3786
|
+
"kms:GenerateDataKey*",
|
|
3787
|
+
"kms:DescribeKey"
|
|
3788
|
+
]
|
|
3789
|
+
Resource = "*"
|
|
3790
|
+
Condition = {
|
|
3791
|
+
ArnEquals = {
|
|
3792
|
+
"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}"
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
]
|
|
3797
|
+
})
|
|
3798
|
+
|
|
3799
|
+
tags = var.tags
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
resource "aws_kms_alias" "waf_logs" {
|
|
3803
|
+
count = var.enable_waf ? 1 : 0
|
|
3804
|
+
|
|
3805
|
+
name = "alias/\${var.api_name}-\${random_id.waf_log_suffix[0].hex}-waf-logs"
|
|
3806
|
+
target_key_id = aws_kms_key.waf_logs[0].key_id
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3075
3809
|
# CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
|
|
3076
3810
|
resource "aws_cloudwatch_log_group" "api_waf_logs" {
|
|
3077
|
-
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3078
3811
|
#checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
|
|
3079
3812
|
count = var.enable_waf ? 1 : 0
|
|
3080
3813
|
|
|
3081
|
-
name =
|
|
3814
|
+
name = local.waf_log_group_name
|
|
3082
3815
|
retention_in_days = 30
|
|
3816
|
+
kms_key_id = aws_kms_key.waf_logs[0].arn
|
|
3083
3817
|
tags = var.tags
|
|
3084
3818
|
}
|
|
3085
3819
|
|
|
3086
|
-
resource "random_id" "waf_log_suffix" {
|
|
3087
|
-
count = var.enable_waf ? 1 : 0
|
|
3088
|
-
byte_length = 4
|
|
3089
|
-
}
|
|
3090
|
-
|
|
3091
3820
|
resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
|
|
3092
3821
|
count = var.enable_waf ? 1 : 0
|
|
3093
3822
|
|
|
@@ -3133,10 +3862,18 @@ output "waf_web_acl_arn" {
|
|
|
3133
3862
|
required_version = ">= 1.0"
|
|
3134
3863
|
|
|
3135
3864
|
required_providers {
|
|
3865
|
+
archive = {
|
|
3866
|
+
source = "hashicorp/archive"
|
|
3867
|
+
version = "2.8.0"
|
|
3868
|
+
}
|
|
3136
3869
|
aws = {
|
|
3137
3870
|
source = "hashicorp/aws"
|
|
3138
3871
|
version = "6.61.0"
|
|
3139
3872
|
}
|
|
3873
|
+
random = {
|
|
3874
|
+
source = "hashicorp/random"
|
|
3875
|
+
version = "3.9.0"
|
|
3876
|
+
}
|
|
3140
3877
|
}
|
|
3141
3878
|
}
|
|
3142
3879
|
|
|
@@ -3242,6 +3979,235 @@ resource "random_string" "suffix" {
|
|
|
3242
3979
|
upper = false
|
|
3243
3980
|
}
|
|
3244
3981
|
|
|
3982
|
+
locals {
|
|
3983
|
+
# Generated at build time from the API definition
|
|
3984
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
3985
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
3986
|
+
|
|
3987
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
3988
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
3989
|
+
|
|
3990
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
3991
|
+
"\${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}"
|
|
3992
|
+
}
|
|
3993
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
3994
|
+
"\${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}"
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
3998
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
3999
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
4000
|
+
|
|
4001
|
+
path_nodes = merge([
|
|
4002
|
+
for op, segments in local.operation_segments : {
|
|
4003
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
4004
|
+
depth = i
|
|
4005
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
4006
|
+
part = segment
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
]...)
|
|
4010
|
+
|
|
4011
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
4012
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
4013
|
+
max_path_depth = 16
|
|
4014
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
4015
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
4016
|
+
} }
|
|
4017
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
4018
|
+
|
|
4019
|
+
resource_ids = merge(
|
|
4020
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
4021
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
4022
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
4023
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
4024
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
4025
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
4026
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
4027
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
4028
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
4029
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
4030
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
4031
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
4032
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
4033
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
4034
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
4035
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
4036
|
+
)
|
|
4037
|
+
|
|
4038
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
4039
|
+
local.resource_ids[join("/", segments)]
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
4043
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
resource "terraform_data" "operations_metadata" {
|
|
4048
|
+
# Fails the plan when the operations metadata has not been generated
|
|
4049
|
+
input = local.operations_file
|
|
4050
|
+
|
|
4051
|
+
lifecycle {
|
|
4052
|
+
precondition {
|
|
4053
|
+
condition = fileexists(local.operations_file)
|
|
4054
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
4055
|
+
}
|
|
4056
|
+
precondition {
|
|
4057
|
+
condition = length(local.operations) > 0
|
|
4058
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
4059
|
+
}
|
|
4060
|
+
precondition {
|
|
4061
|
+
condition = length(local.paths_too_deep) == 0
|
|
4062
|
+
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."
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
# API Gateway resources at path depth 0
|
|
4068
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
4069
|
+
for_each = local.path_nodes_by_depth[0]
|
|
4070
|
+
|
|
4071
|
+
rest_api_id = module.rest_api.api_id
|
|
4072
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
4073
|
+
path_part = each.value.part
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
# API Gateway resources at path depth 1
|
|
4077
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
4078
|
+
for_each = local.path_nodes_by_depth[1]
|
|
4079
|
+
|
|
4080
|
+
rest_api_id = module.rest_api.api_id
|
|
4081
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
4082
|
+
path_part = each.value.part
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
# API Gateway resources at path depth 2
|
|
4086
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
4087
|
+
for_each = local.path_nodes_by_depth[2]
|
|
4088
|
+
|
|
4089
|
+
rest_api_id = module.rest_api.api_id
|
|
4090
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
4091
|
+
path_part = each.value.part
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
# API Gateway resources at path depth 3
|
|
4095
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
4096
|
+
for_each = local.path_nodes_by_depth[3]
|
|
4097
|
+
|
|
4098
|
+
rest_api_id = module.rest_api.api_id
|
|
4099
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
4100
|
+
path_part = each.value.part
|
|
4101
|
+
}
|
|
4102
|
+
|
|
4103
|
+
# API Gateway resources at path depth 4
|
|
4104
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
4105
|
+
for_each = local.path_nodes_by_depth[4]
|
|
4106
|
+
|
|
4107
|
+
rest_api_id = module.rest_api.api_id
|
|
4108
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
4109
|
+
path_part = each.value.part
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
# API Gateway resources at path depth 5
|
|
4113
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
4114
|
+
for_each = local.path_nodes_by_depth[5]
|
|
4115
|
+
|
|
4116
|
+
rest_api_id = module.rest_api.api_id
|
|
4117
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
4118
|
+
path_part = each.value.part
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
# API Gateway resources at path depth 6
|
|
4122
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
4123
|
+
for_each = local.path_nodes_by_depth[6]
|
|
4124
|
+
|
|
4125
|
+
rest_api_id = module.rest_api.api_id
|
|
4126
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
4127
|
+
path_part = each.value.part
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
# API Gateway resources at path depth 7
|
|
4131
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
4132
|
+
for_each = local.path_nodes_by_depth[7]
|
|
4133
|
+
|
|
4134
|
+
rest_api_id = module.rest_api.api_id
|
|
4135
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
4136
|
+
path_part = each.value.part
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
# API Gateway resources at path depth 8
|
|
4140
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
4141
|
+
for_each = local.path_nodes_by_depth[8]
|
|
4142
|
+
|
|
4143
|
+
rest_api_id = module.rest_api.api_id
|
|
4144
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
4145
|
+
path_part = each.value.part
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
# API Gateway resources at path depth 9
|
|
4149
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
4150
|
+
for_each = local.path_nodes_by_depth[9]
|
|
4151
|
+
|
|
4152
|
+
rest_api_id = module.rest_api.api_id
|
|
4153
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
4154
|
+
path_part = each.value.part
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
# API Gateway resources at path depth 10
|
|
4158
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
4159
|
+
for_each = local.path_nodes_by_depth[10]
|
|
4160
|
+
|
|
4161
|
+
rest_api_id = module.rest_api.api_id
|
|
4162
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
4163
|
+
path_part = each.value.part
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
# API Gateway resources at path depth 11
|
|
4167
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
4168
|
+
for_each = local.path_nodes_by_depth[11]
|
|
4169
|
+
|
|
4170
|
+
rest_api_id = module.rest_api.api_id
|
|
4171
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
4172
|
+
path_part = each.value.part
|
|
4173
|
+
}
|
|
4174
|
+
|
|
4175
|
+
# API Gateway resources at path depth 12
|
|
4176
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
4177
|
+
for_each = local.path_nodes_by_depth[12]
|
|
4178
|
+
|
|
4179
|
+
rest_api_id = module.rest_api.api_id
|
|
4180
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
4181
|
+
path_part = each.value.part
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
# API Gateway resources at path depth 13
|
|
4185
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
4186
|
+
for_each = local.path_nodes_by_depth[13]
|
|
4187
|
+
|
|
4188
|
+
rest_api_id = module.rest_api.api_id
|
|
4189
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
4190
|
+
path_part = each.value.part
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
# API Gateway resources at path depth 14
|
|
4194
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
4195
|
+
for_each = local.path_nodes_by_depth[14]
|
|
4196
|
+
|
|
4197
|
+
rest_api_id = module.rest_api.api_id
|
|
4198
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
4199
|
+
path_part = each.value.part
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
# API Gateway resources at path depth 15
|
|
4203
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
4204
|
+
for_each = local.path_nodes_by_depth[15]
|
|
4205
|
+
|
|
4206
|
+
rest_api_id = module.rest_api.api_id
|
|
4207
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
4208
|
+
path_part = each.value.part
|
|
4209
|
+
}
|
|
4210
|
+
|
|
3245
4211
|
# Resources
|
|
3246
4212
|
|
|
3247
4213
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -3316,8 +4282,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
3316
4282
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
3317
4283
|
}
|
|
3318
4284
|
|
|
3319
|
-
# Lambda function
|
|
3320
4285
|
resource "aws_lambda_function" "api_lambda" {
|
|
4286
|
+
# One lambda function per operation, each serving just that operation
|
|
4287
|
+
for_each = local.operations
|
|
4288
|
+
|
|
3321
4289
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
3322
4290
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3323
4291
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -3326,8 +4294,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3326
4294
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3327
4295
|
s3_key = aws_s3_object.lambda_zip.key
|
|
3328
4296
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3329
|
-
function_name =
|
|
3330
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
4297
|
+
function_name = local.function_name[each.key]
|
|
4298
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
3331
4299
|
handler = "index.handler"
|
|
3332
4300
|
runtime = "nodejs22.x"
|
|
3333
4301
|
timeout = 30
|
|
@@ -3363,7 +4331,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3363
4331
|
|
|
3364
4332
|
# IAM role for Lambda execution
|
|
3365
4333
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
3366
|
-
|
|
4334
|
+
for_each = local.operations
|
|
4335
|
+
|
|
4336
|
+
name = local.role_name[each.key]
|
|
3367
4337
|
|
|
3368
4338
|
assume_role_policy = jsonencode({
|
|
3369
4339
|
Version = "2012-10-17"
|
|
@@ -3383,32 +4353,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
3383
4353
|
|
|
3384
4354
|
# Attach basic execution policy to Lambda role
|
|
3385
4355
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4356
|
+
for_each = local.operations
|
|
4357
|
+
|
|
3386
4358
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
3387
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4359
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3388
4360
|
}
|
|
3389
4361
|
|
|
3390
4362
|
# Attach X-Ray tracing policy to Lambda role
|
|
3391
4363
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4364
|
+
for_each = local.operations
|
|
4365
|
+
|
|
3392
4366
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
3393
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4367
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3394
4368
|
}
|
|
3395
4369
|
|
|
3396
4370
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
3397
4371
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
3398
|
-
|
|
4372
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
4373
|
+
|
|
3399
4374
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
3400
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4375
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3401
4376
|
}
|
|
3402
4377
|
|
|
3403
4378
|
# Additional IAM policies for Lambda (if provided)
|
|
3404
4379
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
4380
|
+
for_each = local.operations
|
|
4381
|
+
|
|
4382
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
4383
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
3408
4384
|
|
|
3409
4385
|
policy = jsonencode({
|
|
3410
4386
|
Version = "2012-10-17"
|
|
3411
|
-
Statement = local.
|
|
4387
|
+
Statement = local.lambda_policy_document_statements
|
|
3412
4388
|
})
|
|
3413
4389
|
}
|
|
3414
4390
|
|
|
@@ -3424,6 +4400,15 @@ locals {
|
|
|
3424
4400
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
3425
4401
|
}
|
|
3426
4402
|
] : [], var.additional_iam_policy_statements)
|
|
4403
|
+
|
|
4404
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
4405
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
4406
|
+
{
|
|
4407
|
+
Effect = "Deny"
|
|
4408
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
4409
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
4410
|
+
}
|
|
4411
|
+
]
|
|
3427
4412
|
}
|
|
3428
4413
|
|
|
3429
4414
|
# CloudWatch Log Group for Lambda
|
|
@@ -3431,63 +4416,69 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
3431
4416
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3432
4417
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
3433
4418
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
3434
|
-
|
|
3435
|
-
tags = var.tags
|
|
3436
|
-
}
|
|
4419
|
+
for_each = local.operations
|
|
3437
4420
|
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
# Create proxy resource (captures all paths)
|
|
3441
|
-
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
3442
|
-
rest_api_id = module.rest_api.api_id
|
|
3443
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
3444
|
-
path_part = "{proxy+}"
|
|
4421
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
4422
|
+
tags = var.tags
|
|
3445
4423
|
}
|
|
3446
4424
|
|
|
3447
|
-
# Lambda integration for REST API
|
|
3448
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3449
|
-
rest_api_id = module.rest_api.api_id
|
|
3450
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3451
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
3452
4425
|
|
|
3453
|
-
integration_http_method = "POST"
|
|
3454
|
-
type = "AWS_PROXY"
|
|
3455
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
3456
4426
|
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
# Method for proxy integration
|
|
3461
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
4427
|
+
# Method per operation
|
|
4428
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
3462
4429
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
4430
|
+
for_each = local.operations
|
|
4431
|
+
|
|
3463
4432
|
rest_api_id = module.rest_api.api_id
|
|
3464
|
-
resource_id =
|
|
3465
|
-
http_method =
|
|
4433
|
+
resource_id = local.operation_resource_id[each.key]
|
|
4434
|
+
http_method = upper(each.value.method)
|
|
3466
4435
|
|
|
3467
4436
|
authorization = "AWS_IAM"
|
|
3468
4437
|
|
|
4438
|
+
# Path parameters must be declared on the method
|
|
3469
4439
|
request_parameters = {
|
|
3470
|
-
|
|
4440
|
+
for segment in local.operation_segments[each.key] :
|
|
4441
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
4442
|
+
if startswith(segment, "{")
|
|
3471
4443
|
}
|
|
3472
4444
|
|
|
3473
4445
|
depends_on = []
|
|
3474
4446
|
}
|
|
3475
4447
|
|
|
3476
|
-
#
|
|
4448
|
+
# Lambda integration per operation
|
|
4449
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
4450
|
+
for_each = local.operations
|
|
4451
|
+
|
|
4452
|
+
rest_api_id = module.rest_api.api_id
|
|
4453
|
+
resource_id = local.operation_resource_id[each.key]
|
|
4454
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
4455
|
+
|
|
4456
|
+
integration_http_method = "POST"
|
|
4457
|
+
type = "AWS_PROXY"
|
|
4458
|
+
uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
4459
|
+
|
|
4460
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
3477
4464
|
resource "aws_api_gateway_method" "options_method" {
|
|
3478
4465
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
3479
4466
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
4467
|
+
for_each = local.resource_ids
|
|
4468
|
+
|
|
3480
4469
|
rest_api_id = module.rest_api.api_id
|
|
3481
|
-
resource_id =
|
|
4470
|
+
resource_id = each.value
|
|
3482
4471
|
http_method = "OPTIONS"
|
|
3483
4472
|
authorization = "NONE"
|
|
3484
4473
|
}
|
|
3485
4474
|
|
|
3486
|
-
# CORS integration for OPTIONS
|
|
4475
|
+
# CORS integration for OPTIONS methods
|
|
3487
4476
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
4477
|
+
for_each = local.resource_ids
|
|
4478
|
+
|
|
3488
4479
|
rest_api_id = module.rest_api.api_id
|
|
3489
|
-
resource_id =
|
|
3490
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4480
|
+
resource_id = each.value
|
|
4481
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3491
4482
|
|
|
3492
4483
|
type = "MOCK"
|
|
3493
4484
|
request_templates = {
|
|
@@ -3495,11 +4486,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
3495
4486
|
}
|
|
3496
4487
|
}
|
|
3497
4488
|
|
|
3498
|
-
# OPTIONS method
|
|
4489
|
+
# OPTIONS method responses
|
|
3499
4490
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
4491
|
+
for_each = local.resource_ids
|
|
4492
|
+
|
|
3500
4493
|
rest_api_id = module.rest_api.api_id
|
|
3501
|
-
resource_id =
|
|
3502
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4494
|
+
resource_id = each.value
|
|
4495
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3503
4496
|
status_code = "204"
|
|
3504
4497
|
|
|
3505
4498
|
response_parameters = {
|
|
@@ -3509,18 +4502,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
3509
4502
|
}
|
|
3510
4503
|
}
|
|
3511
4504
|
|
|
3512
|
-
# OPTIONS integration
|
|
4505
|
+
# OPTIONS integration responses
|
|
3513
4506
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
4507
|
+
for_each = local.resource_ids
|
|
4508
|
+
|
|
3514
4509
|
rest_api_id = module.rest_api.api_id
|
|
3515
|
-
resource_id =
|
|
3516
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3517
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
4510
|
+
resource_id = each.value
|
|
4511
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4512
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
3518
4513
|
|
|
3519
4514
|
response_parameters = {
|
|
3520
4515
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3521
4516
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3522
4517
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3523
4518
|
}
|
|
4519
|
+
|
|
4520
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
3524
4521
|
}
|
|
3525
4522
|
|
|
3526
4523
|
# API Gateway deployment
|
|
@@ -3528,13 +4525,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3528
4525
|
rest_api_id = module.rest_api.api_id
|
|
3529
4526
|
|
|
3530
4527
|
triggers = {
|
|
3531
|
-
redeployment = sha1(jsonencode(
|
|
3532
|
-
aws_api_gateway_resource.
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
4528
|
+
redeployment = sha1(jsonencode(concat(
|
|
4529
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
4530
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
4531
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
4532
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
4533
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
4534
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
4535
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
4536
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
4537
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
4538
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
4539
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
4540
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
4541
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
4542
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
4543
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
4544
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
4545
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
4546
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
4547
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
4548
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
4549
|
+
)))
|
|
3538
4550
|
}
|
|
3539
4551
|
|
|
3540
4552
|
lifecycle {
|
|
@@ -3542,7 +4554,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3542
4554
|
}
|
|
3543
4555
|
|
|
3544
4556
|
depends_on = [
|
|
3545
|
-
aws_api_gateway_method.
|
|
4557
|
+
aws_api_gateway_method.operation_methods,
|
|
3546
4558
|
aws_api_gateway_integration.lambda_integration,
|
|
3547
4559
|
aws_api_gateway_method.options_method,
|
|
3548
4560
|
aws_api_gateway_integration.options_integration,
|
|
@@ -3681,11 +4693,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
3681
4693
|
|
|
3682
4694
|
# Lambda permission for API Gateway to invoke the function
|
|
3683
4695
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
4696
|
+
for_each = local.operations
|
|
4697
|
+
|
|
3684
4698
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
3685
4699
|
action = "lambda:InvokeFunction"
|
|
3686
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4700
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3687
4701
|
principal = "apigateway.amazonaws.com"
|
|
3688
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
4702
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3689
4703
|
|
|
3690
4704
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
3691
4705
|
}
|
|
@@ -3697,7 +4711,7 @@ module "add_url_to_runtime_config" {
|
|
|
3697
4711
|
|
|
3698
4712
|
namespace = "connection"
|
|
3699
4713
|
key = "apis"
|
|
3700
|
-
value = { "TestApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
4714
|
+
value = { "TestApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
3701
4715
|
|
|
3702
4716
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
3703
4717
|
}
|
|
@@ -3750,83 +4764,83 @@ output "stage_id" {
|
|
|
3750
4764
|
value = aws_api_gateway_stage.api_stage.id
|
|
3751
4765
|
}
|
|
3752
4766
|
|
|
3753
|
-
# Lambda Function Outputs
|
|
3754
|
-
output "
|
|
3755
|
-
description = "
|
|
3756
|
-
value =
|
|
4767
|
+
# Lambda Function Outputs, keyed by operation name
|
|
4768
|
+
output "operations" {
|
|
4769
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
4770
|
+
value = keys(local.operations)
|
|
3757
4771
|
}
|
|
3758
4772
|
|
|
3759
|
-
output "
|
|
3760
|
-
description = "
|
|
3761
|
-
value = aws_lambda_function.api_lambda.
|
|
4773
|
+
output "lambda_function_names" {
|
|
4774
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
4775
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
3762
4776
|
}
|
|
3763
4777
|
|
|
3764
|
-
output "
|
|
3765
|
-
description = "
|
|
3766
|
-
value = aws_lambda_function.api_lambda.
|
|
4778
|
+
output "lambda_function_arns" {
|
|
4779
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
4780
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
3767
4781
|
}
|
|
3768
4782
|
|
|
3769
|
-
output "
|
|
3770
|
-
description = "
|
|
3771
|
-
value = aws_lambda_function.api_lambda.
|
|
4783
|
+
output "lambda_invoke_arns" {
|
|
4784
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
4785
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
3772
4786
|
}
|
|
3773
4787
|
|
|
3774
|
-
output "
|
|
3775
|
-
description = "
|
|
3776
|
-
value = aws_lambda_function.api_lambda.
|
|
4788
|
+
output "lambda_qualified_arns" {
|
|
4789
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
4790
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
3777
4791
|
}
|
|
3778
4792
|
|
|
3779
|
-
output "
|
|
3780
|
-
description = "
|
|
3781
|
-
value = aws_lambda_function.api_lambda.
|
|
4793
|
+
output "lambda_versions" {
|
|
4794
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
4795
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
3782
4796
|
}
|
|
3783
4797
|
|
|
3784
|
-
output "
|
|
3785
|
-
description = "
|
|
3786
|
-
value =
|
|
4798
|
+
output "lambda_source_code_hash" {
|
|
4799
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
4800
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
3787
4801
|
}
|
|
3788
4802
|
|
|
3789
|
-
# IAM Role Outputs
|
|
3790
|
-
output "
|
|
3791
|
-
description = "ARN of
|
|
3792
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
4803
|
+
# IAM Role Outputs, keyed by operation name
|
|
4804
|
+
output "lambda_execution_role_arns" {
|
|
4805
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
4806
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
3793
4807
|
}
|
|
3794
4808
|
|
|
3795
|
-
output "
|
|
3796
|
-
description = "Name of
|
|
3797
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
4809
|
+
output "lambda_execution_role_names" {
|
|
4810
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
4811
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
3798
4812
|
}
|
|
3799
4813
|
|
|
3800
4814
|
output "security_group_id" {
|
|
3801
|
-
description = "Security group ID
|
|
4815
|
+
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."
|
|
3802
4816
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
3803
4817
|
}
|
|
3804
4818
|
|
|
3805
|
-
# Integration Outputs
|
|
3806
|
-
output "
|
|
3807
|
-
description = "ID of
|
|
3808
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
4819
|
+
# Integration Outputs, keyed by operation name
|
|
4820
|
+
output "integration_ids" {
|
|
4821
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
4822
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
3809
4823
|
}
|
|
3810
4824
|
|
|
3811
|
-
output "
|
|
3812
|
-
description = "ID of
|
|
3813
|
-
value =
|
|
4825
|
+
output "method_ids" {
|
|
4826
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
4827
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
3814
4828
|
}
|
|
3815
4829
|
|
|
3816
|
-
output "
|
|
3817
|
-
description = "ID of
|
|
3818
|
-
value =
|
|
4830
|
+
output "resource_ids" {
|
|
4831
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
4832
|
+
value = local.resource_ids
|
|
3819
4833
|
}
|
|
3820
4834
|
|
|
3821
|
-
# CloudWatch Log Groups
|
|
3822
|
-
output "
|
|
3823
|
-
description = "Name of
|
|
3824
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
4835
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
4836
|
+
output "lambda_log_group_names" {
|
|
4837
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4838
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
3825
4839
|
}
|
|
3826
4840
|
|
|
3827
|
-
output "
|
|
3828
|
-
description = "ARN of
|
|
3829
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
4841
|
+
output "lambda_log_group_arns" {
|
|
4842
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4843
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
3830
4844
|
}
|
|
3831
4845
|
",
|
|
3832
4846
|
}
|
|
@@ -3837,10 +4851,18 @@ exports[`tsSmithyApiGenerator > terraform iac > should generate terraform with c
|
|
|
3837
4851
|
required_version = ">= 1.0"
|
|
3838
4852
|
|
|
3839
4853
|
required_providers {
|
|
4854
|
+
archive = {
|
|
4855
|
+
source = "hashicorp/archive"
|
|
4856
|
+
version = "2.8.0"
|
|
4857
|
+
}
|
|
3840
4858
|
aws = {
|
|
3841
4859
|
source = "hashicorp/aws"
|
|
3842
4860
|
version = "6.61.0"
|
|
3843
4861
|
}
|
|
4862
|
+
random = {
|
|
4863
|
+
source = "hashicorp/random"
|
|
4864
|
+
version = "3.9.0"
|
|
4865
|
+
}
|
|
3844
4866
|
}
|
|
3845
4867
|
}
|
|
3846
4868
|
|
|
@@ -3946,6 +4968,235 @@ resource "random_string" "suffix" {
|
|
|
3946
4968
|
upper = false
|
|
3947
4969
|
}
|
|
3948
4970
|
|
|
4971
|
+
locals {
|
|
4972
|
+
# Generated at build time from the API definition
|
|
4973
|
+
operations_file = "\${path.module}/../../../generated/custom-api/operations.json"
|
|
4974
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
4975
|
+
|
|
4976
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
4977
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
4978
|
+
|
|
4979
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
4980
|
+
"\${substr("CustomApi-\${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}"
|
|
4981
|
+
}
|
|
4982
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
4983
|
+
"\${substr("CustomApi-\${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}"
|
|
4984
|
+
}
|
|
4985
|
+
|
|
4986
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
4987
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
4988
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
4989
|
+
|
|
4990
|
+
path_nodes = merge([
|
|
4991
|
+
for op, segments in local.operation_segments : {
|
|
4992
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
4993
|
+
depth = i
|
|
4994
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
4995
|
+
part = segment
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
]...)
|
|
4999
|
+
|
|
5000
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
5001
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
5002
|
+
max_path_depth = 16
|
|
5003
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
5004
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
5005
|
+
} }
|
|
5006
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
5007
|
+
|
|
5008
|
+
resource_ids = merge(
|
|
5009
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
5010
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
5011
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
5012
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
5013
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
5014
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
5015
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
5016
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
5017
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
5018
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
5019
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
5020
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
5021
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
5022
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
5023
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
5024
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
5025
|
+
)
|
|
5026
|
+
|
|
5027
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
5028
|
+
local.resource_ids[join("/", segments)]
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
5032
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
|
|
5036
|
+
resource "terraform_data" "operations_metadata" {
|
|
5037
|
+
# Fails the plan when the operations metadata has not been generated
|
|
5038
|
+
input = local.operations_file
|
|
5039
|
+
|
|
5040
|
+
lifecycle {
|
|
5041
|
+
precondition {
|
|
5042
|
+
condition = fileexists(local.operations_file)
|
|
5043
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the CustomApi project to generate it."
|
|
5044
|
+
}
|
|
5045
|
+
precondition {
|
|
5046
|
+
condition = length(local.operations) > 0
|
|
5047
|
+
error_message = "No operations found in \${local.operations_file}. The CustomApi API must define at least one operation."
|
|
5048
|
+
}
|
|
5049
|
+
precondition {
|
|
5050
|
+
condition = length(local.paths_too_deep) == 0
|
|
5051
|
+
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."
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
|
|
5056
|
+
# API Gateway resources at path depth 0
|
|
5057
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
5058
|
+
for_each = local.path_nodes_by_depth[0]
|
|
5059
|
+
|
|
5060
|
+
rest_api_id = module.rest_api.api_id
|
|
5061
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
5062
|
+
path_part = each.value.part
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
# API Gateway resources at path depth 1
|
|
5066
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
5067
|
+
for_each = local.path_nodes_by_depth[1]
|
|
5068
|
+
|
|
5069
|
+
rest_api_id = module.rest_api.api_id
|
|
5070
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
5071
|
+
path_part = each.value.part
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
# API Gateway resources at path depth 2
|
|
5075
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
5076
|
+
for_each = local.path_nodes_by_depth[2]
|
|
5077
|
+
|
|
5078
|
+
rest_api_id = module.rest_api.api_id
|
|
5079
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
5080
|
+
path_part = each.value.part
|
|
5081
|
+
}
|
|
5082
|
+
|
|
5083
|
+
# API Gateway resources at path depth 3
|
|
5084
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
5085
|
+
for_each = local.path_nodes_by_depth[3]
|
|
5086
|
+
|
|
5087
|
+
rest_api_id = module.rest_api.api_id
|
|
5088
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
5089
|
+
path_part = each.value.part
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
# API Gateway resources at path depth 4
|
|
5093
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
5094
|
+
for_each = local.path_nodes_by_depth[4]
|
|
5095
|
+
|
|
5096
|
+
rest_api_id = module.rest_api.api_id
|
|
5097
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
5098
|
+
path_part = each.value.part
|
|
5099
|
+
}
|
|
5100
|
+
|
|
5101
|
+
# API Gateway resources at path depth 5
|
|
5102
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
5103
|
+
for_each = local.path_nodes_by_depth[5]
|
|
5104
|
+
|
|
5105
|
+
rest_api_id = module.rest_api.api_id
|
|
5106
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
5107
|
+
path_part = each.value.part
|
|
5108
|
+
}
|
|
5109
|
+
|
|
5110
|
+
# API Gateway resources at path depth 6
|
|
5111
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
5112
|
+
for_each = local.path_nodes_by_depth[6]
|
|
5113
|
+
|
|
5114
|
+
rest_api_id = module.rest_api.api_id
|
|
5115
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
5116
|
+
path_part = each.value.part
|
|
5117
|
+
}
|
|
5118
|
+
|
|
5119
|
+
# API Gateway resources at path depth 7
|
|
5120
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
5121
|
+
for_each = local.path_nodes_by_depth[7]
|
|
5122
|
+
|
|
5123
|
+
rest_api_id = module.rest_api.api_id
|
|
5124
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
5125
|
+
path_part = each.value.part
|
|
5126
|
+
}
|
|
5127
|
+
|
|
5128
|
+
# API Gateway resources at path depth 8
|
|
5129
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
5130
|
+
for_each = local.path_nodes_by_depth[8]
|
|
5131
|
+
|
|
5132
|
+
rest_api_id = module.rest_api.api_id
|
|
5133
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
5134
|
+
path_part = each.value.part
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
# API Gateway resources at path depth 9
|
|
5138
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
5139
|
+
for_each = local.path_nodes_by_depth[9]
|
|
5140
|
+
|
|
5141
|
+
rest_api_id = module.rest_api.api_id
|
|
5142
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
5143
|
+
path_part = each.value.part
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
# API Gateway resources at path depth 10
|
|
5147
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
5148
|
+
for_each = local.path_nodes_by_depth[10]
|
|
5149
|
+
|
|
5150
|
+
rest_api_id = module.rest_api.api_id
|
|
5151
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
5152
|
+
path_part = each.value.part
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
# API Gateway resources at path depth 11
|
|
5156
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
5157
|
+
for_each = local.path_nodes_by_depth[11]
|
|
5158
|
+
|
|
5159
|
+
rest_api_id = module.rest_api.api_id
|
|
5160
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
5161
|
+
path_part = each.value.part
|
|
5162
|
+
}
|
|
5163
|
+
|
|
5164
|
+
# API Gateway resources at path depth 12
|
|
5165
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
5166
|
+
for_each = local.path_nodes_by_depth[12]
|
|
5167
|
+
|
|
5168
|
+
rest_api_id = module.rest_api.api_id
|
|
5169
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
5170
|
+
path_part = each.value.part
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
# API Gateway resources at path depth 13
|
|
5174
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
5175
|
+
for_each = local.path_nodes_by_depth[13]
|
|
5176
|
+
|
|
5177
|
+
rest_api_id = module.rest_api.api_id
|
|
5178
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
5179
|
+
path_part = each.value.part
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
# API Gateway resources at path depth 14
|
|
5183
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
5184
|
+
for_each = local.path_nodes_by_depth[14]
|
|
5185
|
+
|
|
5186
|
+
rest_api_id = module.rest_api.api_id
|
|
5187
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
5188
|
+
path_part = each.value.part
|
|
5189
|
+
}
|
|
5190
|
+
|
|
5191
|
+
# API Gateway resources at path depth 15
|
|
5192
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
5193
|
+
for_each = local.path_nodes_by_depth[15]
|
|
5194
|
+
|
|
5195
|
+
rest_api_id = module.rest_api.api_id
|
|
5196
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
5197
|
+
path_part = each.value.part
|
|
5198
|
+
}
|
|
5199
|
+
|
|
3949
5200
|
# Resources
|
|
3950
5201
|
|
|
3951
5202
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -4020,8 +5271,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
4020
5271
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
4021
5272
|
}
|
|
4022
5273
|
|
|
4023
|
-
# Lambda function
|
|
4024
5274
|
resource "aws_lambda_function" "api_lambda" {
|
|
5275
|
+
# One lambda function per operation, each serving just that operation
|
|
5276
|
+
for_each = local.operations
|
|
5277
|
+
|
|
4025
5278
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
4026
5279
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4027
5280
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -4030,8 +5283,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4030
5283
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4031
5284
|
s3_key = aws_s3_object.lambda_zip.key
|
|
4032
5285
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4033
|
-
function_name =
|
|
4034
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
5286
|
+
function_name = local.function_name[each.key]
|
|
5287
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
4035
5288
|
handler = "index.handler"
|
|
4036
5289
|
runtime = "nodejs22.x"
|
|
4037
5290
|
timeout = 30
|
|
@@ -4067,7 +5320,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4067
5320
|
|
|
4068
5321
|
# IAM role for Lambda execution
|
|
4069
5322
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
4070
|
-
|
|
5323
|
+
for_each = local.operations
|
|
5324
|
+
|
|
5325
|
+
name = local.role_name[each.key]
|
|
4071
5326
|
|
|
4072
5327
|
assume_role_policy = jsonencode({
|
|
4073
5328
|
Version = "2012-10-17"
|
|
@@ -4087,32 +5342,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
4087
5342
|
|
|
4088
5343
|
# Attach basic execution policy to Lambda role
|
|
4089
5344
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
5345
|
+
for_each = local.operations
|
|
5346
|
+
|
|
4090
5347
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4091
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5348
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4092
5349
|
}
|
|
4093
5350
|
|
|
4094
5351
|
# Attach X-Ray tracing policy to Lambda role
|
|
4095
5352
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
5353
|
+
for_each = local.operations
|
|
5354
|
+
|
|
4096
5355
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4097
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5356
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4098
5357
|
}
|
|
4099
5358
|
|
|
4100
5359
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
4101
5360
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
4102
|
-
|
|
5361
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
5362
|
+
|
|
4103
5363
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
4104
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5364
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4105
5365
|
}
|
|
4106
5366
|
|
|
4107
5367
|
# Additional IAM policies for Lambda (if provided)
|
|
4108
5368
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
5369
|
+
for_each = local.operations
|
|
5370
|
+
|
|
5371
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
5372
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
4112
5373
|
|
|
4113
5374
|
policy = jsonencode({
|
|
4114
5375
|
Version = "2012-10-17"
|
|
4115
|
-
Statement = local.
|
|
5376
|
+
Statement = local.lambda_policy_document_statements
|
|
4116
5377
|
})
|
|
4117
5378
|
}
|
|
4118
5379
|
|
|
@@ -4128,6 +5389,15 @@ locals {
|
|
|
4128
5389
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
4129
5390
|
}
|
|
4130
5391
|
] : [], var.additional_iam_policy_statements)
|
|
5392
|
+
|
|
5393
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
5394
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
5395
|
+
{
|
|
5396
|
+
Effect = "Deny"
|
|
5397
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
5398
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
5399
|
+
}
|
|
5400
|
+
]
|
|
4131
5401
|
}
|
|
4132
5402
|
|
|
4133
5403
|
# CloudWatch Log Group for Lambda
|
|
@@ -4135,8 +5405,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
4135
5405
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4136
5406
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4137
5407
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4138
|
-
|
|
4139
|
-
|
|
5408
|
+
for_each = local.operations
|
|
5409
|
+
|
|
5410
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
5411
|
+
tags = var.tags
|
|
4140
5412
|
}
|
|
4141
5413
|
|
|
4142
5414
|
|
|
@@ -4233,58 +5505,62 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
4233
5505
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
4234
5506
|
}
|
|
4235
5507
|
|
|
4236
|
-
#
|
|
4237
|
-
resource "
|
|
4238
|
-
rest_api_id = module.rest_api.api_id
|
|
4239
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
4240
|
-
path_part = "{proxy+}"
|
|
4241
|
-
}
|
|
4242
|
-
|
|
4243
|
-
# Lambda integration for REST API
|
|
4244
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
4245
|
-
rest_api_id = module.rest_api.api_id
|
|
4246
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4247
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
4248
|
-
|
|
4249
|
-
integration_http_method = "POST"
|
|
4250
|
-
type = "AWS_PROXY"
|
|
4251
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
4252
|
-
|
|
4253
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
4254
|
-
}
|
|
4255
|
-
|
|
4256
|
-
# Method for proxy integration
|
|
4257
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
5508
|
+
# Method per operation
|
|
5509
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
4258
5510
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
5511
|
+
for_each = local.operations
|
|
5512
|
+
|
|
4259
5513
|
rest_api_id = module.rest_api.api_id
|
|
4260
|
-
resource_id =
|
|
4261
|
-
http_method =
|
|
5514
|
+
resource_id = local.operation_resource_id[each.key]
|
|
5515
|
+
http_method = upper(each.value.method)
|
|
4262
5516
|
|
|
4263
5517
|
authorization = "CUSTOM"
|
|
4264
5518
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
4265
5519
|
|
|
5520
|
+
# Path parameters must be declared on the method
|
|
4266
5521
|
request_parameters = {
|
|
4267
|
-
|
|
5522
|
+
for segment in local.operation_segments[each.key] :
|
|
5523
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
5524
|
+
if startswith(segment, "{")
|
|
4268
5525
|
}
|
|
4269
5526
|
|
|
4270
5527
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
4271
5528
|
}
|
|
4272
5529
|
|
|
4273
|
-
#
|
|
5530
|
+
# Lambda integration per operation
|
|
5531
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
5532
|
+
for_each = local.operations
|
|
5533
|
+
|
|
5534
|
+
rest_api_id = module.rest_api.api_id
|
|
5535
|
+
resource_id = local.operation_resource_id[each.key]
|
|
5536
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
5537
|
+
|
|
5538
|
+
integration_http_method = "POST"
|
|
5539
|
+
type = "AWS_PROXY"
|
|
5540
|
+
uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
5541
|
+
|
|
5542
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
4274
5546
|
resource "aws_api_gateway_method" "options_method" {
|
|
4275
5547
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
4276
5548
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
5549
|
+
for_each = local.resource_ids
|
|
5550
|
+
|
|
4277
5551
|
rest_api_id = module.rest_api.api_id
|
|
4278
|
-
resource_id =
|
|
5552
|
+
resource_id = each.value
|
|
4279
5553
|
http_method = "OPTIONS"
|
|
4280
5554
|
authorization = "NONE"
|
|
4281
5555
|
}
|
|
4282
5556
|
|
|
4283
|
-
# CORS integration for OPTIONS
|
|
5557
|
+
# CORS integration for OPTIONS methods
|
|
4284
5558
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
5559
|
+
for_each = local.resource_ids
|
|
5560
|
+
|
|
4285
5561
|
rest_api_id = module.rest_api.api_id
|
|
4286
|
-
resource_id =
|
|
4287
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
5562
|
+
resource_id = each.value
|
|
5563
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4288
5564
|
|
|
4289
5565
|
type = "MOCK"
|
|
4290
5566
|
request_templates = {
|
|
@@ -4292,11 +5568,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
4292
5568
|
}
|
|
4293
5569
|
}
|
|
4294
5570
|
|
|
4295
|
-
# OPTIONS method
|
|
5571
|
+
# OPTIONS method responses
|
|
4296
5572
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
5573
|
+
for_each = local.resource_ids
|
|
5574
|
+
|
|
4297
5575
|
rest_api_id = module.rest_api.api_id
|
|
4298
|
-
resource_id =
|
|
4299
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
5576
|
+
resource_id = each.value
|
|
5577
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4300
5578
|
status_code = "204"
|
|
4301
5579
|
|
|
4302
5580
|
response_parameters = {
|
|
@@ -4306,18 +5584,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
4306
5584
|
}
|
|
4307
5585
|
}
|
|
4308
5586
|
|
|
4309
|
-
# OPTIONS integration
|
|
5587
|
+
# OPTIONS integration responses
|
|
4310
5588
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
5589
|
+
for_each = local.resource_ids
|
|
5590
|
+
|
|
4311
5591
|
rest_api_id = module.rest_api.api_id
|
|
4312
|
-
resource_id =
|
|
4313
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4314
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
5592
|
+
resource_id = each.value
|
|
5593
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
5594
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
4315
5595
|
|
|
4316
5596
|
response_parameters = {
|
|
4317
5597
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
4318
5598
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
4319
5599
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
4320
5600
|
}
|
|
5601
|
+
|
|
5602
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
4321
5603
|
}
|
|
4322
5604
|
|
|
4323
5605
|
# API Gateway deployment
|
|
@@ -4325,13 +5607,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4325
5607
|
rest_api_id = module.rest_api.api_id
|
|
4326
5608
|
|
|
4327
5609
|
triggers = {
|
|
4328
|
-
redeployment = sha1(jsonencode(
|
|
4329
|
-
aws_api_gateway_resource.
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
5610
|
+
redeployment = sha1(jsonencode(concat(
|
|
5611
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
5612
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
5613
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
5614
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
5615
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
5616
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
5617
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
5618
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
5619
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
5620
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
5621
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
5622
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
5623
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
5624
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
5625
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
5626
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
5627
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
5628
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
5629
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
5630
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
5631
|
+
)))
|
|
4335
5632
|
}
|
|
4336
5633
|
|
|
4337
5634
|
lifecycle {
|
|
@@ -4339,7 +5636,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4339
5636
|
}
|
|
4340
5637
|
|
|
4341
5638
|
depends_on = [
|
|
4342
|
-
aws_api_gateway_method.
|
|
5639
|
+
aws_api_gateway_method.operation_methods,
|
|
4343
5640
|
aws_api_gateway_integration.lambda_integration,
|
|
4344
5641
|
aws_api_gateway_method.options_method,
|
|
4345
5642
|
aws_api_gateway_integration.options_integration,
|
|
@@ -4468,11 +5765,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
4468
5765
|
|
|
4469
5766
|
# Lambda permission for API Gateway to invoke the function
|
|
4470
5767
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
5768
|
+
for_each = local.operations
|
|
5769
|
+
|
|
4471
5770
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
4472
5771
|
action = "lambda:InvokeFunction"
|
|
4473
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5772
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
4474
5773
|
principal = "apigateway.amazonaws.com"
|
|
4475
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
5774
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4476
5775
|
|
|
4477
5776
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
4478
5777
|
}
|
|
@@ -4484,7 +5783,7 @@ module "add_url_to_runtime_config" {
|
|
|
4484
5783
|
|
|
4485
5784
|
namespace = "connection"
|
|
4486
5785
|
key = "apis"
|
|
4487
|
-
value = { "CustomApi" = aws_api_gateway_stage.api_stage.invoke_url }
|
|
5786
|
+
value = { "CustomApi" = "\${aws_api_gateway_stage.api_stage.invoke_url}/" }
|
|
4488
5787
|
|
|
4489
5788
|
depends_on = [aws_api_gateway_stage.api_stage]
|
|
4490
5789
|
}
|
|
@@ -4537,83 +5836,83 @@ output "stage_id" {
|
|
|
4537
5836
|
value = aws_api_gateway_stage.api_stage.id
|
|
4538
5837
|
}
|
|
4539
5838
|
|
|
4540
|
-
# Lambda Function Outputs
|
|
4541
|
-
output "
|
|
4542
|
-
description = "
|
|
4543
|
-
value =
|
|
5839
|
+
# Lambda Function Outputs, keyed by operation name
|
|
5840
|
+
output "operations" {
|
|
5841
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
5842
|
+
value = keys(local.operations)
|
|
4544
5843
|
}
|
|
4545
5844
|
|
|
4546
|
-
output "
|
|
4547
|
-
description = "
|
|
4548
|
-
value = aws_lambda_function.api_lambda.
|
|
5845
|
+
output "lambda_function_names" {
|
|
5846
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
5847
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
4549
5848
|
}
|
|
4550
5849
|
|
|
4551
|
-
output "
|
|
4552
|
-
description = "
|
|
4553
|
-
value = aws_lambda_function.api_lambda.
|
|
5850
|
+
output "lambda_function_arns" {
|
|
5851
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
5852
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
4554
5853
|
}
|
|
4555
5854
|
|
|
4556
|
-
output "
|
|
4557
|
-
description = "
|
|
4558
|
-
value = aws_lambda_function.api_lambda.
|
|
5855
|
+
output "lambda_invoke_arns" {
|
|
5856
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
5857
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
4559
5858
|
}
|
|
4560
5859
|
|
|
4561
|
-
output "
|
|
4562
|
-
description = "
|
|
4563
|
-
value = aws_lambda_function.api_lambda.
|
|
5860
|
+
output "lambda_qualified_arns" {
|
|
5861
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
5862
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
4564
5863
|
}
|
|
4565
5864
|
|
|
4566
|
-
output "
|
|
4567
|
-
description = "
|
|
4568
|
-
value = aws_lambda_function.api_lambda.
|
|
5865
|
+
output "lambda_versions" {
|
|
5866
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
5867
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
4569
5868
|
}
|
|
4570
5869
|
|
|
4571
|
-
output "
|
|
4572
|
-
description = "
|
|
4573
|
-
value =
|
|
5870
|
+
output "lambda_source_code_hash" {
|
|
5871
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
5872
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
4574
5873
|
}
|
|
4575
5874
|
|
|
4576
|
-
# IAM Role Outputs
|
|
4577
|
-
output "
|
|
4578
|
-
description = "ARN of
|
|
4579
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
5875
|
+
# IAM Role Outputs, keyed by operation name
|
|
5876
|
+
output "lambda_execution_role_arns" {
|
|
5877
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
5878
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
4580
5879
|
}
|
|
4581
5880
|
|
|
4582
|
-
output "
|
|
4583
|
-
description = "Name of
|
|
4584
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
5881
|
+
output "lambda_execution_role_names" {
|
|
5882
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
5883
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
4585
5884
|
}
|
|
4586
5885
|
|
|
4587
5886
|
output "security_group_id" {
|
|
4588
|
-
description = "Security group ID
|
|
5887
|
+
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."
|
|
4589
5888
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
4590
5889
|
}
|
|
4591
5890
|
|
|
4592
|
-
# Integration Outputs
|
|
4593
|
-
output "
|
|
4594
|
-
description = "ID of
|
|
4595
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
5891
|
+
# Integration Outputs, keyed by operation name
|
|
5892
|
+
output "integration_ids" {
|
|
5893
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
5894
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
4596
5895
|
}
|
|
4597
5896
|
|
|
4598
|
-
output "
|
|
4599
|
-
description = "ID of
|
|
4600
|
-
value =
|
|
5897
|
+
output "method_ids" {
|
|
5898
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
5899
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
4601
5900
|
}
|
|
4602
5901
|
|
|
4603
|
-
output "
|
|
4604
|
-
description = "ID of
|
|
4605
|
-
value =
|
|
5902
|
+
output "resource_ids" {
|
|
5903
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
5904
|
+
value = local.resource_ids
|
|
4606
5905
|
}
|
|
4607
5906
|
|
|
4608
|
-
# CloudWatch Log Groups
|
|
4609
|
-
output "
|
|
4610
|
-
description = "Name of
|
|
4611
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
5907
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
5908
|
+
output "lambda_log_group_names" {
|
|
5909
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5910
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
4612
5911
|
}
|
|
4613
5912
|
|
|
4614
|
-
output "
|
|
4615
|
-
description = "ARN of
|
|
4616
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
5913
|
+
output "lambda_log_group_arns" {
|
|
5914
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5915
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
4617
5916
|
}
|
|
4618
5917
|
"
|
|
4619
5918
|
`;
|