@aws/nx-plugin 1.0.0-rc.84 → 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 +6 -1
- package/package.json +1 -1
- package/src/internal/test-matrix/generator.js +16 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- 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/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/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1604 -559
- package/src/py/fast-api/generator.js +2 -1
- package/src/py/fast-api/generator.js.map +1 -1
- 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 +1548 -440
- 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 +2 -0
- package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
- package/src/terraform/project/files/application/src/providers.tf.template +2 -0
- package/src/terraform/project/files/checkov/checkov.yml.template +8 -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 +1552 -525
- 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/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 +243 -3
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +428 -1
- 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
|
@@ -840,6 +840,235 @@ resource "random_string" "suffix" {
|
|
|
840
840
|
upper = false
|
|
841
841
|
}
|
|
842
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
|
+
|
|
843
1072
|
# Resources
|
|
844
1073
|
|
|
845
1074
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -914,8 +1143,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
914
1143
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
915
1144
|
}
|
|
916
1145
|
|
|
917
|
-
# Lambda function
|
|
918
1146
|
resource "aws_lambda_function" "api_lambda" {
|
|
1147
|
+
# One lambda function per operation, each serving just that operation
|
|
1148
|
+
for_each = local.operations
|
|
1149
|
+
|
|
919
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
|
|
920
1151
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
921
1152
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -924,8 +1155,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
924
1155
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
925
1156
|
s3_key = aws_s3_object.lambda_zip.key
|
|
926
1157
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
927
|
-
function_name =
|
|
928
|
-
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
|
|
929
1160
|
handler = "index.handler"
|
|
930
1161
|
runtime = "nodejs22.x"
|
|
931
1162
|
timeout = 30
|
|
@@ -961,7 +1192,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
961
1192
|
|
|
962
1193
|
# IAM role for Lambda execution
|
|
963
1194
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
964
|
-
|
|
1195
|
+
for_each = local.operations
|
|
1196
|
+
|
|
1197
|
+
name = local.role_name[each.key]
|
|
965
1198
|
|
|
966
1199
|
assume_role_policy = jsonencode({
|
|
967
1200
|
Version = "2012-10-17"
|
|
@@ -981,32 +1214,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
981
1214
|
|
|
982
1215
|
# Attach basic execution policy to Lambda role
|
|
983
1216
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
1217
|
+
for_each = local.operations
|
|
1218
|
+
|
|
984
1219
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
985
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1220
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
986
1221
|
}
|
|
987
1222
|
|
|
988
1223
|
# Attach X-Ray tracing policy to Lambda role
|
|
989
1224
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
1225
|
+
for_each = local.operations
|
|
1226
|
+
|
|
990
1227
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
991
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1228
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
992
1229
|
}
|
|
993
1230
|
|
|
994
1231
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
995
1232
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
996
|
-
|
|
1233
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
1234
|
+
|
|
997
1235
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
998
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1236
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
999
1237
|
}
|
|
1000
1238
|
|
|
1001
1239
|
# Additional IAM policies for Lambda (if provided)
|
|
1002
1240
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
|
1006
1245
|
|
|
1007
1246
|
policy = jsonencode({
|
|
1008
1247
|
Version = "2012-10-17"
|
|
1009
|
-
Statement = local.
|
|
1248
|
+
Statement = local.lambda_policy_document_statements
|
|
1010
1249
|
})
|
|
1011
1250
|
}
|
|
1012
1251
|
|
|
@@ -1022,6 +1261,15 @@ locals {
|
|
|
1022
1261
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
1023
1262
|
}
|
|
1024
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
|
+
]
|
|
1025
1273
|
}
|
|
1026
1274
|
|
|
1027
1275
|
# CloudWatch Log Group for Lambda
|
|
@@ -1029,8 +1277,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
1029
1277
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
1030
1278
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1031
1279
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1032
|
-
|
|
1033
|
-
|
|
1280
|
+
for_each = local.operations
|
|
1281
|
+
|
|
1282
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
1283
|
+
tags = var.tags
|
|
1034
1284
|
}
|
|
1035
1285
|
|
|
1036
1286
|
|
|
@@ -1127,58 +1377,62 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
1127
1377
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
1128
1378
|
}
|
|
1129
1379
|
|
|
1130
|
-
#
|
|
1131
|
-
resource "
|
|
1132
|
-
rest_api_id = module.rest_api.api_id
|
|
1133
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
1134
|
-
path_part = "{proxy+}"
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
# Lambda integration for REST API
|
|
1138
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
1139
|
-
rest_api_id = module.rest_api.api_id
|
|
1140
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
1141
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
1142
|
-
|
|
1143
|
-
integration_http_method = "POST"
|
|
1144
|
-
type = "AWS_PROXY"
|
|
1145
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
1146
|
-
|
|
1147
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
# Method for proxy integration
|
|
1151
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
1380
|
+
# Method per operation
|
|
1381
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
1152
1382
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
1383
|
+
for_each = local.operations
|
|
1384
|
+
|
|
1153
1385
|
rest_api_id = module.rest_api.api_id
|
|
1154
|
-
resource_id =
|
|
1155
|
-
http_method =
|
|
1386
|
+
resource_id = local.operation_resource_id[each.key]
|
|
1387
|
+
http_method = upper(each.value.method)
|
|
1156
1388
|
|
|
1157
1389
|
authorization = "CUSTOM"
|
|
1158
1390
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
1159
1391
|
|
|
1392
|
+
# Path parameters must be declared on the method
|
|
1160
1393
|
request_parameters = {
|
|
1161
|
-
|
|
1394
|
+
for segment in local.operation_segments[each.key] :
|
|
1395
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
1396
|
+
if startswith(segment, "{")
|
|
1162
1397
|
}
|
|
1163
1398
|
|
|
1164
1399
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
1165
1400
|
}
|
|
1166
1401
|
|
|
1167
|
-
#
|
|
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
|
|
1168
1418
|
resource "aws_api_gateway_method" "options_method" {
|
|
1169
1419
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
1170
1420
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
1421
|
+
for_each = local.resource_ids
|
|
1422
|
+
|
|
1171
1423
|
rest_api_id = module.rest_api.api_id
|
|
1172
|
-
resource_id =
|
|
1424
|
+
resource_id = each.value
|
|
1173
1425
|
http_method = "OPTIONS"
|
|
1174
1426
|
authorization = "NONE"
|
|
1175
1427
|
}
|
|
1176
1428
|
|
|
1177
|
-
# CORS integration for OPTIONS
|
|
1429
|
+
# CORS integration for OPTIONS methods
|
|
1178
1430
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
1431
|
+
for_each = local.resource_ids
|
|
1432
|
+
|
|
1179
1433
|
rest_api_id = module.rest_api.api_id
|
|
1180
|
-
resource_id =
|
|
1181
|
-
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
|
|
1182
1436
|
|
|
1183
1437
|
type = "MOCK"
|
|
1184
1438
|
request_templates = {
|
|
@@ -1186,11 +1440,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
1186
1440
|
}
|
|
1187
1441
|
}
|
|
1188
1442
|
|
|
1189
|
-
# OPTIONS method
|
|
1443
|
+
# OPTIONS method responses
|
|
1190
1444
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
1445
|
+
for_each = local.resource_ids
|
|
1446
|
+
|
|
1191
1447
|
rest_api_id = module.rest_api.api_id
|
|
1192
|
-
resource_id =
|
|
1193
|
-
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
|
|
1194
1450
|
status_code = "204"
|
|
1195
1451
|
|
|
1196
1452
|
response_parameters = {
|
|
@@ -1200,18 +1456,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
1200
1456
|
}
|
|
1201
1457
|
}
|
|
1202
1458
|
|
|
1203
|
-
# OPTIONS integration
|
|
1459
|
+
# OPTIONS integration responses
|
|
1204
1460
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
1461
|
+
for_each = local.resource_ids
|
|
1462
|
+
|
|
1205
1463
|
rest_api_id = module.rest_api.api_id
|
|
1206
|
-
resource_id =
|
|
1207
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
1208
|
-
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
|
|
1209
1467
|
|
|
1210
1468
|
response_parameters = {
|
|
1211
1469
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
1212
1470
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
1213
1471
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
1214
1472
|
}
|
|
1473
|
+
|
|
1474
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
1215
1475
|
}
|
|
1216
1476
|
|
|
1217
1477
|
# API Gateway deployment
|
|
@@ -1219,13 +1479,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
1219
1479
|
rest_api_id = module.rest_api.api_id
|
|
1220
1480
|
|
|
1221
1481
|
triggers = {
|
|
1222
|
-
redeployment = sha1(jsonencode(
|
|
1223
|
-
aws_api_gateway_resource.
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
+
)))
|
|
1229
1504
|
}
|
|
1230
1505
|
|
|
1231
1506
|
lifecycle {
|
|
@@ -1233,7 +1508,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
1233
1508
|
}
|
|
1234
1509
|
|
|
1235
1510
|
depends_on = [
|
|
1236
|
-
aws_api_gateway_method.
|
|
1511
|
+
aws_api_gateway_method.operation_methods,
|
|
1237
1512
|
aws_api_gateway_integration.lambda_integration,
|
|
1238
1513
|
aws_api_gateway_method.options_method,
|
|
1239
1514
|
aws_api_gateway_integration.options_integration,
|
|
@@ -1362,11 +1637,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
1362
1637
|
|
|
1363
1638
|
# Lambda permission for API Gateway to invoke the function
|
|
1364
1639
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
1640
|
+
for_each = local.operations
|
|
1641
|
+
|
|
1365
1642
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
1366
1643
|
action = "lambda:InvokeFunction"
|
|
1367
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
1644
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
1368
1645
|
principal = "apigateway.amazonaws.com"
|
|
1369
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
1646
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
1370
1647
|
|
|
1371
1648
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
1372
1649
|
}
|
|
@@ -1431,83 +1708,83 @@ output "stage_id" {
|
|
|
1431
1708
|
value = aws_api_gateway_stage.api_stage.id
|
|
1432
1709
|
}
|
|
1433
1710
|
|
|
1434
|
-
# Lambda Function Outputs
|
|
1435
|
-
output "
|
|
1436
|
-
description = "
|
|
1437
|
-
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)
|
|
1438
1715
|
}
|
|
1439
1716
|
|
|
1440
|
-
output "
|
|
1441
|
-
description = "
|
|
1442
|
-
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 }
|
|
1443
1720
|
}
|
|
1444
1721
|
|
|
1445
|
-
output "
|
|
1446
|
-
description = "
|
|
1447
|
-
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 }
|
|
1448
1725
|
}
|
|
1449
1726
|
|
|
1450
|
-
output "
|
|
1451
|
-
description = "
|
|
1452
|
-
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 }
|
|
1453
1730
|
}
|
|
1454
1731
|
|
|
1455
|
-
output "
|
|
1456
|
-
description = "
|
|
1457
|
-
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 }
|
|
1458
1735
|
}
|
|
1459
1736
|
|
|
1460
|
-
output "
|
|
1461
|
-
description = "
|
|
1462
|
-
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 }
|
|
1463
1740
|
}
|
|
1464
1741
|
|
|
1465
|
-
output "
|
|
1466
|
-
description = "
|
|
1467
|
-
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
|
|
1468
1745
|
}
|
|
1469
1746
|
|
|
1470
|
-
# IAM Role Outputs
|
|
1471
|
-
output "
|
|
1472
|
-
description = "ARN of
|
|
1473
|
-
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 }
|
|
1474
1751
|
}
|
|
1475
1752
|
|
|
1476
|
-
output "
|
|
1477
|
-
description = "Name of
|
|
1478
|
-
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 }
|
|
1479
1756
|
}
|
|
1480
1757
|
|
|
1481
1758
|
output "security_group_id" {
|
|
1482
|
-
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."
|
|
1483
1760
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
1484
1761
|
}
|
|
1485
1762
|
|
|
1486
|
-
# Integration Outputs
|
|
1487
|
-
output "
|
|
1488
|
-
description = "ID of
|
|
1489
|
-
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 }
|
|
1490
1767
|
}
|
|
1491
1768
|
|
|
1492
|
-
output "
|
|
1493
|
-
description = "ID of
|
|
1494
|
-
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 }
|
|
1495
1772
|
}
|
|
1496
1773
|
|
|
1497
|
-
output "
|
|
1498
|
-
description = "ID of
|
|
1499
|
-
value =
|
|
1774
|
+
output "resource_ids" {
|
|
1775
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
1776
|
+
value = local.resource_ids
|
|
1500
1777
|
}
|
|
1501
1778
|
|
|
1502
|
-
# CloudWatch Log Groups
|
|
1503
|
-
output "
|
|
1504
|
-
description = "Name of
|
|
1505
|
-
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 }
|
|
1506
1783
|
}
|
|
1507
1784
|
|
|
1508
|
-
output "
|
|
1509
|
-
description = "ARN of
|
|
1510
|
-
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 }
|
|
1511
1788
|
}
|
|
1512
1789
|
"
|
|
1513
1790
|
`;
|
|
@@ -2399,6 +2676,235 @@ resource "random_string" "suffix" {
|
|
|
2399
2676
|
upper = false
|
|
2400
2677
|
}
|
|
2401
2678
|
|
|
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
|
+
|
|
2402
2908
|
# Resources
|
|
2403
2909
|
|
|
2404
2910
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -2473,8 +2979,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
2473
2979
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
2474
2980
|
}
|
|
2475
2981
|
|
|
2476
|
-
# Lambda function
|
|
2477
2982
|
resource "aws_lambda_function" "api_lambda" {
|
|
2983
|
+
# One lambda function per operation, each serving just that operation
|
|
2984
|
+
for_each = local.operations
|
|
2985
|
+
|
|
2478
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
|
|
2479
2987
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
2480
2988
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -2483,8 +2991,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2483
2991
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
2484
2992
|
s3_key = aws_s3_object.lambda_zip.key
|
|
2485
2993
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
2486
|
-
function_name =
|
|
2487
|
-
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
|
|
2488
2996
|
handler = "index.handler"
|
|
2489
2997
|
runtime = "nodejs22.x"
|
|
2490
2998
|
timeout = 30
|
|
@@ -2520,7 +3028,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2520
3028
|
|
|
2521
3029
|
# IAM role for Lambda execution
|
|
2522
3030
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
2523
|
-
|
|
3031
|
+
for_each = local.operations
|
|
3032
|
+
|
|
3033
|
+
name = local.role_name[each.key]
|
|
2524
3034
|
|
|
2525
3035
|
assume_role_policy = jsonencode({
|
|
2526
3036
|
Version = "2012-10-17"
|
|
@@ -2540,32 +3050,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
2540
3050
|
|
|
2541
3051
|
# Attach basic execution policy to Lambda role
|
|
2542
3052
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3053
|
+
for_each = local.operations
|
|
3054
|
+
|
|
2543
3055
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
2544
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3056
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2545
3057
|
}
|
|
2546
3058
|
|
|
2547
3059
|
# Attach X-Ray tracing policy to Lambda role
|
|
2548
3060
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3061
|
+
for_each = local.operations
|
|
3062
|
+
|
|
2549
3063
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
2550
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3064
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2551
3065
|
}
|
|
2552
3066
|
|
|
2553
3067
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
2554
3068
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
2555
|
-
|
|
3069
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
3070
|
+
|
|
2556
3071
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
2557
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3072
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2558
3073
|
}
|
|
2559
3074
|
|
|
2560
3075
|
# Additional IAM policies for Lambda (if provided)
|
|
2561
3076
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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
|
|
2565
3081
|
|
|
2566
3082
|
policy = jsonencode({
|
|
2567
3083
|
Version = "2012-10-17"
|
|
2568
|
-
Statement = local.
|
|
3084
|
+
Statement = local.lambda_policy_document_statements
|
|
2569
3085
|
})
|
|
2570
3086
|
}
|
|
2571
3087
|
|
|
@@ -2581,6 +3097,15 @@ locals {
|
|
|
2581
3097
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
2582
3098
|
}
|
|
2583
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
|
+
]
|
|
2584
3109
|
}
|
|
2585
3110
|
|
|
2586
3111
|
# CloudWatch Log Group for Lambda
|
|
@@ -2588,8 +3113,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
2588
3113
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2589
3114
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
2590
3115
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
2591
|
-
|
|
2592
|
-
|
|
3116
|
+
for_each = local.operations
|
|
3117
|
+
|
|
3118
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
3119
|
+
tags = var.tags
|
|
2593
3120
|
}
|
|
2594
3121
|
|
|
2595
3122
|
|
|
@@ -2602,32 +3129,14 @@ resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
|
2602
3129
|
identity_source = "method.request.header.Authorization"
|
|
2603
3130
|
}
|
|
2604
3131
|
|
|
2605
|
-
#
|
|
2606
|
-
resource "
|
|
2607
|
-
rest_api_id = module.rest_api.api_id
|
|
2608
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
2609
|
-
path_part = "{proxy+}"
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
# Lambda integration for REST API
|
|
2613
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
2614
|
-
rest_api_id = module.rest_api.api_id
|
|
2615
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2616
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
2617
|
-
|
|
2618
|
-
integration_http_method = "POST"
|
|
2619
|
-
type = "AWS_PROXY"
|
|
2620
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
2621
|
-
|
|
2622
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
# Method for proxy integration
|
|
2626
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
3132
|
+
# Method per operation
|
|
3133
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
2627
3134
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
3135
|
+
for_each = local.operations
|
|
3136
|
+
|
|
2628
3137
|
rest_api_id = module.rest_api.api_id
|
|
2629
|
-
resource_id =
|
|
2630
|
-
http_method =
|
|
3138
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3139
|
+
http_method = upper(each.value.method)
|
|
2631
3140
|
|
|
2632
3141
|
authorization = "COGNITO_USER_POOLS"
|
|
2633
3142
|
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
@@ -2635,40 +3144,64 @@ resource "aws_api_gateway_method" "proxy_method" {
|
|
|
2635
3144
|
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
2636
3145
|
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
2637
3146
|
|
|
3147
|
+
# Path parameters must be declared on the method
|
|
2638
3148
|
request_parameters = {
|
|
2639
|
-
|
|
3149
|
+
for segment in local.operation_segments[each.key] :
|
|
3150
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
3151
|
+
if startswith(segment, "{")
|
|
2640
3152
|
}
|
|
2641
3153
|
|
|
2642
3154
|
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
2643
3155
|
}
|
|
2644
3156
|
|
|
2645
|
-
#
|
|
2646
|
-
resource "
|
|
2647
|
-
|
|
2648
|
-
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
2649
|
-
rest_api_id = module.rest_api.api_id
|
|
2650
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
2651
|
-
http_method = "OPTIONS"
|
|
2652
|
-
authorization = "NONE"
|
|
2653
|
-
}
|
|
3157
|
+
# Lambda integration per operation
|
|
3158
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3159
|
+
for_each = local.operations
|
|
2654
3160
|
|
|
2655
|
-
# CORS integration for OPTIONS method
|
|
2656
|
-
resource "aws_api_gateway_integration" "options_integration" {
|
|
2657
3161
|
rest_api_id = module.rest_api.api_id
|
|
2658
|
-
resource_id =
|
|
2659
|
-
http_method = aws_api_gateway_method.
|
|
3162
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3163
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
2660
3164
|
|
|
2661
|
-
|
|
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
|
|
3173
|
+
resource "aws_api_gateway_method" "options_method" {
|
|
3174
|
+
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
3175
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
3176
|
+
for_each = local.resource_ids
|
|
3177
|
+
|
|
3178
|
+
rest_api_id = module.rest_api.api_id
|
|
3179
|
+
resource_id = each.value
|
|
3180
|
+
http_method = "OPTIONS"
|
|
3181
|
+
authorization = "NONE"
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
# CORS integration for OPTIONS methods
|
|
3185
|
+
resource "aws_api_gateway_integration" "options_integration" {
|
|
3186
|
+
for_each = local.resource_ids
|
|
3187
|
+
|
|
3188
|
+
rest_api_id = module.rest_api.api_id
|
|
3189
|
+
resource_id = each.value
|
|
3190
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3191
|
+
|
|
3192
|
+
type = "MOCK"
|
|
2662
3193
|
request_templates = {
|
|
2663
3194
|
"application/json" = "{\\"statusCode\\": 204}"
|
|
2664
3195
|
}
|
|
2665
3196
|
}
|
|
2666
3197
|
|
|
2667
|
-
# OPTIONS method
|
|
3198
|
+
# OPTIONS method responses
|
|
2668
3199
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
3200
|
+
for_each = local.resource_ids
|
|
3201
|
+
|
|
2669
3202
|
rest_api_id = module.rest_api.api_id
|
|
2670
|
-
resource_id =
|
|
2671
|
-
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
|
|
2672
3205
|
status_code = "204"
|
|
2673
3206
|
|
|
2674
3207
|
response_parameters = {
|
|
@@ -2678,18 +3211,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
2678
3211
|
}
|
|
2679
3212
|
}
|
|
2680
3213
|
|
|
2681
|
-
# OPTIONS integration
|
|
3214
|
+
# OPTIONS integration responses
|
|
2682
3215
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
3216
|
+
for_each = local.resource_ids
|
|
3217
|
+
|
|
2683
3218
|
rest_api_id = module.rest_api.api_id
|
|
2684
|
-
resource_id =
|
|
2685
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
2686
|
-
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
|
|
2687
3222
|
|
|
2688
3223
|
response_parameters = {
|
|
2689
3224
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
2690
3225
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
2691
3226
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
2692
3227
|
}
|
|
3228
|
+
|
|
3229
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
2693
3230
|
}
|
|
2694
3231
|
|
|
2695
3232
|
# API Gateway deployment
|
|
@@ -2697,13 +3234,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
2697
3234
|
rest_api_id = module.rest_api.api_id
|
|
2698
3235
|
|
|
2699
3236
|
triggers = {
|
|
2700
|
-
redeployment = sha1(jsonencode(
|
|
2701
|
-
aws_api_gateway_resource.
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
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
|
+
)))
|
|
2707
3259
|
}
|
|
2708
3260
|
|
|
2709
3261
|
lifecycle {
|
|
@@ -2711,7 +3263,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
2711
3263
|
}
|
|
2712
3264
|
|
|
2713
3265
|
depends_on = [
|
|
2714
|
-
aws_api_gateway_method.
|
|
3266
|
+
aws_api_gateway_method.operation_methods,
|
|
2715
3267
|
aws_api_gateway_integration.lambda_integration,
|
|
2716
3268
|
aws_api_gateway_method.options_method,
|
|
2717
3269
|
aws_api_gateway_integration.options_integration,
|
|
@@ -2839,11 +3391,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
2839
3391
|
|
|
2840
3392
|
# Lambda permission for API Gateway to invoke the function
|
|
2841
3393
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3394
|
+
for_each = local.operations
|
|
3395
|
+
|
|
2842
3396
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
2843
3397
|
action = "lambda:InvokeFunction"
|
|
2844
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3398
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
2845
3399
|
principal = "apigateway.amazonaws.com"
|
|
2846
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
3400
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
2847
3401
|
|
|
2848
3402
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
2849
3403
|
}
|
|
@@ -2908,83 +3462,83 @@ output "stage_id" {
|
|
|
2908
3462
|
value = aws_api_gateway_stage.api_stage.id
|
|
2909
3463
|
}
|
|
2910
3464
|
|
|
2911
|
-
# Lambda Function Outputs
|
|
2912
|
-
output "
|
|
2913
|
-
description = "
|
|
2914
|
-
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)
|
|
2915
3469
|
}
|
|
2916
3470
|
|
|
2917
|
-
output "
|
|
2918
|
-
description = "
|
|
2919
|
-
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 }
|
|
2920
3474
|
}
|
|
2921
3475
|
|
|
2922
|
-
output "
|
|
2923
|
-
description = "
|
|
2924
|
-
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 }
|
|
2925
3479
|
}
|
|
2926
3480
|
|
|
2927
|
-
output "
|
|
2928
|
-
description = "
|
|
2929
|
-
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 }
|
|
2930
3484
|
}
|
|
2931
3485
|
|
|
2932
|
-
output "
|
|
2933
|
-
description = "
|
|
2934
|
-
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 }
|
|
2935
3489
|
}
|
|
2936
3490
|
|
|
2937
|
-
output "
|
|
2938
|
-
description = "
|
|
2939
|
-
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 }
|
|
2940
3494
|
}
|
|
2941
3495
|
|
|
2942
|
-
output "
|
|
2943
|
-
description = "
|
|
2944
|
-
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
|
|
2945
3499
|
}
|
|
2946
3500
|
|
|
2947
|
-
# IAM Role Outputs
|
|
2948
|
-
output "
|
|
2949
|
-
description = "ARN of
|
|
2950
|
-
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 }
|
|
2951
3505
|
}
|
|
2952
3506
|
|
|
2953
|
-
output "
|
|
2954
|
-
description = "Name of
|
|
2955
|
-
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 }
|
|
2956
3510
|
}
|
|
2957
3511
|
|
|
2958
3512
|
output "security_group_id" {
|
|
2959
|
-
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."
|
|
2960
3514
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
2961
3515
|
}
|
|
2962
3516
|
|
|
2963
|
-
# Integration Outputs
|
|
2964
|
-
output "
|
|
2965
|
-
description = "ID of
|
|
2966
|
-
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 }
|
|
2967
3521
|
}
|
|
2968
3522
|
|
|
2969
|
-
output "
|
|
2970
|
-
description = "ID of
|
|
2971
|
-
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 }
|
|
2972
3526
|
}
|
|
2973
3527
|
|
|
2974
|
-
output "
|
|
2975
|
-
description = "ID of
|
|
2976
|
-
value =
|
|
3528
|
+
output "resource_ids" {
|
|
3529
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
3530
|
+
value = local.resource_ids
|
|
2977
3531
|
}
|
|
2978
3532
|
|
|
2979
|
-
# CloudWatch Log Groups
|
|
2980
|
-
output "
|
|
2981
|
-
description = "Name of
|
|
2982
|
-
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 }
|
|
2983
3537
|
}
|
|
2984
3538
|
|
|
2985
|
-
output "
|
|
2986
|
-
description = "ARN of
|
|
2987
|
-
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 }
|
|
2988
3542
|
}
|
|
2989
3543
|
",
|
|
2990
3544
|
}
|
|
@@ -3425,6 +3979,235 @@ resource "random_string" "suffix" {
|
|
|
3425
3979
|
upper = false
|
|
3426
3980
|
}
|
|
3427
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
|
+
|
|
3428
4211
|
# Resources
|
|
3429
4212
|
|
|
3430
4213
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -3499,8 +4282,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
3499
4282
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
3500
4283
|
}
|
|
3501
4284
|
|
|
3502
|
-
# Lambda function
|
|
3503
4285
|
resource "aws_lambda_function" "api_lambda" {
|
|
4286
|
+
# One lambda function per operation, each serving just that operation
|
|
4287
|
+
for_each = local.operations
|
|
4288
|
+
|
|
3504
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
|
|
3505
4290
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3506
4291
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -3509,8 +4294,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3509
4294
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3510
4295
|
s3_key = aws_s3_object.lambda_zip.key
|
|
3511
4296
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3512
|
-
function_name =
|
|
3513
|
-
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
|
|
3514
4299
|
handler = "index.handler"
|
|
3515
4300
|
runtime = "nodejs22.x"
|
|
3516
4301
|
timeout = 30
|
|
@@ -3546,7 +4331,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3546
4331
|
|
|
3547
4332
|
# IAM role for Lambda execution
|
|
3548
4333
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
3549
|
-
|
|
4334
|
+
for_each = local.operations
|
|
4335
|
+
|
|
4336
|
+
name = local.role_name[each.key]
|
|
3550
4337
|
|
|
3551
4338
|
assume_role_policy = jsonencode({
|
|
3552
4339
|
Version = "2012-10-17"
|
|
@@ -3566,32 +4353,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
3566
4353
|
|
|
3567
4354
|
# Attach basic execution policy to Lambda role
|
|
3568
4355
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4356
|
+
for_each = local.operations
|
|
4357
|
+
|
|
3569
4358
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
3570
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4359
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3571
4360
|
}
|
|
3572
4361
|
|
|
3573
4362
|
# Attach X-Ray tracing policy to Lambda role
|
|
3574
4363
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4364
|
+
for_each = local.operations
|
|
4365
|
+
|
|
3575
4366
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
3576
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4367
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3577
4368
|
}
|
|
3578
4369
|
|
|
3579
4370
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
3580
4371
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
3581
|
-
|
|
4372
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
4373
|
+
|
|
3582
4374
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
3583
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4375
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3584
4376
|
}
|
|
3585
4377
|
|
|
3586
4378
|
# Additional IAM policies for Lambda (if provided)
|
|
3587
4379
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
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
|
|
3591
4384
|
|
|
3592
4385
|
policy = jsonencode({
|
|
3593
4386
|
Version = "2012-10-17"
|
|
3594
|
-
Statement = local.
|
|
4387
|
+
Statement = local.lambda_policy_document_statements
|
|
3595
4388
|
})
|
|
3596
4389
|
}
|
|
3597
4390
|
|
|
@@ -3607,6 +4400,15 @@ locals {
|
|
|
3607
4400
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
3608
4401
|
}
|
|
3609
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
|
+
]
|
|
3610
4412
|
}
|
|
3611
4413
|
|
|
3612
4414
|
# CloudWatch Log Group for Lambda
|
|
@@ -3614,63 +4416,69 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
3614
4416
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3615
4417
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
3616
4418
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
3617
|
-
|
|
3618
|
-
tags = var.tags
|
|
3619
|
-
}
|
|
3620
|
-
|
|
4419
|
+
for_each = local.operations
|
|
3621
4420
|
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
resource "aws_api_gateway_resource" "proxy_resource" {
|
|
3625
|
-
rest_api_id = module.rest_api.api_id
|
|
3626
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
3627
|
-
path_part = "{proxy+}"
|
|
4421
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
4422
|
+
tags = var.tags
|
|
3628
4423
|
}
|
|
3629
4424
|
|
|
3630
|
-
# Lambda integration for REST API
|
|
3631
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3632
|
-
rest_api_id = module.rest_api.api_id
|
|
3633
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3634
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
3635
|
-
|
|
3636
|
-
integration_http_method = "POST"
|
|
3637
|
-
type = "AWS_PROXY"
|
|
3638
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
3639
4425
|
|
|
3640
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
3641
|
-
}
|
|
3642
4426
|
|
|
3643
|
-
# Method
|
|
3644
|
-
resource "aws_api_gateway_method" "
|
|
4427
|
+
# Method per operation
|
|
4428
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
3645
4429
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
4430
|
+
for_each = local.operations
|
|
4431
|
+
|
|
3646
4432
|
rest_api_id = module.rest_api.api_id
|
|
3647
|
-
resource_id =
|
|
3648
|
-
http_method =
|
|
4433
|
+
resource_id = local.operation_resource_id[each.key]
|
|
4434
|
+
http_method = upper(each.value.method)
|
|
3649
4435
|
|
|
3650
4436
|
authorization = "AWS_IAM"
|
|
3651
4437
|
|
|
4438
|
+
# Path parameters must be declared on the method
|
|
3652
4439
|
request_parameters = {
|
|
3653
|
-
|
|
4440
|
+
for segment in local.operation_segments[each.key] :
|
|
4441
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
4442
|
+
if startswith(segment, "{")
|
|
3654
4443
|
}
|
|
3655
4444
|
|
|
3656
4445
|
depends_on = []
|
|
3657
4446
|
}
|
|
3658
4447
|
|
|
3659
|
-
#
|
|
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
|
|
3660
4464
|
resource "aws_api_gateway_method" "options_method" {
|
|
3661
4465
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
3662
4466
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
4467
|
+
for_each = local.resource_ids
|
|
4468
|
+
|
|
3663
4469
|
rest_api_id = module.rest_api.api_id
|
|
3664
|
-
resource_id =
|
|
4470
|
+
resource_id = each.value
|
|
3665
4471
|
http_method = "OPTIONS"
|
|
3666
4472
|
authorization = "NONE"
|
|
3667
4473
|
}
|
|
3668
4474
|
|
|
3669
|
-
# CORS integration for OPTIONS
|
|
4475
|
+
# CORS integration for OPTIONS methods
|
|
3670
4476
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
4477
|
+
for_each = local.resource_ids
|
|
4478
|
+
|
|
3671
4479
|
rest_api_id = module.rest_api.api_id
|
|
3672
|
-
resource_id =
|
|
3673
|
-
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
|
|
3674
4482
|
|
|
3675
4483
|
type = "MOCK"
|
|
3676
4484
|
request_templates = {
|
|
@@ -3678,11 +4486,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
3678
4486
|
}
|
|
3679
4487
|
}
|
|
3680
4488
|
|
|
3681
|
-
# OPTIONS method
|
|
4489
|
+
# OPTIONS method responses
|
|
3682
4490
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
4491
|
+
for_each = local.resource_ids
|
|
4492
|
+
|
|
3683
4493
|
rest_api_id = module.rest_api.api_id
|
|
3684
|
-
resource_id =
|
|
3685
|
-
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
|
|
3686
4496
|
status_code = "204"
|
|
3687
4497
|
|
|
3688
4498
|
response_parameters = {
|
|
@@ -3692,18 +4502,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
3692
4502
|
}
|
|
3693
4503
|
}
|
|
3694
4504
|
|
|
3695
|
-
# OPTIONS integration
|
|
4505
|
+
# OPTIONS integration responses
|
|
3696
4506
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
4507
|
+
for_each = local.resource_ids
|
|
4508
|
+
|
|
3697
4509
|
rest_api_id = module.rest_api.api_id
|
|
3698
|
-
resource_id =
|
|
3699
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3700
|
-
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
|
|
3701
4513
|
|
|
3702
4514
|
response_parameters = {
|
|
3703
4515
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3704
4516
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3705
4517
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3706
4518
|
}
|
|
4519
|
+
|
|
4520
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
3707
4521
|
}
|
|
3708
4522
|
|
|
3709
4523
|
# API Gateway deployment
|
|
@@ -3711,13 +4525,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3711
4525
|
rest_api_id = module.rest_api.api_id
|
|
3712
4526
|
|
|
3713
4527
|
triggers = {
|
|
3714
|
-
redeployment = sha1(jsonencode(
|
|
3715
|
-
aws_api_gateway_resource.
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
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
|
+
)))
|
|
3721
4550
|
}
|
|
3722
4551
|
|
|
3723
4552
|
lifecycle {
|
|
@@ -3725,7 +4554,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3725
4554
|
}
|
|
3726
4555
|
|
|
3727
4556
|
depends_on = [
|
|
3728
|
-
aws_api_gateway_method.
|
|
4557
|
+
aws_api_gateway_method.operation_methods,
|
|
3729
4558
|
aws_api_gateway_integration.lambda_integration,
|
|
3730
4559
|
aws_api_gateway_method.options_method,
|
|
3731
4560
|
aws_api_gateway_integration.options_integration,
|
|
@@ -3864,11 +4693,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
3864
4693
|
|
|
3865
4694
|
# Lambda permission for API Gateway to invoke the function
|
|
3866
4695
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
4696
|
+
for_each = local.operations
|
|
4697
|
+
|
|
3867
4698
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
3868
4699
|
action = "lambda:InvokeFunction"
|
|
3869
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4700
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3870
4701
|
principal = "apigateway.amazonaws.com"
|
|
3871
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
4702
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3872
4703
|
|
|
3873
4704
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
3874
4705
|
}
|
|
@@ -3933,83 +4764,83 @@ output "stage_id" {
|
|
|
3933
4764
|
value = aws_api_gateway_stage.api_stage.id
|
|
3934
4765
|
}
|
|
3935
4766
|
|
|
3936
|
-
# Lambda Function Outputs
|
|
3937
|
-
output "
|
|
3938
|
-
description = "
|
|
3939
|
-
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)
|
|
3940
4771
|
}
|
|
3941
4772
|
|
|
3942
|
-
output "
|
|
3943
|
-
description = "
|
|
3944
|
-
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 }
|
|
3945
4776
|
}
|
|
3946
4777
|
|
|
3947
|
-
output "
|
|
3948
|
-
description = "
|
|
3949
|
-
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 }
|
|
3950
4781
|
}
|
|
3951
4782
|
|
|
3952
|
-
output "
|
|
3953
|
-
description = "
|
|
3954
|
-
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 }
|
|
3955
4786
|
}
|
|
3956
4787
|
|
|
3957
|
-
output "
|
|
3958
|
-
description = "
|
|
3959
|
-
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 }
|
|
3960
4791
|
}
|
|
3961
4792
|
|
|
3962
|
-
output "
|
|
3963
|
-
description = "
|
|
3964
|
-
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 }
|
|
3965
4796
|
}
|
|
3966
4797
|
|
|
3967
|
-
output "
|
|
3968
|
-
description = "
|
|
3969
|
-
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
|
|
3970
4801
|
}
|
|
3971
4802
|
|
|
3972
|
-
# IAM Role Outputs
|
|
3973
|
-
output "
|
|
3974
|
-
description = "ARN of
|
|
3975
|
-
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 }
|
|
3976
4807
|
}
|
|
3977
4808
|
|
|
3978
|
-
output "
|
|
3979
|
-
description = "Name of
|
|
3980
|
-
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 }
|
|
3981
4812
|
}
|
|
3982
4813
|
|
|
3983
4814
|
output "security_group_id" {
|
|
3984
|
-
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."
|
|
3985
4816
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
3986
4817
|
}
|
|
3987
4818
|
|
|
3988
|
-
# Integration Outputs
|
|
3989
|
-
output "
|
|
3990
|
-
description = "ID of
|
|
3991
|
-
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 }
|
|
3992
4823
|
}
|
|
3993
4824
|
|
|
3994
|
-
output "
|
|
3995
|
-
description = "ID of
|
|
3996
|
-
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 }
|
|
3997
4828
|
}
|
|
3998
4829
|
|
|
3999
|
-
output "
|
|
4000
|
-
description = "ID of
|
|
4001
|
-
value =
|
|
4830
|
+
output "resource_ids" {
|
|
4831
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
4832
|
+
value = local.resource_ids
|
|
4002
4833
|
}
|
|
4003
4834
|
|
|
4004
|
-
# CloudWatch Log Groups
|
|
4005
|
-
output "
|
|
4006
|
-
description = "Name of
|
|
4007
|
-
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 }
|
|
4008
4839
|
}
|
|
4009
4840
|
|
|
4010
|
-
output "
|
|
4011
|
-
description = "ARN of
|
|
4012
|
-
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 }
|
|
4013
4844
|
}
|
|
4014
4845
|
",
|
|
4015
4846
|
}
|
|
@@ -4137,6 +4968,235 @@ resource "random_string" "suffix" {
|
|
|
4137
4968
|
upper = false
|
|
4138
4969
|
}
|
|
4139
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
|
+
|
|
4140
5200
|
# Resources
|
|
4141
5201
|
|
|
4142
5202
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -4211,8 +5271,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
4211
5271
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
4212
5272
|
}
|
|
4213
5273
|
|
|
4214
|
-
# Lambda function
|
|
4215
5274
|
resource "aws_lambda_function" "api_lambda" {
|
|
5275
|
+
# One lambda function per operation, each serving just that operation
|
|
5276
|
+
for_each = local.operations
|
|
5277
|
+
|
|
4216
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
|
|
4217
5279
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4218
5280
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -4221,8 +5283,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4221
5283
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4222
5284
|
s3_key = aws_s3_object.lambda_zip.key
|
|
4223
5285
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4224
|
-
function_name =
|
|
4225
|
-
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
|
|
4226
5288
|
handler = "index.handler"
|
|
4227
5289
|
runtime = "nodejs22.x"
|
|
4228
5290
|
timeout = 30
|
|
@@ -4258,7 +5320,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4258
5320
|
|
|
4259
5321
|
# IAM role for Lambda execution
|
|
4260
5322
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
4261
|
-
|
|
5323
|
+
for_each = local.operations
|
|
5324
|
+
|
|
5325
|
+
name = local.role_name[each.key]
|
|
4262
5326
|
|
|
4263
5327
|
assume_role_policy = jsonencode({
|
|
4264
5328
|
Version = "2012-10-17"
|
|
@@ -4278,32 +5342,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
4278
5342
|
|
|
4279
5343
|
# Attach basic execution policy to Lambda role
|
|
4280
5344
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
5345
|
+
for_each = local.operations
|
|
5346
|
+
|
|
4281
5347
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4282
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5348
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4283
5349
|
}
|
|
4284
5350
|
|
|
4285
5351
|
# Attach X-Ray tracing policy to Lambda role
|
|
4286
5352
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
5353
|
+
for_each = local.operations
|
|
5354
|
+
|
|
4287
5355
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4288
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5356
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4289
5357
|
}
|
|
4290
5358
|
|
|
4291
5359
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
4292
5360
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
4293
|
-
|
|
5361
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
5362
|
+
|
|
4294
5363
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
4295
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5364
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4296
5365
|
}
|
|
4297
5366
|
|
|
4298
5367
|
# Additional IAM policies for Lambda (if provided)
|
|
4299
5368
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
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
|
|
4303
5373
|
|
|
4304
5374
|
policy = jsonencode({
|
|
4305
5375
|
Version = "2012-10-17"
|
|
4306
|
-
Statement = local.
|
|
5376
|
+
Statement = local.lambda_policy_document_statements
|
|
4307
5377
|
})
|
|
4308
5378
|
}
|
|
4309
5379
|
|
|
@@ -4319,6 +5389,15 @@ locals {
|
|
|
4319
5389
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
4320
5390
|
}
|
|
4321
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
|
+
]
|
|
4322
5401
|
}
|
|
4323
5402
|
|
|
4324
5403
|
# CloudWatch Log Group for Lambda
|
|
@@ -4326,8 +5405,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
4326
5405
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4327
5406
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4328
5407
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4329
|
-
|
|
4330
|
-
|
|
5408
|
+
for_each = local.operations
|
|
5409
|
+
|
|
5410
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
5411
|
+
tags = var.tags
|
|
4331
5412
|
}
|
|
4332
5413
|
|
|
4333
5414
|
|
|
@@ -4424,58 +5505,62 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
4424
5505
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
4425
5506
|
}
|
|
4426
5507
|
|
|
4427
|
-
#
|
|
4428
|
-
resource "
|
|
4429
|
-
rest_api_id = module.rest_api.api_id
|
|
4430
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
4431
|
-
path_part = "{proxy+}"
|
|
4432
|
-
}
|
|
4433
|
-
|
|
4434
|
-
# Lambda integration for REST API
|
|
4435
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
4436
|
-
rest_api_id = module.rest_api.api_id
|
|
4437
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4438
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
4439
|
-
|
|
4440
|
-
integration_http_method = "POST"
|
|
4441
|
-
type = "AWS_PROXY"
|
|
4442
|
-
uri = aws_lambda_function.api_lambda.invoke_arn
|
|
4443
|
-
|
|
4444
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
# Method for proxy integration
|
|
4448
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
5508
|
+
# Method per operation
|
|
5509
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
4449
5510
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
5511
|
+
for_each = local.operations
|
|
5512
|
+
|
|
4450
5513
|
rest_api_id = module.rest_api.api_id
|
|
4451
|
-
resource_id =
|
|
4452
|
-
http_method =
|
|
5514
|
+
resource_id = local.operation_resource_id[each.key]
|
|
5515
|
+
http_method = upper(each.value.method)
|
|
4453
5516
|
|
|
4454
5517
|
authorization = "CUSTOM"
|
|
4455
5518
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
4456
5519
|
|
|
5520
|
+
# Path parameters must be declared on the method
|
|
4457
5521
|
request_parameters = {
|
|
4458
|
-
|
|
5522
|
+
for segment in local.operation_segments[each.key] :
|
|
5523
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
5524
|
+
if startswith(segment, "{")
|
|
4459
5525
|
}
|
|
4460
5526
|
|
|
4461
5527
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
4462
5528
|
}
|
|
4463
5529
|
|
|
4464
|
-
#
|
|
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
|
|
4465
5546
|
resource "aws_api_gateway_method" "options_method" {
|
|
4466
5547
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
4467
5548
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
5549
|
+
for_each = local.resource_ids
|
|
5550
|
+
|
|
4468
5551
|
rest_api_id = module.rest_api.api_id
|
|
4469
|
-
resource_id =
|
|
5552
|
+
resource_id = each.value
|
|
4470
5553
|
http_method = "OPTIONS"
|
|
4471
5554
|
authorization = "NONE"
|
|
4472
5555
|
}
|
|
4473
5556
|
|
|
4474
|
-
# CORS integration for OPTIONS
|
|
5557
|
+
# CORS integration for OPTIONS methods
|
|
4475
5558
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
5559
|
+
for_each = local.resource_ids
|
|
5560
|
+
|
|
4476
5561
|
rest_api_id = module.rest_api.api_id
|
|
4477
|
-
resource_id =
|
|
4478
|
-
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
|
|
4479
5564
|
|
|
4480
5565
|
type = "MOCK"
|
|
4481
5566
|
request_templates = {
|
|
@@ -4483,11 +5568,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
4483
5568
|
}
|
|
4484
5569
|
}
|
|
4485
5570
|
|
|
4486
|
-
# OPTIONS method
|
|
5571
|
+
# OPTIONS method responses
|
|
4487
5572
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
5573
|
+
for_each = local.resource_ids
|
|
5574
|
+
|
|
4488
5575
|
rest_api_id = module.rest_api.api_id
|
|
4489
|
-
resource_id =
|
|
4490
|
-
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
|
|
4491
5578
|
status_code = "204"
|
|
4492
5579
|
|
|
4493
5580
|
response_parameters = {
|
|
@@ -4497,18 +5584,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
4497
5584
|
}
|
|
4498
5585
|
}
|
|
4499
5586
|
|
|
4500
|
-
# OPTIONS integration
|
|
5587
|
+
# OPTIONS integration responses
|
|
4501
5588
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
5589
|
+
for_each = local.resource_ids
|
|
5590
|
+
|
|
4502
5591
|
rest_api_id = module.rest_api.api_id
|
|
4503
|
-
resource_id =
|
|
4504
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4505
|
-
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
|
|
4506
5595
|
|
|
4507
5596
|
response_parameters = {
|
|
4508
5597
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
4509
5598
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
4510
5599
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
4511
5600
|
}
|
|
5601
|
+
|
|
5602
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
4512
5603
|
}
|
|
4513
5604
|
|
|
4514
5605
|
# API Gateway deployment
|
|
@@ -4516,13 +5607,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4516
5607
|
rest_api_id = module.rest_api.api_id
|
|
4517
5608
|
|
|
4518
5609
|
triggers = {
|
|
4519
|
-
redeployment = sha1(jsonencode(
|
|
4520
|
-
aws_api_gateway_resource.
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
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
|
+
)))
|
|
4526
5632
|
}
|
|
4527
5633
|
|
|
4528
5634
|
lifecycle {
|
|
@@ -4530,7 +5636,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4530
5636
|
}
|
|
4531
5637
|
|
|
4532
5638
|
depends_on = [
|
|
4533
|
-
aws_api_gateway_method.
|
|
5639
|
+
aws_api_gateway_method.operation_methods,
|
|
4534
5640
|
aws_api_gateway_integration.lambda_integration,
|
|
4535
5641
|
aws_api_gateway_method.options_method,
|
|
4536
5642
|
aws_api_gateway_integration.options_integration,
|
|
@@ -4659,11 +5765,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
4659
5765
|
|
|
4660
5766
|
# Lambda permission for API Gateway to invoke the function
|
|
4661
5767
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
5768
|
+
for_each = local.operations
|
|
5769
|
+
|
|
4662
5770
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
4663
5771
|
action = "lambda:InvokeFunction"
|
|
4664
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5772
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
4665
5773
|
principal = "apigateway.amazonaws.com"
|
|
4666
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
5774
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4667
5775
|
|
|
4668
5776
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
4669
5777
|
}
|
|
@@ -4728,83 +5836,83 @@ output "stage_id" {
|
|
|
4728
5836
|
value = aws_api_gateway_stage.api_stage.id
|
|
4729
5837
|
}
|
|
4730
5838
|
|
|
4731
|
-
# Lambda Function Outputs
|
|
4732
|
-
output "
|
|
4733
|
-
description = "
|
|
4734
|
-
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)
|
|
4735
5843
|
}
|
|
4736
5844
|
|
|
4737
|
-
output "
|
|
4738
|
-
description = "
|
|
4739
|
-
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 }
|
|
4740
5848
|
}
|
|
4741
5849
|
|
|
4742
|
-
output "
|
|
4743
|
-
description = "
|
|
4744
|
-
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 }
|
|
4745
5853
|
}
|
|
4746
5854
|
|
|
4747
|
-
output "
|
|
4748
|
-
description = "
|
|
4749
|
-
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 }
|
|
4750
5858
|
}
|
|
4751
5859
|
|
|
4752
|
-
output "
|
|
4753
|
-
description = "
|
|
4754
|
-
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 }
|
|
4755
5863
|
}
|
|
4756
5864
|
|
|
4757
|
-
output "
|
|
4758
|
-
description = "
|
|
4759
|
-
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 }
|
|
4760
5868
|
}
|
|
4761
5869
|
|
|
4762
|
-
output "
|
|
4763
|
-
description = "
|
|
4764
|
-
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
|
|
4765
5873
|
}
|
|
4766
5874
|
|
|
4767
|
-
# IAM Role Outputs
|
|
4768
|
-
output "
|
|
4769
|
-
description = "ARN of
|
|
4770
|
-
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 }
|
|
4771
5879
|
}
|
|
4772
5880
|
|
|
4773
|
-
output "
|
|
4774
|
-
description = "Name of
|
|
4775
|
-
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 }
|
|
4776
5884
|
}
|
|
4777
5885
|
|
|
4778
5886
|
output "security_group_id" {
|
|
4779
|
-
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."
|
|
4780
5888
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
4781
5889
|
}
|
|
4782
5890
|
|
|
4783
|
-
# Integration Outputs
|
|
4784
|
-
output "
|
|
4785
|
-
description = "ID of
|
|
4786
|
-
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 }
|
|
4787
5895
|
}
|
|
4788
5896
|
|
|
4789
|
-
output "
|
|
4790
|
-
description = "ID of
|
|
4791
|
-
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 }
|
|
4792
5900
|
}
|
|
4793
5901
|
|
|
4794
|
-
output "
|
|
4795
|
-
description = "ID of
|
|
4796
|
-
value =
|
|
5902
|
+
output "resource_ids" {
|
|
5903
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
5904
|
+
value = local.resource_ids
|
|
4797
5905
|
}
|
|
4798
5906
|
|
|
4799
|
-
# CloudWatch Log Groups
|
|
4800
|
-
output "
|
|
4801
|
-
description = "Name of
|
|
4802
|
-
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 }
|
|
4803
5911
|
}
|
|
4804
5912
|
|
|
4805
|
-
output "
|
|
4806
|
-
description = "ARN of
|
|
4807
|
-
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 }
|
|
4808
5916
|
}
|
|
4809
5917
|
"
|
|
4810
5918
|
`;
|