@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
|
@@ -3551,6 +3551,49 @@ resource "random_string" "suffix" {
|
|
|
3551
3551
|
upper = false
|
|
3552
3552
|
}
|
|
3553
3553
|
|
|
3554
|
+
locals {
|
|
3555
|
+
# Generated at build time from the API definition
|
|
3556
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
3557
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
3558
|
+
|
|
3559
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
3560
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
3561
|
+
|
|
3562
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
3563
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
3564
|
+
}
|
|
3565
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
3566
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
operation_path = { for op, details in local.operations : op =>
|
|
3570
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
3571
|
+
}
|
|
3572
|
+
route_key = { for op, details in local.operations : op =>
|
|
3573
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
3577
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
resource "terraform_data" "operations_metadata" {
|
|
3582
|
+
# Fails the plan when the operations metadata has not been generated
|
|
3583
|
+
input = local.operations_file
|
|
3584
|
+
|
|
3585
|
+
lifecycle {
|
|
3586
|
+
precondition {
|
|
3587
|
+
condition = fileexists(local.operations_file)
|
|
3588
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
3589
|
+
}
|
|
3590
|
+
precondition {
|
|
3591
|
+
condition = length(local.operations) > 0
|
|
3592
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3554
3597
|
# Resources
|
|
3555
3598
|
|
|
3556
3599
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -3611,9 +3654,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
3611
3654
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
3612
3655
|
}
|
|
3613
3656
|
|
|
3614
|
-
# Lambda function
|
|
3615
|
-
# This configures a single "router" lambda to serve all requests
|
|
3616
3657
|
resource "aws_lambda_function" "api_lambda" {
|
|
3658
|
+
# One lambda function per operation, each serving just that operation
|
|
3659
|
+
for_each = local.operations
|
|
3660
|
+
|
|
3617
3661
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
3618
3662
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3619
3663
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -3622,8 +3666,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3622
3666
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3623
3667
|
s3_key = aws_s3_object.lambda_zip.key
|
|
3624
3668
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3625
|
-
function_name =
|
|
3626
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
3669
|
+
function_name = local.function_name[each.key]
|
|
3670
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
3627
3671
|
handler = "index.handler"
|
|
3628
3672
|
runtime = "nodejs22.x"
|
|
3629
3673
|
timeout = 30
|
|
@@ -3659,7 +3703,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3659
3703
|
|
|
3660
3704
|
# IAM role for Lambda execution
|
|
3661
3705
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
3662
|
-
|
|
3706
|
+
for_each = local.operations
|
|
3707
|
+
|
|
3708
|
+
name = local.role_name[each.key]
|
|
3663
3709
|
|
|
3664
3710
|
assume_role_policy = jsonencode({
|
|
3665
3711
|
Version = "2012-10-17"
|
|
@@ -3679,32 +3725,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
3679
3725
|
|
|
3680
3726
|
# Attach basic execution policy to Lambda role
|
|
3681
3727
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3728
|
+
for_each = local.operations
|
|
3729
|
+
|
|
3682
3730
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
3683
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3731
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3684
3732
|
}
|
|
3685
3733
|
|
|
3686
3734
|
# Attach X-Ray tracing policy to Lambda role
|
|
3687
3735
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3736
|
+
for_each = local.operations
|
|
3737
|
+
|
|
3688
3738
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
3689
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3739
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3690
3740
|
}
|
|
3691
3741
|
|
|
3692
3742
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
3693
3743
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
3694
|
-
|
|
3744
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
3745
|
+
|
|
3695
3746
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
3696
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3747
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
3697
3748
|
}
|
|
3698
3749
|
|
|
3699
3750
|
# Additional IAM policies for Lambda (if provided)
|
|
3700
3751
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3752
|
+
for_each = local.operations
|
|
3753
|
+
|
|
3754
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
3755
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
3704
3756
|
|
|
3705
3757
|
policy = jsonencode({
|
|
3706
3758
|
Version = "2012-10-17"
|
|
3707
|
-
Statement = local.
|
|
3759
|
+
Statement = local.lambda_policy_document_statements
|
|
3708
3760
|
})
|
|
3709
3761
|
}
|
|
3710
3762
|
|
|
@@ -3720,6 +3772,15 @@ locals {
|
|
|
3720
3772
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
3721
3773
|
}
|
|
3722
3774
|
] : [], var.additional_iam_policy_statements)
|
|
3775
|
+
|
|
3776
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
3777
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
3778
|
+
{
|
|
3779
|
+
Effect = "Deny"
|
|
3780
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
3781
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
3782
|
+
}
|
|
3783
|
+
]
|
|
3723
3784
|
}
|
|
3724
3785
|
|
|
3725
3786
|
# CloudWatch Log Group for Lambda
|
|
@@ -3727,8 +3788,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
3727
3788
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
3728
3789
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
3729
3790
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
3730
|
-
|
|
3731
|
-
|
|
3791
|
+
for_each = local.operations
|
|
3792
|
+
|
|
3793
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
3794
|
+
tags = var.tags
|
|
3732
3795
|
}
|
|
3733
3796
|
|
|
3734
3797
|
|
|
@@ -3747,9 +3810,11 @@ resource "aws_apigatewayv2_authorizer" "cognito_authorizer" {
|
|
|
3747
3810
|
|
|
3748
3811
|
# Lambda integration for HTTP API
|
|
3749
3812
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
3813
|
+
for_each = local.operations
|
|
3814
|
+
|
|
3750
3815
|
api_id = module.http_api.api_id
|
|
3751
3816
|
integration_type = "AWS_PROXY"
|
|
3752
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
3817
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
3753
3818
|
|
|
3754
3819
|
payload_format_version = "2.0"
|
|
3755
3820
|
timeout_milliseconds = 30000
|
|
@@ -3757,15 +3822,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
3757
3822
|
depends_on = [aws_lambda_function.api_lambda]
|
|
3758
3823
|
}
|
|
3759
3824
|
|
|
3760
|
-
#
|
|
3761
|
-
resource "aws_apigatewayv2_route" "
|
|
3762
|
-
|
|
3763
|
-
# when cors settings are configured
|
|
3764
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
3825
|
+
# One route per operation, each targeting that operation's integration
|
|
3826
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
3827
|
+
for_each = local.operations
|
|
3765
3828
|
|
|
3766
3829
|
api_id = module.http_api.api_id
|
|
3767
|
-
route_key =
|
|
3768
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
3830
|
+
route_key = local.route_key[each.key]
|
|
3831
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
3769
3832
|
|
|
3770
3833
|
authorization_type = "JWT"
|
|
3771
3834
|
authorizer_id = aws_apigatewayv2_authorizer.cognito_authorizer.id
|
|
@@ -3786,11 +3849,13 @@ module "add_url_to_runtime_config" {
|
|
|
3786
3849
|
|
|
3787
3850
|
# Lambda permission for API Gateway to invoke the function
|
|
3788
3851
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3852
|
+
for_each = local.operations
|
|
3853
|
+
|
|
3789
3854
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
3790
3855
|
action = "lambda:InvokeFunction"
|
|
3791
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3856
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3792
3857
|
principal = "apigateway.amazonaws.com"
|
|
3793
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
3858
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3794
3859
|
|
|
3795
3860
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
3796
3861
|
}
|
|
@@ -3833,73 +3898,73 @@ output "stage_execution_arn" {
|
|
|
3833
3898
|
value = module.http_api.stage_execution_arn
|
|
3834
3899
|
}
|
|
3835
3900
|
|
|
3836
|
-
# Lambda Function Outputs
|
|
3837
|
-
output "
|
|
3838
|
-
description = "
|
|
3839
|
-
value =
|
|
3901
|
+
# Lambda Function Outputs, keyed by operation name
|
|
3902
|
+
output "operations" {
|
|
3903
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
3904
|
+
value = keys(local.operations)
|
|
3840
3905
|
}
|
|
3841
3906
|
|
|
3842
|
-
output "
|
|
3843
|
-
description = "
|
|
3844
|
-
value = aws_lambda_function.api_lambda.
|
|
3907
|
+
output "lambda_function_names" {
|
|
3908
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
3909
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
3845
3910
|
}
|
|
3846
3911
|
|
|
3847
|
-
output "
|
|
3848
|
-
description = "
|
|
3849
|
-
value = aws_lambda_function.api_lambda.
|
|
3912
|
+
output "lambda_function_arns" {
|
|
3913
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
3914
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
3850
3915
|
}
|
|
3851
3916
|
|
|
3852
|
-
output "
|
|
3853
|
-
description = "
|
|
3854
|
-
value = aws_lambda_function.api_lambda.
|
|
3917
|
+
output "lambda_invoke_arns" {
|
|
3918
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
3919
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
3855
3920
|
}
|
|
3856
3921
|
|
|
3857
|
-
output "
|
|
3858
|
-
description = "
|
|
3859
|
-
value = aws_lambda_function.api_lambda.
|
|
3922
|
+
output "lambda_qualified_arns" {
|
|
3923
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
3924
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
3860
3925
|
}
|
|
3861
3926
|
|
|
3862
|
-
output "
|
|
3863
|
-
description = "
|
|
3864
|
-
value = aws_lambda_function.api_lambda.
|
|
3927
|
+
output "lambda_versions" {
|
|
3928
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
3929
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
3865
3930
|
}
|
|
3866
3931
|
|
|
3867
|
-
output "
|
|
3868
|
-
description = "
|
|
3869
|
-
value =
|
|
3932
|
+
output "lambda_source_code_hash" {
|
|
3933
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
3934
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
3870
3935
|
}
|
|
3871
3936
|
|
|
3872
|
-
# IAM Role Outputs
|
|
3873
|
-
output "
|
|
3874
|
-
description = "ARN of
|
|
3875
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
3937
|
+
# IAM Role Outputs, keyed by operation name
|
|
3938
|
+
output "lambda_execution_role_arns" {
|
|
3939
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
3940
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
3876
3941
|
}
|
|
3877
3942
|
|
|
3878
|
-
output "
|
|
3879
|
-
description = "Name of
|
|
3880
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
3943
|
+
output "lambda_execution_role_names" {
|
|
3944
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
3945
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
3881
3946
|
}
|
|
3882
3947
|
|
|
3883
3948
|
output "security_group_id" {
|
|
3884
|
-
description = "Security group ID
|
|
3949
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
3885
3950
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
3886
3951
|
}
|
|
3887
3952
|
|
|
3888
|
-
# Integration Outputs
|
|
3889
|
-
output "
|
|
3890
|
-
description = "ID of
|
|
3891
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
3953
|
+
# Integration Outputs, keyed by operation name
|
|
3954
|
+
output "integration_ids" {
|
|
3955
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
3956
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
3892
3957
|
}
|
|
3893
3958
|
|
|
3894
|
-
# CloudWatch Log Groups
|
|
3895
|
-
output "
|
|
3896
|
-
description = "Name of
|
|
3897
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
3959
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
3960
|
+
output "lambda_log_group_names" {
|
|
3961
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3962
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
3898
3963
|
}
|
|
3899
3964
|
|
|
3900
|
-
output "
|
|
3901
|
-
description = "ARN of
|
|
3902
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
3965
|
+
output "lambda_log_group_arns" {
|
|
3966
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3967
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
3903
3968
|
}
|
|
3904
3969
|
|
|
3905
3970
|
output "api_log_group_name" {
|
|
@@ -4291,6 +4356,49 @@ resource "random_string" "suffix" {
|
|
|
4291
4356
|
upper = false
|
|
4292
4357
|
}
|
|
4293
4358
|
|
|
4359
|
+
locals {
|
|
4360
|
+
# Generated at build time from the API definition
|
|
4361
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
4362
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
4363
|
+
|
|
4364
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
4365
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
4366
|
+
|
|
4367
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
4368
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
4369
|
+
}
|
|
4370
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
4371
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
operation_path = { for op, details in local.operations : op =>
|
|
4375
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
4376
|
+
}
|
|
4377
|
+
route_key = { for op, details in local.operations : op =>
|
|
4378
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
4382
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
resource "terraform_data" "operations_metadata" {
|
|
4387
|
+
# Fails the plan when the operations metadata has not been generated
|
|
4388
|
+
input = local.operations_file
|
|
4389
|
+
|
|
4390
|
+
lifecycle {
|
|
4391
|
+
precondition {
|
|
4392
|
+
condition = fileexists(local.operations_file)
|
|
4393
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
4394
|
+
}
|
|
4395
|
+
precondition {
|
|
4396
|
+
condition = length(local.operations) > 0
|
|
4397
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4294
4402
|
# Resources
|
|
4295
4403
|
|
|
4296
4404
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -4351,9 +4459,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
4351
4459
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
4352
4460
|
}
|
|
4353
4461
|
|
|
4354
|
-
# Lambda function
|
|
4355
|
-
# This configures a single "router" lambda to serve all requests
|
|
4356
4462
|
resource "aws_lambda_function" "api_lambda" {
|
|
4463
|
+
# One lambda function per operation, each serving just that operation
|
|
4464
|
+
for_each = local.operations
|
|
4465
|
+
|
|
4357
4466
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
4358
4467
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4359
4468
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -4362,8 +4471,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4362
4471
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4363
4472
|
s3_key = aws_s3_object.lambda_zip.key
|
|
4364
4473
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4365
|
-
function_name =
|
|
4366
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
4474
|
+
function_name = local.function_name[each.key]
|
|
4475
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
4367
4476
|
handler = "index.handler"
|
|
4368
4477
|
runtime = "nodejs22.x"
|
|
4369
4478
|
timeout = 30
|
|
@@ -4399,7 +4508,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4399
4508
|
|
|
4400
4509
|
# IAM role for Lambda execution
|
|
4401
4510
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
4402
|
-
|
|
4511
|
+
for_each = local.operations
|
|
4512
|
+
|
|
4513
|
+
name = local.role_name[each.key]
|
|
4403
4514
|
|
|
4404
4515
|
assume_role_policy = jsonencode({
|
|
4405
4516
|
Version = "2012-10-17"
|
|
@@ -4419,32 +4530,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
4419
4530
|
|
|
4420
4531
|
# Attach basic execution policy to Lambda role
|
|
4421
4532
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4533
|
+
for_each = local.operations
|
|
4534
|
+
|
|
4422
4535
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4423
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4536
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4424
4537
|
}
|
|
4425
4538
|
|
|
4426
4539
|
# Attach X-Ray tracing policy to Lambda role
|
|
4427
4540
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4541
|
+
for_each = local.operations
|
|
4542
|
+
|
|
4428
4543
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4429
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4544
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4430
4545
|
}
|
|
4431
4546
|
|
|
4432
4547
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
4433
4548
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
4434
|
-
|
|
4549
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
4550
|
+
|
|
4435
4551
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
4436
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4552
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4437
4553
|
}
|
|
4438
4554
|
|
|
4439
4555
|
# Additional IAM policies for Lambda (if provided)
|
|
4440
4556
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4557
|
+
for_each = local.operations
|
|
4558
|
+
|
|
4559
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
4560
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
4444
4561
|
|
|
4445
4562
|
policy = jsonencode({
|
|
4446
4563
|
Version = "2012-10-17"
|
|
4447
|
-
Statement = local.
|
|
4564
|
+
Statement = local.lambda_policy_document_statements
|
|
4448
4565
|
})
|
|
4449
4566
|
}
|
|
4450
4567
|
|
|
@@ -4460,6 +4577,15 @@ locals {
|
|
|
4460
4577
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
4461
4578
|
}
|
|
4462
4579
|
] : [], var.additional_iam_policy_statements)
|
|
4580
|
+
|
|
4581
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
4582
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
4583
|
+
{
|
|
4584
|
+
Effect = "Deny"
|
|
4585
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
4586
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
4587
|
+
}
|
|
4588
|
+
]
|
|
4463
4589
|
}
|
|
4464
4590
|
|
|
4465
4591
|
# CloudWatch Log Group for Lambda
|
|
@@ -4467,8 +4593,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
4467
4593
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4468
4594
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4469
4595
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4470
|
-
|
|
4471
|
-
|
|
4596
|
+
for_each = local.operations
|
|
4597
|
+
|
|
4598
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
4599
|
+
tags = var.tags
|
|
4472
4600
|
}
|
|
4473
4601
|
|
|
4474
4602
|
|
|
@@ -4554,7 +4682,8 @@ resource "aws_apigatewayv2_authorizer" "custom_authorizer" {
|
|
|
4554
4682
|
authorizer_type = "REQUEST"
|
|
4555
4683
|
authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
|
|
4556
4684
|
authorizer_payload_format_version = "2.0"
|
|
4557
|
-
authorizer_result_ttl_in_seconds =
|
|
4685
|
+
authorizer_result_ttl_in_seconds = 300
|
|
4686
|
+
identity_sources = ["$request.header.Authorization"]
|
|
4558
4687
|
enable_simple_responses = true
|
|
4559
4688
|
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
4560
4689
|
}
|
|
@@ -4569,9 +4698,11 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
4569
4698
|
|
|
4570
4699
|
# Lambda integration for HTTP API
|
|
4571
4700
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
4701
|
+
for_each = local.operations
|
|
4702
|
+
|
|
4572
4703
|
api_id = module.http_api.api_id
|
|
4573
4704
|
integration_type = "AWS_PROXY"
|
|
4574
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
4705
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
4575
4706
|
|
|
4576
4707
|
payload_format_version = "2.0"
|
|
4577
4708
|
timeout_milliseconds = 30000
|
|
@@ -4579,15 +4710,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
4579
4710
|
depends_on = [aws_lambda_function.api_lambda]
|
|
4580
4711
|
}
|
|
4581
4712
|
|
|
4582
|
-
#
|
|
4583
|
-
resource "aws_apigatewayv2_route" "
|
|
4584
|
-
|
|
4585
|
-
# when cors settings are configured
|
|
4586
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
4713
|
+
# One route per operation, each targeting that operation's integration
|
|
4714
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
4715
|
+
for_each = local.operations
|
|
4587
4716
|
|
|
4588
4717
|
api_id = module.http_api.api_id
|
|
4589
|
-
route_key =
|
|
4590
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
4718
|
+
route_key = local.route_key[each.key]
|
|
4719
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
4591
4720
|
|
|
4592
4721
|
authorization_type = "CUSTOM"
|
|
4593
4722
|
authorizer_id = aws_apigatewayv2_authorizer.custom_authorizer.id
|
|
@@ -4608,11 +4737,13 @@ module "add_url_to_runtime_config" {
|
|
|
4608
4737
|
|
|
4609
4738
|
# Lambda permission for API Gateway to invoke the function
|
|
4610
4739
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
4740
|
+
for_each = local.operations
|
|
4741
|
+
|
|
4611
4742
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
4612
4743
|
action = "lambda:InvokeFunction"
|
|
4613
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4744
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
4614
4745
|
principal = "apigateway.amazonaws.com"
|
|
4615
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
4746
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4616
4747
|
|
|
4617
4748
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
4618
4749
|
}
|
|
@@ -4655,73 +4786,73 @@ output "stage_execution_arn" {
|
|
|
4655
4786
|
value = module.http_api.stage_execution_arn
|
|
4656
4787
|
}
|
|
4657
4788
|
|
|
4658
|
-
# Lambda Function Outputs
|
|
4659
|
-
output "
|
|
4660
|
-
description = "
|
|
4661
|
-
value =
|
|
4789
|
+
# Lambda Function Outputs, keyed by operation name
|
|
4790
|
+
output "operations" {
|
|
4791
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
4792
|
+
value = keys(local.operations)
|
|
4662
4793
|
}
|
|
4663
4794
|
|
|
4664
|
-
output "
|
|
4665
|
-
description = "
|
|
4666
|
-
value = aws_lambda_function.api_lambda.
|
|
4795
|
+
output "lambda_function_names" {
|
|
4796
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
4797
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
4667
4798
|
}
|
|
4668
4799
|
|
|
4669
|
-
output "
|
|
4670
|
-
description = "
|
|
4671
|
-
value = aws_lambda_function.api_lambda.
|
|
4800
|
+
output "lambda_function_arns" {
|
|
4801
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
4802
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
4672
4803
|
}
|
|
4673
4804
|
|
|
4674
|
-
output "
|
|
4675
|
-
description = "
|
|
4676
|
-
value = aws_lambda_function.api_lambda.
|
|
4805
|
+
output "lambda_invoke_arns" {
|
|
4806
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
4807
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
4677
4808
|
}
|
|
4678
4809
|
|
|
4679
|
-
output "
|
|
4680
|
-
description = "
|
|
4681
|
-
value = aws_lambda_function.api_lambda.
|
|
4810
|
+
output "lambda_qualified_arns" {
|
|
4811
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
4812
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
4682
4813
|
}
|
|
4683
4814
|
|
|
4684
|
-
output "
|
|
4685
|
-
description = "
|
|
4686
|
-
value = aws_lambda_function.api_lambda.
|
|
4815
|
+
output "lambda_versions" {
|
|
4816
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
4817
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
4687
4818
|
}
|
|
4688
4819
|
|
|
4689
|
-
output "
|
|
4690
|
-
description = "
|
|
4691
|
-
value =
|
|
4820
|
+
output "lambda_source_code_hash" {
|
|
4821
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
4822
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
4692
4823
|
}
|
|
4693
4824
|
|
|
4694
|
-
# IAM Role Outputs
|
|
4695
|
-
output "
|
|
4696
|
-
description = "ARN of
|
|
4697
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
4825
|
+
# IAM Role Outputs, keyed by operation name
|
|
4826
|
+
output "lambda_execution_role_arns" {
|
|
4827
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
4828
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
4698
4829
|
}
|
|
4699
4830
|
|
|
4700
|
-
output "
|
|
4701
|
-
description = "Name of
|
|
4702
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
4831
|
+
output "lambda_execution_role_names" {
|
|
4832
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
4833
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
4703
4834
|
}
|
|
4704
4835
|
|
|
4705
4836
|
output "security_group_id" {
|
|
4706
|
-
description = "Security group ID
|
|
4837
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
4707
4838
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
4708
4839
|
}
|
|
4709
4840
|
|
|
4710
|
-
# Integration Outputs
|
|
4711
|
-
output "
|
|
4712
|
-
description = "ID of
|
|
4713
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
4841
|
+
# Integration Outputs, keyed by operation name
|
|
4842
|
+
output "integration_ids" {
|
|
4843
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
4844
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
4714
4845
|
}
|
|
4715
4846
|
|
|
4716
|
-
# CloudWatch Log Groups
|
|
4717
|
-
output "
|
|
4718
|
-
description = "Name of
|
|
4719
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
4847
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
4848
|
+
output "lambda_log_group_names" {
|
|
4849
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4850
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
4720
4851
|
}
|
|
4721
4852
|
|
|
4722
|
-
output "
|
|
4723
|
-
description = "ARN of
|
|
4724
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
4853
|
+
output "lambda_log_group_arns" {
|
|
4854
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
4855
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
4725
4856
|
}
|
|
4726
4857
|
|
|
4727
4858
|
output "api_log_group_name" {
|
|
@@ -5113,6 +5244,49 @@ resource "random_string" "suffix" {
|
|
|
5113
5244
|
upper = false
|
|
5114
5245
|
}
|
|
5115
5246
|
|
|
5247
|
+
locals {
|
|
5248
|
+
# Generated at build time from the API definition
|
|
5249
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
5250
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
5251
|
+
|
|
5252
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
5253
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
5254
|
+
|
|
5255
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
5256
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
5257
|
+
}
|
|
5258
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
5259
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
operation_path = { for op, details in local.operations : op =>
|
|
5263
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
5264
|
+
}
|
|
5265
|
+
route_key = { for op, details in local.operations : op =>
|
|
5266
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
5270
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
resource "terraform_data" "operations_metadata" {
|
|
5275
|
+
# Fails the plan when the operations metadata has not been generated
|
|
5276
|
+
input = local.operations_file
|
|
5277
|
+
|
|
5278
|
+
lifecycle {
|
|
5279
|
+
precondition {
|
|
5280
|
+
condition = fileexists(local.operations_file)
|
|
5281
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
5282
|
+
}
|
|
5283
|
+
precondition {
|
|
5284
|
+
condition = length(local.operations) > 0
|
|
5285
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5116
5290
|
# Resources
|
|
5117
5291
|
|
|
5118
5292
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -5173,9 +5347,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
5173
5347
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
5174
5348
|
}
|
|
5175
5349
|
|
|
5176
|
-
# Lambda function
|
|
5177
|
-
# This configures a single "router" lambda to serve all requests
|
|
5178
5350
|
resource "aws_lambda_function" "api_lambda" {
|
|
5351
|
+
# One lambda function per operation, each serving just that operation
|
|
5352
|
+
for_each = local.operations
|
|
5353
|
+
|
|
5179
5354
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
5180
5355
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
5181
5356
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -5184,8 +5359,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5184
5359
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
5185
5360
|
s3_key = aws_s3_object.lambda_zip.key
|
|
5186
5361
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
5187
|
-
function_name =
|
|
5188
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
5362
|
+
function_name = local.function_name[each.key]
|
|
5363
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
5189
5364
|
handler = "index.handler"
|
|
5190
5365
|
runtime = "nodejs22.x"
|
|
5191
5366
|
timeout = 30
|
|
@@ -5221,7 +5396,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5221
5396
|
|
|
5222
5397
|
# IAM role for Lambda execution
|
|
5223
5398
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
5224
|
-
|
|
5399
|
+
for_each = local.operations
|
|
5400
|
+
|
|
5401
|
+
name = local.role_name[each.key]
|
|
5225
5402
|
|
|
5226
5403
|
assume_role_policy = jsonencode({
|
|
5227
5404
|
Version = "2012-10-17"
|
|
@@ -5241,32 +5418,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
5241
5418
|
|
|
5242
5419
|
# Attach basic execution policy to Lambda role
|
|
5243
5420
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
5421
|
+
for_each = local.operations
|
|
5422
|
+
|
|
5244
5423
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
5245
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5424
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5246
5425
|
}
|
|
5247
5426
|
|
|
5248
5427
|
# Attach X-Ray tracing policy to Lambda role
|
|
5249
5428
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
5429
|
+
for_each = local.operations
|
|
5430
|
+
|
|
5250
5431
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
5251
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5432
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5252
5433
|
}
|
|
5253
5434
|
|
|
5254
5435
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
5255
5436
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
5256
|
-
|
|
5437
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
5438
|
+
|
|
5257
5439
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
5258
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
5440
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5259
5441
|
}
|
|
5260
5442
|
|
|
5261
5443
|
# Additional IAM policies for Lambda (if provided)
|
|
5262
5444
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5445
|
+
for_each = local.operations
|
|
5446
|
+
|
|
5447
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
5448
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
5266
5449
|
|
|
5267
5450
|
policy = jsonencode({
|
|
5268
5451
|
Version = "2012-10-17"
|
|
5269
|
-
Statement = local.
|
|
5452
|
+
Statement = local.lambda_policy_document_statements
|
|
5270
5453
|
})
|
|
5271
5454
|
}
|
|
5272
5455
|
|
|
@@ -5282,6 +5465,15 @@ locals {
|
|
|
5282
5465
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
5283
5466
|
}
|
|
5284
5467
|
] : [], var.additional_iam_policy_statements)
|
|
5468
|
+
|
|
5469
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
5470
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
5471
|
+
{
|
|
5472
|
+
Effect = "Deny"
|
|
5473
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
5474
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
5475
|
+
}
|
|
5476
|
+
]
|
|
5285
5477
|
}
|
|
5286
5478
|
|
|
5287
5479
|
# CloudWatch Log Group for Lambda
|
|
@@ -5289,17 +5481,21 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
5289
5481
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
5290
5482
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
5291
5483
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
5292
|
-
|
|
5293
|
-
|
|
5484
|
+
for_each = local.operations
|
|
5485
|
+
|
|
5486
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
5487
|
+
tags = var.tags
|
|
5294
5488
|
}
|
|
5295
5489
|
|
|
5296
5490
|
|
|
5297
5491
|
|
|
5298
5492
|
# Lambda integration for HTTP API
|
|
5299
5493
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
5494
|
+
for_each = local.operations
|
|
5495
|
+
|
|
5300
5496
|
api_id = module.http_api.api_id
|
|
5301
5497
|
integration_type = "AWS_PROXY"
|
|
5302
|
-
integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
5498
|
+
integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
|
|
5303
5499
|
|
|
5304
5500
|
payload_format_version = "2.0"
|
|
5305
5501
|
timeout_milliseconds = 30000
|
|
@@ -5307,15 +5503,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
5307
5503
|
depends_on = [aws_lambda_function.api_lambda]
|
|
5308
5504
|
}
|
|
5309
5505
|
|
|
5310
|
-
#
|
|
5311
|
-
resource "aws_apigatewayv2_route" "
|
|
5312
|
-
|
|
5313
|
-
# when cors settings are configured
|
|
5314
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
5506
|
+
# One route per operation, each targeting that operation's integration
|
|
5507
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
5508
|
+
for_each = local.operations
|
|
5315
5509
|
|
|
5316
5510
|
api_id = module.http_api.api_id
|
|
5317
|
-
route_key =
|
|
5318
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
5511
|
+
route_key = local.route_key[each.key]
|
|
5512
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
5319
5513
|
|
|
5320
5514
|
authorization_type = "AWS_IAM"
|
|
5321
5515
|
|
|
@@ -5335,11 +5529,13 @@ module "add_url_to_runtime_config" {
|
|
|
5335
5529
|
|
|
5336
5530
|
# Lambda permission for API Gateway to invoke the function
|
|
5337
5531
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
5532
|
+
for_each = local.operations
|
|
5533
|
+
|
|
5338
5534
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
5339
5535
|
action = "lambda:InvokeFunction"
|
|
5340
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5536
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
5341
5537
|
principal = "apigateway.amazonaws.com"
|
|
5342
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
5538
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
5343
5539
|
|
|
5344
5540
|
depends_on = [module.http_api, aws_lambda_function.api_lambda]
|
|
5345
5541
|
}
|
|
@@ -5382,73 +5578,73 @@ output "stage_execution_arn" {
|
|
|
5382
5578
|
value = module.http_api.stage_execution_arn
|
|
5383
5579
|
}
|
|
5384
5580
|
|
|
5385
|
-
# Lambda Function Outputs
|
|
5386
|
-
output "
|
|
5387
|
-
description = "
|
|
5388
|
-
value =
|
|
5581
|
+
# Lambda Function Outputs, keyed by operation name
|
|
5582
|
+
output "operations" {
|
|
5583
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
5584
|
+
value = keys(local.operations)
|
|
5389
5585
|
}
|
|
5390
5586
|
|
|
5391
|
-
output "
|
|
5392
|
-
description = "
|
|
5393
|
-
value = aws_lambda_function.api_lambda.
|
|
5587
|
+
output "lambda_function_names" {
|
|
5588
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
5589
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
5394
5590
|
}
|
|
5395
5591
|
|
|
5396
|
-
output "
|
|
5397
|
-
description = "
|
|
5398
|
-
value = aws_lambda_function.api_lambda.
|
|
5592
|
+
output "lambda_function_arns" {
|
|
5593
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
5594
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
5399
5595
|
}
|
|
5400
5596
|
|
|
5401
|
-
output "
|
|
5402
|
-
description = "
|
|
5403
|
-
value = aws_lambda_function.api_lambda.
|
|
5597
|
+
output "lambda_invoke_arns" {
|
|
5598
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
5599
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
5404
5600
|
}
|
|
5405
5601
|
|
|
5406
|
-
output "
|
|
5407
|
-
description = "
|
|
5408
|
-
value = aws_lambda_function.api_lambda.
|
|
5602
|
+
output "lambda_qualified_arns" {
|
|
5603
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
5604
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
5409
5605
|
}
|
|
5410
5606
|
|
|
5411
|
-
output "
|
|
5412
|
-
description = "
|
|
5413
|
-
value = aws_lambda_function.api_lambda.
|
|
5607
|
+
output "lambda_versions" {
|
|
5608
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
5609
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
5414
5610
|
}
|
|
5415
5611
|
|
|
5416
|
-
output "
|
|
5417
|
-
description = "
|
|
5418
|
-
value =
|
|
5612
|
+
output "lambda_source_code_hash" {
|
|
5613
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
5614
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
5419
5615
|
}
|
|
5420
5616
|
|
|
5421
|
-
# IAM Role Outputs
|
|
5422
|
-
output "
|
|
5423
|
-
description = "ARN of
|
|
5424
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
5617
|
+
# IAM Role Outputs, keyed by operation name
|
|
5618
|
+
output "lambda_execution_role_arns" {
|
|
5619
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
5620
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
5425
5621
|
}
|
|
5426
5622
|
|
|
5427
|
-
output "
|
|
5428
|
-
description = "Name of
|
|
5429
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
5623
|
+
output "lambda_execution_role_names" {
|
|
5624
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
5625
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
5430
5626
|
}
|
|
5431
5627
|
|
|
5432
5628
|
output "security_group_id" {
|
|
5433
|
-
description = "Security group ID
|
|
5629
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
5434
5630
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
5435
5631
|
}
|
|
5436
5632
|
|
|
5437
|
-
# Integration Outputs
|
|
5438
|
-
output "
|
|
5439
|
-
description = "ID of
|
|
5440
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
5633
|
+
# Integration Outputs, keyed by operation name
|
|
5634
|
+
output "integration_ids" {
|
|
5635
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
5636
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
5441
5637
|
}
|
|
5442
5638
|
|
|
5443
|
-
# CloudWatch Log Groups
|
|
5444
|
-
output "
|
|
5445
|
-
description = "Name of
|
|
5446
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
5639
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
5640
|
+
output "lambda_log_group_names" {
|
|
5641
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5642
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
5447
5643
|
}
|
|
5448
5644
|
|
|
5449
|
-
output "
|
|
5450
|
-
description = "ARN of
|
|
5451
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
5645
|
+
output "lambda_log_group_arns" {
|
|
5646
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5647
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
5452
5648
|
}
|
|
5453
5649
|
|
|
5454
5650
|
output "api_log_group_name" {
|
|
@@ -5908,6 +6104,235 @@ resource "random_string" "suffix" {
|
|
|
5908
6104
|
upper = false
|
|
5909
6105
|
}
|
|
5910
6106
|
|
|
6107
|
+
locals {
|
|
6108
|
+
# Generated at build time from the API definition
|
|
6109
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
6110
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
6111
|
+
|
|
6112
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
6113
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
6114
|
+
|
|
6115
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
6116
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
6117
|
+
}
|
|
6118
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
6119
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
6123
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
6124
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
6125
|
+
|
|
6126
|
+
path_nodes = merge([
|
|
6127
|
+
for op, segments in local.operation_segments : {
|
|
6128
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
6129
|
+
depth = i
|
|
6130
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
6131
|
+
part = segment
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
]...)
|
|
6135
|
+
|
|
6136
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
6137
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
6138
|
+
max_path_depth = 16
|
|
6139
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
6140
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
6141
|
+
} }
|
|
6142
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
6143
|
+
|
|
6144
|
+
resource_ids = merge(
|
|
6145
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
6146
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
6147
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
6148
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
6149
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
6150
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
6151
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
6152
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
6153
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
6154
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
6155
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
6156
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
6157
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
6158
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
6159
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
6160
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
6161
|
+
)
|
|
6162
|
+
|
|
6163
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
6164
|
+
local.resource_ids[join("/", segments)]
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
6168
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
resource "terraform_data" "operations_metadata" {
|
|
6173
|
+
# Fails the plan when the operations metadata has not been generated
|
|
6174
|
+
input = local.operations_file
|
|
6175
|
+
|
|
6176
|
+
lifecycle {
|
|
6177
|
+
precondition {
|
|
6178
|
+
condition = fileexists(local.operations_file)
|
|
6179
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
6180
|
+
}
|
|
6181
|
+
precondition {
|
|
6182
|
+
condition = length(local.operations) > 0
|
|
6183
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
6184
|
+
}
|
|
6185
|
+
precondition {
|
|
6186
|
+
condition = length(local.paths_too_deep) == 0
|
|
6187
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
|
|
6192
|
+
# API Gateway resources at path depth 0
|
|
6193
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
6194
|
+
for_each = local.path_nodes_by_depth[0]
|
|
6195
|
+
|
|
6196
|
+
rest_api_id = module.rest_api.api_id
|
|
6197
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
6198
|
+
path_part = each.value.part
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
# API Gateway resources at path depth 1
|
|
6202
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
6203
|
+
for_each = local.path_nodes_by_depth[1]
|
|
6204
|
+
|
|
6205
|
+
rest_api_id = module.rest_api.api_id
|
|
6206
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
6207
|
+
path_part = each.value.part
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
# API Gateway resources at path depth 2
|
|
6211
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
6212
|
+
for_each = local.path_nodes_by_depth[2]
|
|
6213
|
+
|
|
6214
|
+
rest_api_id = module.rest_api.api_id
|
|
6215
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
6216
|
+
path_part = each.value.part
|
|
6217
|
+
}
|
|
6218
|
+
|
|
6219
|
+
# API Gateway resources at path depth 3
|
|
6220
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
6221
|
+
for_each = local.path_nodes_by_depth[3]
|
|
6222
|
+
|
|
6223
|
+
rest_api_id = module.rest_api.api_id
|
|
6224
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
6225
|
+
path_part = each.value.part
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
# API Gateway resources at path depth 4
|
|
6229
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
6230
|
+
for_each = local.path_nodes_by_depth[4]
|
|
6231
|
+
|
|
6232
|
+
rest_api_id = module.rest_api.api_id
|
|
6233
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
6234
|
+
path_part = each.value.part
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
# API Gateway resources at path depth 5
|
|
6238
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
6239
|
+
for_each = local.path_nodes_by_depth[5]
|
|
6240
|
+
|
|
6241
|
+
rest_api_id = module.rest_api.api_id
|
|
6242
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
6243
|
+
path_part = each.value.part
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
# API Gateway resources at path depth 6
|
|
6247
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
6248
|
+
for_each = local.path_nodes_by_depth[6]
|
|
6249
|
+
|
|
6250
|
+
rest_api_id = module.rest_api.api_id
|
|
6251
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
6252
|
+
path_part = each.value.part
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
# API Gateway resources at path depth 7
|
|
6256
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
6257
|
+
for_each = local.path_nodes_by_depth[7]
|
|
6258
|
+
|
|
6259
|
+
rest_api_id = module.rest_api.api_id
|
|
6260
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
6261
|
+
path_part = each.value.part
|
|
6262
|
+
}
|
|
6263
|
+
|
|
6264
|
+
# API Gateway resources at path depth 8
|
|
6265
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
6266
|
+
for_each = local.path_nodes_by_depth[8]
|
|
6267
|
+
|
|
6268
|
+
rest_api_id = module.rest_api.api_id
|
|
6269
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
6270
|
+
path_part = each.value.part
|
|
6271
|
+
}
|
|
6272
|
+
|
|
6273
|
+
# API Gateway resources at path depth 9
|
|
6274
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
6275
|
+
for_each = local.path_nodes_by_depth[9]
|
|
6276
|
+
|
|
6277
|
+
rest_api_id = module.rest_api.api_id
|
|
6278
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
6279
|
+
path_part = each.value.part
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6282
|
+
# API Gateway resources at path depth 10
|
|
6283
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
6284
|
+
for_each = local.path_nodes_by_depth[10]
|
|
6285
|
+
|
|
6286
|
+
rest_api_id = module.rest_api.api_id
|
|
6287
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
6288
|
+
path_part = each.value.part
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
# API Gateway resources at path depth 11
|
|
6292
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
6293
|
+
for_each = local.path_nodes_by_depth[11]
|
|
6294
|
+
|
|
6295
|
+
rest_api_id = module.rest_api.api_id
|
|
6296
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
6297
|
+
path_part = each.value.part
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
# API Gateway resources at path depth 12
|
|
6301
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
6302
|
+
for_each = local.path_nodes_by_depth[12]
|
|
6303
|
+
|
|
6304
|
+
rest_api_id = module.rest_api.api_id
|
|
6305
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
6306
|
+
path_part = each.value.part
|
|
6307
|
+
}
|
|
6308
|
+
|
|
6309
|
+
# API Gateway resources at path depth 13
|
|
6310
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
6311
|
+
for_each = local.path_nodes_by_depth[13]
|
|
6312
|
+
|
|
6313
|
+
rest_api_id = module.rest_api.api_id
|
|
6314
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
6315
|
+
path_part = each.value.part
|
|
6316
|
+
}
|
|
6317
|
+
|
|
6318
|
+
# API Gateway resources at path depth 14
|
|
6319
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
6320
|
+
for_each = local.path_nodes_by_depth[14]
|
|
6321
|
+
|
|
6322
|
+
rest_api_id = module.rest_api.api_id
|
|
6323
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
6324
|
+
path_part = each.value.part
|
|
6325
|
+
}
|
|
6326
|
+
|
|
6327
|
+
# API Gateway resources at path depth 15
|
|
6328
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
6329
|
+
for_each = local.path_nodes_by_depth[15]
|
|
6330
|
+
|
|
6331
|
+
rest_api_id = module.rest_api.api_id
|
|
6332
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
6333
|
+
path_part = each.value.part
|
|
6334
|
+
}
|
|
6335
|
+
|
|
5911
6336
|
# Resources
|
|
5912
6337
|
|
|
5913
6338
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -5982,8 +6407,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
5982
6407
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
5983
6408
|
}
|
|
5984
6409
|
|
|
5985
|
-
# Lambda function
|
|
5986
6410
|
resource "aws_lambda_function" "api_lambda" {
|
|
6411
|
+
# One lambda function per operation, each serving just that operation
|
|
6412
|
+
for_each = local.operations
|
|
6413
|
+
|
|
5987
6414
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
5988
6415
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
5989
6416
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -5992,8 +6419,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5992
6419
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
5993
6420
|
s3_key = aws_s3_object.lambda_zip.key
|
|
5994
6421
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
5995
|
-
function_name =
|
|
5996
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
6422
|
+
function_name = local.function_name[each.key]
|
|
6423
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
5997
6424
|
handler = "index.handler"
|
|
5998
6425
|
runtime = "nodejs22.x"
|
|
5999
6426
|
timeout = 30
|
|
@@ -6029,7 +6456,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
6029
6456
|
|
|
6030
6457
|
# IAM role for Lambda execution
|
|
6031
6458
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
6032
|
-
|
|
6459
|
+
for_each = local.operations
|
|
6460
|
+
|
|
6461
|
+
name = local.role_name[each.key]
|
|
6033
6462
|
|
|
6034
6463
|
assume_role_policy = jsonencode({
|
|
6035
6464
|
Version = "2012-10-17"
|
|
@@ -6049,32 +6478,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
6049
6478
|
|
|
6050
6479
|
# Attach basic execution policy to Lambda role
|
|
6051
6480
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
6481
|
+
for_each = local.operations
|
|
6482
|
+
|
|
6052
6483
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
6053
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6484
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6054
6485
|
}
|
|
6055
6486
|
|
|
6056
6487
|
# Attach X-Ray tracing policy to Lambda role
|
|
6057
6488
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
6489
|
+
for_each = local.operations
|
|
6490
|
+
|
|
6058
6491
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
6059
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6492
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6060
6493
|
}
|
|
6061
6494
|
|
|
6062
6495
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
6063
6496
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
6064
|
-
|
|
6497
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
6498
|
+
|
|
6065
6499
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
6066
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6500
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
6067
6501
|
}
|
|
6068
6502
|
|
|
6069
6503
|
# Additional IAM policies for Lambda (if provided)
|
|
6070
6504
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6505
|
+
for_each = local.operations
|
|
6506
|
+
|
|
6507
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
6508
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
6074
6509
|
|
|
6075
6510
|
policy = jsonencode({
|
|
6076
6511
|
Version = "2012-10-17"
|
|
6077
|
-
Statement = local.
|
|
6512
|
+
Statement = local.lambda_policy_document_statements
|
|
6078
6513
|
})
|
|
6079
6514
|
}
|
|
6080
6515
|
|
|
@@ -6090,6 +6525,15 @@ locals {
|
|
|
6090
6525
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
6091
6526
|
}
|
|
6092
6527
|
] : [], var.additional_iam_policy_statements)
|
|
6528
|
+
|
|
6529
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
6530
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
6531
|
+
{
|
|
6532
|
+
Effect = "Deny"
|
|
6533
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
6534
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
6535
|
+
}
|
|
6536
|
+
]
|
|
6093
6537
|
}
|
|
6094
6538
|
|
|
6095
6539
|
# CloudWatch Log Group for Lambda
|
|
@@ -6097,8 +6541,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
6097
6541
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
6098
6542
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
6099
6543
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
6100
|
-
|
|
6101
|
-
|
|
6544
|
+
for_each = local.operations
|
|
6545
|
+
|
|
6546
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
6547
|
+
tags = var.tags
|
|
6102
6548
|
}
|
|
6103
6549
|
|
|
6104
6550
|
|
|
@@ -6111,33 +6557,14 @@ resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
|
6111
6557
|
identity_source = "method.request.header.Authorization"
|
|
6112
6558
|
}
|
|
6113
6559
|
|
|
6114
|
-
#
|
|
6115
|
-
resource "
|
|
6116
|
-
rest_api_id = module.rest_api.api_id
|
|
6117
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
6118
|
-
path_part = "{proxy+}"
|
|
6119
|
-
}
|
|
6120
|
-
|
|
6121
|
-
# Lambda integration for REST API
|
|
6122
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6123
|
-
rest_api_id = module.rest_api.api_id
|
|
6124
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
6125
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
6126
|
-
|
|
6127
|
-
integration_http_method = "POST"
|
|
6128
|
-
type = "AWS_PROXY"
|
|
6129
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
6130
|
-
response_transfer_mode = "STREAM"
|
|
6131
|
-
|
|
6132
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
6133
|
-
}
|
|
6134
|
-
|
|
6135
|
-
# Method for proxy integration
|
|
6136
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
6560
|
+
# Method per operation
|
|
6561
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
6137
6562
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
6563
|
+
for_each = local.operations
|
|
6564
|
+
|
|
6138
6565
|
rest_api_id = module.rest_api.api_id
|
|
6139
|
-
resource_id =
|
|
6140
|
-
http_method =
|
|
6566
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6567
|
+
http_method = upper(each.value.method)
|
|
6141
6568
|
|
|
6142
6569
|
authorization = "COGNITO_USER_POOLS"
|
|
6143
6570
|
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
@@ -6145,28 +6572,51 @@ resource "aws_api_gateway_method" "proxy_method" {
|
|
|
6145
6572
|
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
6146
6573
|
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
6147
6574
|
|
|
6575
|
+
# Path parameters must be declared on the method
|
|
6148
6576
|
request_parameters = {
|
|
6149
|
-
|
|
6577
|
+
for segment in local.operation_segments[each.key] :
|
|
6578
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
6579
|
+
if startswith(segment, "{")
|
|
6150
6580
|
}
|
|
6151
6581
|
|
|
6152
6582
|
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
6153
6583
|
}
|
|
6154
6584
|
|
|
6155
|
-
#
|
|
6585
|
+
# Lambda integration per operation
|
|
6586
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6587
|
+
for_each = local.operations
|
|
6588
|
+
|
|
6589
|
+
rest_api_id = module.rest_api.api_id
|
|
6590
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6591
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
6592
|
+
|
|
6593
|
+
integration_http_method = "POST"
|
|
6594
|
+
type = "AWS_PROXY"
|
|
6595
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
6596
|
+
response_transfer_mode = "STREAM"
|
|
6597
|
+
|
|
6598
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
6156
6602
|
resource "aws_api_gateway_method" "options_method" {
|
|
6157
6603
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
6158
6604
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
6605
|
+
for_each = local.resource_ids
|
|
6606
|
+
|
|
6159
6607
|
rest_api_id = module.rest_api.api_id
|
|
6160
|
-
resource_id =
|
|
6608
|
+
resource_id = each.value
|
|
6161
6609
|
http_method = "OPTIONS"
|
|
6162
6610
|
authorization = "NONE"
|
|
6163
6611
|
}
|
|
6164
6612
|
|
|
6165
|
-
# CORS integration for OPTIONS
|
|
6613
|
+
# CORS integration for OPTIONS methods
|
|
6166
6614
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
6615
|
+
for_each = local.resource_ids
|
|
6616
|
+
|
|
6167
6617
|
rest_api_id = module.rest_api.api_id
|
|
6168
|
-
resource_id =
|
|
6169
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6618
|
+
resource_id = each.value
|
|
6619
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6170
6620
|
|
|
6171
6621
|
type = "MOCK"
|
|
6172
6622
|
request_templates = {
|
|
@@ -6174,11 +6624,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
6174
6624
|
}
|
|
6175
6625
|
}
|
|
6176
6626
|
|
|
6177
|
-
# OPTIONS method
|
|
6627
|
+
# OPTIONS method responses
|
|
6178
6628
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
6629
|
+
for_each = local.resource_ids
|
|
6630
|
+
|
|
6179
6631
|
rest_api_id = module.rest_api.api_id
|
|
6180
|
-
resource_id =
|
|
6181
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6632
|
+
resource_id = each.value
|
|
6633
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6182
6634
|
status_code = "204"
|
|
6183
6635
|
|
|
6184
6636
|
response_parameters = {
|
|
@@ -6188,18 +6640,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
6188
6640
|
}
|
|
6189
6641
|
}
|
|
6190
6642
|
|
|
6191
|
-
# OPTIONS integration
|
|
6643
|
+
# OPTIONS integration responses
|
|
6192
6644
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
6645
|
+
for_each = local.resource_ids
|
|
6646
|
+
|
|
6193
6647
|
rest_api_id = module.rest_api.api_id
|
|
6194
|
-
resource_id =
|
|
6195
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6196
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
6648
|
+
resource_id = each.value
|
|
6649
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6650
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
6197
6651
|
|
|
6198
6652
|
response_parameters = {
|
|
6199
6653
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
6200
6654
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
6201
6655
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
6202
6656
|
}
|
|
6657
|
+
|
|
6658
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
6203
6659
|
}
|
|
6204
6660
|
|
|
6205
6661
|
# API Gateway deployment
|
|
@@ -6207,13 +6663,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
6207
6663
|
rest_api_id = module.rest_api.api_id
|
|
6208
6664
|
|
|
6209
6665
|
triggers = {
|
|
6210
|
-
redeployment = sha1(jsonencode(
|
|
6211
|
-
aws_api_gateway_resource.
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6666
|
+
redeployment = sha1(jsonencode(concat(
|
|
6667
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
6668
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
6669
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
6670
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
6671
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
6672
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
6673
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
6674
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
6675
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
6676
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
6677
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
6678
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
6679
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
6680
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
6681
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
6682
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
6683
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
6684
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
6685
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
6686
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
6687
|
+
)))
|
|
6217
6688
|
}
|
|
6218
6689
|
|
|
6219
6690
|
lifecycle {
|
|
@@ -6221,7 +6692,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
6221
6692
|
}
|
|
6222
6693
|
|
|
6223
6694
|
depends_on = [
|
|
6224
|
-
aws_api_gateway_method.
|
|
6695
|
+
aws_api_gateway_method.operation_methods,
|
|
6225
6696
|
aws_api_gateway_integration.lambda_integration,
|
|
6226
6697
|
aws_api_gateway_method.options_method,
|
|
6227
6698
|
aws_api_gateway_integration.options_integration,
|
|
@@ -6349,11 +6820,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
6349
6820
|
|
|
6350
6821
|
# Lambda permission for API Gateway to invoke the function
|
|
6351
6822
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
6823
|
+
for_each = local.operations
|
|
6824
|
+
|
|
6352
6825
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
6353
6826
|
action = "lambda:InvokeFunction"
|
|
6354
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
6827
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
6355
6828
|
principal = "apigateway.amazonaws.com"
|
|
6356
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
6829
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
6357
6830
|
|
|
6358
6831
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
6359
6832
|
}
|
|
@@ -6418,83 +6891,83 @@ output "stage_id" {
|
|
|
6418
6891
|
value = aws_api_gateway_stage.api_stage.id
|
|
6419
6892
|
}
|
|
6420
6893
|
|
|
6421
|
-
# Lambda Function Outputs
|
|
6422
|
-
output "
|
|
6423
|
-
description = "
|
|
6424
|
-
value =
|
|
6894
|
+
# Lambda Function Outputs, keyed by operation name
|
|
6895
|
+
output "operations" {
|
|
6896
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
6897
|
+
value = keys(local.operations)
|
|
6425
6898
|
}
|
|
6426
6899
|
|
|
6427
|
-
output "
|
|
6428
|
-
description = "
|
|
6429
|
-
value = aws_lambda_function.api_lambda.
|
|
6900
|
+
output "lambda_function_names" {
|
|
6901
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
6902
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
6430
6903
|
}
|
|
6431
6904
|
|
|
6432
|
-
output "
|
|
6433
|
-
description = "
|
|
6434
|
-
value = aws_lambda_function.api_lambda.
|
|
6905
|
+
output "lambda_function_arns" {
|
|
6906
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
6907
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
6435
6908
|
}
|
|
6436
6909
|
|
|
6437
|
-
output "
|
|
6438
|
-
description = "
|
|
6439
|
-
value = aws_lambda_function.api_lambda.
|
|
6910
|
+
output "lambda_invoke_arns" {
|
|
6911
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
6912
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
6440
6913
|
}
|
|
6441
6914
|
|
|
6442
|
-
output "
|
|
6443
|
-
description = "
|
|
6444
|
-
value = aws_lambda_function.api_lambda.
|
|
6915
|
+
output "lambda_qualified_arns" {
|
|
6916
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
6917
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
6445
6918
|
}
|
|
6446
6919
|
|
|
6447
|
-
output "
|
|
6448
|
-
description = "
|
|
6449
|
-
value = aws_lambda_function.api_lambda.
|
|
6920
|
+
output "lambda_versions" {
|
|
6921
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
6922
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
6450
6923
|
}
|
|
6451
6924
|
|
|
6452
|
-
output "
|
|
6453
|
-
description = "
|
|
6454
|
-
value =
|
|
6925
|
+
output "lambda_source_code_hash" {
|
|
6926
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
6927
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
6455
6928
|
}
|
|
6456
6929
|
|
|
6457
|
-
# IAM Role Outputs
|
|
6458
|
-
output "
|
|
6459
|
-
description = "ARN of
|
|
6460
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
6930
|
+
# IAM Role Outputs, keyed by operation name
|
|
6931
|
+
output "lambda_execution_role_arns" {
|
|
6932
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
6933
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
6461
6934
|
}
|
|
6462
6935
|
|
|
6463
|
-
output "
|
|
6464
|
-
description = "Name of
|
|
6465
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
6936
|
+
output "lambda_execution_role_names" {
|
|
6937
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
6938
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
6466
6939
|
}
|
|
6467
6940
|
|
|
6468
6941
|
output "security_group_id" {
|
|
6469
|
-
description = "Security group ID
|
|
6942
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
6470
6943
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
6471
6944
|
}
|
|
6472
6945
|
|
|
6473
|
-
# Integration Outputs
|
|
6474
|
-
output "
|
|
6475
|
-
description = "ID of
|
|
6476
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
6946
|
+
# Integration Outputs, keyed by operation name
|
|
6947
|
+
output "integration_ids" {
|
|
6948
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
6949
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
6477
6950
|
}
|
|
6478
6951
|
|
|
6479
|
-
output "
|
|
6480
|
-
description = "ID of
|
|
6481
|
-
value =
|
|
6952
|
+
output "method_ids" {
|
|
6953
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
6954
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
6482
6955
|
}
|
|
6483
6956
|
|
|
6484
|
-
output "
|
|
6485
|
-
description = "ID of
|
|
6486
|
-
value =
|
|
6957
|
+
output "resource_ids" {
|
|
6958
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
6959
|
+
value = local.resource_ids
|
|
6487
6960
|
}
|
|
6488
6961
|
|
|
6489
|
-
# CloudWatch Log Groups
|
|
6490
|
-
output "
|
|
6491
|
-
description = "Name of
|
|
6492
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
6962
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
6963
|
+
output "lambda_log_group_names" {
|
|
6964
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6965
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
6493
6966
|
}
|
|
6494
6967
|
|
|
6495
|
-
output "
|
|
6496
|
-
description = "ARN of
|
|
6497
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
6968
|
+
output "lambda_log_group_arns" {
|
|
6969
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6970
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
6498
6971
|
}
|
|
6499
6972
|
",
|
|
6500
6973
|
}
|
|
@@ -6935,6 +7408,235 @@ resource "random_string" "suffix" {
|
|
|
6935
7408
|
upper = false
|
|
6936
7409
|
}
|
|
6937
7410
|
|
|
7411
|
+
locals {
|
|
7412
|
+
# Generated at build time from the API definition
|
|
7413
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
7414
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
7415
|
+
|
|
7416
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
7417
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
7418
|
+
|
|
7419
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
7420
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
7421
|
+
}
|
|
7422
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
7423
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7426
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
7427
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
7428
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
7429
|
+
|
|
7430
|
+
path_nodes = merge([
|
|
7431
|
+
for op, segments in local.operation_segments : {
|
|
7432
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
7433
|
+
depth = i
|
|
7434
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
7435
|
+
part = segment
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
]...)
|
|
7439
|
+
|
|
7440
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
7441
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
7442
|
+
max_path_depth = 16
|
|
7443
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
7444
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
7445
|
+
} }
|
|
7446
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
7447
|
+
|
|
7448
|
+
resource_ids = merge(
|
|
7449
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
7450
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
7451
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
7452
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
7453
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
7454
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
7455
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
7456
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
7457
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
7458
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
7459
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
7460
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
7461
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
7462
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
7463
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
7464
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
7465
|
+
)
|
|
7466
|
+
|
|
7467
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
7468
|
+
local.resource_ids[join("/", segments)]
|
|
7469
|
+
}
|
|
7470
|
+
|
|
7471
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
7472
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
|
|
7476
|
+
resource "terraform_data" "operations_metadata" {
|
|
7477
|
+
# Fails the plan when the operations metadata has not been generated
|
|
7478
|
+
input = local.operations_file
|
|
7479
|
+
|
|
7480
|
+
lifecycle {
|
|
7481
|
+
precondition {
|
|
7482
|
+
condition = fileexists(local.operations_file)
|
|
7483
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
7484
|
+
}
|
|
7485
|
+
precondition {
|
|
7486
|
+
condition = length(local.operations) > 0
|
|
7487
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
7488
|
+
}
|
|
7489
|
+
precondition {
|
|
7490
|
+
condition = length(local.paths_too_deep) == 0
|
|
7491
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
# API Gateway resources at path depth 0
|
|
7497
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
7498
|
+
for_each = local.path_nodes_by_depth[0]
|
|
7499
|
+
|
|
7500
|
+
rest_api_id = module.rest_api.api_id
|
|
7501
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
7502
|
+
path_part = each.value.part
|
|
7503
|
+
}
|
|
7504
|
+
|
|
7505
|
+
# API Gateway resources at path depth 1
|
|
7506
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
7507
|
+
for_each = local.path_nodes_by_depth[1]
|
|
7508
|
+
|
|
7509
|
+
rest_api_id = module.rest_api.api_id
|
|
7510
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
7511
|
+
path_part = each.value.part
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
# API Gateway resources at path depth 2
|
|
7515
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
7516
|
+
for_each = local.path_nodes_by_depth[2]
|
|
7517
|
+
|
|
7518
|
+
rest_api_id = module.rest_api.api_id
|
|
7519
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
7520
|
+
path_part = each.value.part
|
|
7521
|
+
}
|
|
7522
|
+
|
|
7523
|
+
# API Gateway resources at path depth 3
|
|
7524
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
7525
|
+
for_each = local.path_nodes_by_depth[3]
|
|
7526
|
+
|
|
7527
|
+
rest_api_id = module.rest_api.api_id
|
|
7528
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
7529
|
+
path_part = each.value.part
|
|
7530
|
+
}
|
|
7531
|
+
|
|
7532
|
+
# API Gateway resources at path depth 4
|
|
7533
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
7534
|
+
for_each = local.path_nodes_by_depth[4]
|
|
7535
|
+
|
|
7536
|
+
rest_api_id = module.rest_api.api_id
|
|
7537
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
7538
|
+
path_part = each.value.part
|
|
7539
|
+
}
|
|
7540
|
+
|
|
7541
|
+
# API Gateway resources at path depth 5
|
|
7542
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
7543
|
+
for_each = local.path_nodes_by_depth[5]
|
|
7544
|
+
|
|
7545
|
+
rest_api_id = module.rest_api.api_id
|
|
7546
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
7547
|
+
path_part = each.value.part
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
# API Gateway resources at path depth 6
|
|
7551
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
7552
|
+
for_each = local.path_nodes_by_depth[6]
|
|
7553
|
+
|
|
7554
|
+
rest_api_id = module.rest_api.api_id
|
|
7555
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
7556
|
+
path_part = each.value.part
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
# API Gateway resources at path depth 7
|
|
7560
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
7561
|
+
for_each = local.path_nodes_by_depth[7]
|
|
7562
|
+
|
|
7563
|
+
rest_api_id = module.rest_api.api_id
|
|
7564
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
7565
|
+
path_part = each.value.part
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
# API Gateway resources at path depth 8
|
|
7569
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
7570
|
+
for_each = local.path_nodes_by_depth[8]
|
|
7571
|
+
|
|
7572
|
+
rest_api_id = module.rest_api.api_id
|
|
7573
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
7574
|
+
path_part = each.value.part
|
|
7575
|
+
}
|
|
7576
|
+
|
|
7577
|
+
# API Gateway resources at path depth 9
|
|
7578
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
7579
|
+
for_each = local.path_nodes_by_depth[9]
|
|
7580
|
+
|
|
7581
|
+
rest_api_id = module.rest_api.api_id
|
|
7582
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
7583
|
+
path_part = each.value.part
|
|
7584
|
+
}
|
|
7585
|
+
|
|
7586
|
+
# API Gateway resources at path depth 10
|
|
7587
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
7588
|
+
for_each = local.path_nodes_by_depth[10]
|
|
7589
|
+
|
|
7590
|
+
rest_api_id = module.rest_api.api_id
|
|
7591
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
7592
|
+
path_part = each.value.part
|
|
7593
|
+
}
|
|
7594
|
+
|
|
7595
|
+
# API Gateway resources at path depth 11
|
|
7596
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
7597
|
+
for_each = local.path_nodes_by_depth[11]
|
|
7598
|
+
|
|
7599
|
+
rest_api_id = module.rest_api.api_id
|
|
7600
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
7601
|
+
path_part = each.value.part
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
# API Gateway resources at path depth 12
|
|
7605
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
7606
|
+
for_each = local.path_nodes_by_depth[12]
|
|
7607
|
+
|
|
7608
|
+
rest_api_id = module.rest_api.api_id
|
|
7609
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
7610
|
+
path_part = each.value.part
|
|
7611
|
+
}
|
|
7612
|
+
|
|
7613
|
+
# API Gateway resources at path depth 13
|
|
7614
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
7615
|
+
for_each = local.path_nodes_by_depth[13]
|
|
7616
|
+
|
|
7617
|
+
rest_api_id = module.rest_api.api_id
|
|
7618
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
7619
|
+
path_part = each.value.part
|
|
7620
|
+
}
|
|
7621
|
+
|
|
7622
|
+
# API Gateway resources at path depth 14
|
|
7623
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
7624
|
+
for_each = local.path_nodes_by_depth[14]
|
|
7625
|
+
|
|
7626
|
+
rest_api_id = module.rest_api.api_id
|
|
7627
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
7628
|
+
path_part = each.value.part
|
|
7629
|
+
}
|
|
7630
|
+
|
|
7631
|
+
# API Gateway resources at path depth 15
|
|
7632
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
7633
|
+
for_each = local.path_nodes_by_depth[15]
|
|
7634
|
+
|
|
7635
|
+
rest_api_id = module.rest_api.api_id
|
|
7636
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
7637
|
+
path_part = each.value.part
|
|
7638
|
+
}
|
|
7639
|
+
|
|
6938
7640
|
# Resources
|
|
6939
7641
|
|
|
6940
7642
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -7009,8 +7711,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
7009
7711
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
7010
7712
|
}
|
|
7011
7713
|
|
|
7012
|
-
# Lambda function
|
|
7013
7714
|
resource "aws_lambda_function" "api_lambda" {
|
|
7715
|
+
# One lambda function per operation, each serving just that operation
|
|
7716
|
+
for_each = local.operations
|
|
7717
|
+
|
|
7014
7718
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
7015
7719
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
7016
7720
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -7019,8 +7723,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
7019
7723
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
7020
7724
|
s3_key = aws_s3_object.lambda_zip.key
|
|
7021
7725
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
7022
|
-
function_name =
|
|
7023
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
7726
|
+
function_name = local.function_name[each.key]
|
|
7727
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
7024
7728
|
handler = "index.handler"
|
|
7025
7729
|
runtime = "nodejs22.x"
|
|
7026
7730
|
timeout = 30
|
|
@@ -7056,7 +7760,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
7056
7760
|
|
|
7057
7761
|
# IAM role for Lambda execution
|
|
7058
7762
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
7059
|
-
|
|
7763
|
+
for_each = local.operations
|
|
7764
|
+
|
|
7765
|
+
name = local.role_name[each.key]
|
|
7060
7766
|
|
|
7061
7767
|
assume_role_policy = jsonencode({
|
|
7062
7768
|
Version = "2012-10-17"
|
|
@@ -7076,32 +7782,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
7076
7782
|
|
|
7077
7783
|
# Attach basic execution policy to Lambda role
|
|
7078
7784
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
7785
|
+
for_each = local.operations
|
|
7786
|
+
|
|
7079
7787
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
7080
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7788
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7081
7789
|
}
|
|
7082
7790
|
|
|
7083
7791
|
# Attach X-Ray tracing policy to Lambda role
|
|
7084
7792
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
7793
|
+
for_each = local.operations
|
|
7794
|
+
|
|
7085
7795
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
7086
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7796
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7087
7797
|
}
|
|
7088
7798
|
|
|
7089
7799
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
7090
7800
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
7091
|
-
|
|
7801
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
7802
|
+
|
|
7092
7803
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
7093
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
7804
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
7094
7805
|
}
|
|
7095
7806
|
|
|
7096
7807
|
# Additional IAM policies for Lambda (if provided)
|
|
7097
7808
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7809
|
+
for_each = local.operations
|
|
7810
|
+
|
|
7811
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
7812
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
7101
7813
|
|
|
7102
7814
|
policy = jsonencode({
|
|
7103
7815
|
Version = "2012-10-17"
|
|
7104
|
-
Statement = local.
|
|
7816
|
+
Statement = local.lambda_policy_document_statements
|
|
7105
7817
|
})
|
|
7106
7818
|
}
|
|
7107
7819
|
|
|
@@ -7117,6 +7829,15 @@ locals {
|
|
|
7117
7829
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
7118
7830
|
}
|
|
7119
7831
|
] : [], var.additional_iam_policy_statements)
|
|
7832
|
+
|
|
7833
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
7834
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
7835
|
+
{
|
|
7836
|
+
Effect = "Deny"
|
|
7837
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
7838
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
7839
|
+
}
|
|
7840
|
+
]
|
|
7120
7841
|
}
|
|
7121
7842
|
|
|
7122
7843
|
# CloudWatch Log Group for Lambda
|
|
@@ -7124,64 +7845,70 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
7124
7845
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
7125
7846
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
7126
7847
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
7127
|
-
|
|
7128
|
-
tags = var.tags
|
|
7129
|
-
}
|
|
7130
|
-
|
|
7131
|
-
|
|
7848
|
+
for_each = local.operations
|
|
7132
7849
|
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
rest_api_id = module.rest_api.api_id
|
|
7136
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
7137
|
-
path_part = "{proxy+}"
|
|
7850
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
7851
|
+
tags = var.tags
|
|
7138
7852
|
}
|
|
7139
7853
|
|
|
7140
|
-
# Lambda integration for REST API
|
|
7141
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
7142
|
-
rest_api_id = module.rest_api.api_id
|
|
7143
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
7144
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
7145
7854
|
|
|
7146
|
-
integration_http_method = "POST"
|
|
7147
|
-
type = "AWS_PROXY"
|
|
7148
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
7149
|
-
response_transfer_mode = "STREAM"
|
|
7150
|
-
|
|
7151
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
7152
|
-
}
|
|
7153
7855
|
|
|
7154
|
-
# Method
|
|
7155
|
-
resource "aws_api_gateway_method" "
|
|
7856
|
+
# Method per operation
|
|
7857
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
7156
7858
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
7859
|
+
for_each = local.operations
|
|
7860
|
+
|
|
7157
7861
|
rest_api_id = module.rest_api.api_id
|
|
7158
|
-
resource_id =
|
|
7159
|
-
http_method =
|
|
7862
|
+
resource_id = local.operation_resource_id[each.key]
|
|
7863
|
+
http_method = upper(each.value.method)
|
|
7160
7864
|
|
|
7161
7865
|
authorization = "AWS_IAM"
|
|
7162
7866
|
|
|
7867
|
+
# Path parameters must be declared on the method
|
|
7163
7868
|
request_parameters = {
|
|
7164
|
-
|
|
7869
|
+
for segment in local.operation_segments[each.key] :
|
|
7870
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
7871
|
+
if startswith(segment, "{")
|
|
7165
7872
|
}
|
|
7166
7873
|
|
|
7167
7874
|
depends_on = []
|
|
7168
7875
|
}
|
|
7169
7876
|
|
|
7170
|
-
#
|
|
7877
|
+
# Lambda integration per operation
|
|
7878
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
7879
|
+
for_each = local.operations
|
|
7880
|
+
|
|
7881
|
+
rest_api_id = module.rest_api.api_id
|
|
7882
|
+
resource_id = local.operation_resource_id[each.key]
|
|
7883
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
7884
|
+
|
|
7885
|
+
integration_http_method = "POST"
|
|
7886
|
+
type = "AWS_PROXY"
|
|
7887
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
7888
|
+
response_transfer_mode = "STREAM"
|
|
7889
|
+
|
|
7890
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7893
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
7171
7894
|
resource "aws_api_gateway_method" "options_method" {
|
|
7172
7895
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
7173
7896
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
7897
|
+
for_each = local.resource_ids
|
|
7898
|
+
|
|
7174
7899
|
rest_api_id = module.rest_api.api_id
|
|
7175
|
-
resource_id =
|
|
7900
|
+
resource_id = each.value
|
|
7176
7901
|
http_method = "OPTIONS"
|
|
7177
7902
|
authorization = "NONE"
|
|
7178
7903
|
}
|
|
7179
7904
|
|
|
7180
|
-
# CORS integration for OPTIONS
|
|
7905
|
+
# CORS integration for OPTIONS methods
|
|
7181
7906
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
7907
|
+
for_each = local.resource_ids
|
|
7908
|
+
|
|
7182
7909
|
rest_api_id = module.rest_api.api_id
|
|
7183
|
-
resource_id =
|
|
7184
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7910
|
+
resource_id = each.value
|
|
7911
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7185
7912
|
|
|
7186
7913
|
type = "MOCK"
|
|
7187
7914
|
request_templates = {
|
|
@@ -7189,11 +7916,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
7189
7916
|
}
|
|
7190
7917
|
}
|
|
7191
7918
|
|
|
7192
|
-
# OPTIONS method
|
|
7919
|
+
# OPTIONS method responses
|
|
7193
7920
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
7921
|
+
for_each = local.resource_ids
|
|
7922
|
+
|
|
7194
7923
|
rest_api_id = module.rest_api.api_id
|
|
7195
|
-
resource_id =
|
|
7196
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7924
|
+
resource_id = each.value
|
|
7925
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7197
7926
|
status_code = "204"
|
|
7198
7927
|
|
|
7199
7928
|
response_parameters = {
|
|
@@ -7203,18 +7932,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
7203
7932
|
}
|
|
7204
7933
|
}
|
|
7205
7934
|
|
|
7206
|
-
# OPTIONS integration
|
|
7935
|
+
# OPTIONS integration responses
|
|
7207
7936
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
7937
|
+
for_each = local.resource_ids
|
|
7938
|
+
|
|
7208
7939
|
rest_api_id = module.rest_api.api_id
|
|
7209
|
-
resource_id =
|
|
7210
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
7211
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
7940
|
+
resource_id = each.value
|
|
7941
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
7942
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
7212
7943
|
|
|
7213
7944
|
response_parameters = {
|
|
7214
7945
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
7215
7946
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
7216
7947
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
7217
7948
|
}
|
|
7949
|
+
|
|
7950
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
7218
7951
|
}
|
|
7219
7952
|
|
|
7220
7953
|
# API Gateway deployment
|
|
@@ -7222,13 +7955,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
7222
7955
|
rest_api_id = module.rest_api.api_id
|
|
7223
7956
|
|
|
7224
7957
|
triggers = {
|
|
7225
|
-
redeployment = sha1(jsonencode(
|
|
7226
|
-
aws_api_gateway_resource.
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7958
|
+
redeployment = sha1(jsonencode(concat(
|
|
7959
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
7960
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
7961
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
7962
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
7963
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
7964
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
7965
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
7966
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
7967
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
7968
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
7969
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
7970
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
7971
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
7972
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
7973
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
7974
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
7975
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
7976
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
7977
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
7978
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
7979
|
+
)))
|
|
7232
7980
|
}
|
|
7233
7981
|
|
|
7234
7982
|
lifecycle {
|
|
@@ -7236,7 +7984,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
7236
7984
|
}
|
|
7237
7985
|
|
|
7238
7986
|
depends_on = [
|
|
7239
|
-
aws_api_gateway_method.
|
|
7987
|
+
aws_api_gateway_method.operation_methods,
|
|
7240
7988
|
aws_api_gateway_integration.lambda_integration,
|
|
7241
7989
|
aws_api_gateway_method.options_method,
|
|
7242
7990
|
aws_api_gateway_integration.options_integration,
|
|
@@ -7375,11 +8123,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
7375
8123
|
|
|
7376
8124
|
# Lambda permission for API Gateway to invoke the function
|
|
7377
8125
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
8126
|
+
for_each = local.operations
|
|
8127
|
+
|
|
7378
8128
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
7379
8129
|
action = "lambda:InvokeFunction"
|
|
7380
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
8130
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
7381
8131
|
principal = "apigateway.amazonaws.com"
|
|
7382
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
8132
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
7383
8133
|
|
|
7384
8134
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
7385
8135
|
}
|
|
@@ -7444,83 +8194,83 @@ output "stage_id" {
|
|
|
7444
8194
|
value = aws_api_gateway_stage.api_stage.id
|
|
7445
8195
|
}
|
|
7446
8196
|
|
|
7447
|
-
# Lambda Function Outputs
|
|
7448
|
-
output "
|
|
7449
|
-
description = "
|
|
7450
|
-
value =
|
|
8197
|
+
# Lambda Function Outputs, keyed by operation name
|
|
8198
|
+
output "operations" {
|
|
8199
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
8200
|
+
value = keys(local.operations)
|
|
7451
8201
|
}
|
|
7452
8202
|
|
|
7453
|
-
output "
|
|
7454
|
-
description = "
|
|
7455
|
-
value = aws_lambda_function.api_lambda.
|
|
8203
|
+
output "lambda_function_names" {
|
|
8204
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
8205
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
7456
8206
|
}
|
|
7457
8207
|
|
|
7458
|
-
output "
|
|
7459
|
-
description = "
|
|
7460
|
-
value = aws_lambda_function.api_lambda.
|
|
8208
|
+
output "lambda_function_arns" {
|
|
8209
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
8210
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
7461
8211
|
}
|
|
7462
8212
|
|
|
7463
|
-
output "
|
|
7464
|
-
description = "
|
|
7465
|
-
value = aws_lambda_function.api_lambda.
|
|
8213
|
+
output "lambda_invoke_arns" {
|
|
8214
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
8215
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
7466
8216
|
}
|
|
7467
8217
|
|
|
7468
|
-
output "
|
|
7469
|
-
description = "
|
|
7470
|
-
value = aws_lambda_function.api_lambda.
|
|
8218
|
+
output "lambda_qualified_arns" {
|
|
8219
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
8220
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
7471
8221
|
}
|
|
7472
8222
|
|
|
7473
|
-
output "
|
|
7474
|
-
description = "
|
|
7475
|
-
value = aws_lambda_function.api_lambda.
|
|
8223
|
+
output "lambda_versions" {
|
|
8224
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
8225
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
7476
8226
|
}
|
|
7477
8227
|
|
|
7478
|
-
output "
|
|
7479
|
-
description = "
|
|
7480
|
-
value =
|
|
8228
|
+
output "lambda_source_code_hash" {
|
|
8229
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
8230
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
7481
8231
|
}
|
|
7482
8232
|
|
|
7483
|
-
# IAM Role Outputs
|
|
7484
|
-
output "
|
|
7485
|
-
description = "ARN of
|
|
7486
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
8233
|
+
# IAM Role Outputs, keyed by operation name
|
|
8234
|
+
output "lambda_execution_role_arns" {
|
|
8235
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
8236
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
7487
8237
|
}
|
|
7488
8238
|
|
|
7489
|
-
output "
|
|
7490
|
-
description = "Name of
|
|
7491
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
8239
|
+
output "lambda_execution_role_names" {
|
|
8240
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
8241
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
7492
8242
|
}
|
|
7493
8243
|
|
|
7494
8244
|
output "security_group_id" {
|
|
7495
|
-
description = "Security group ID
|
|
8245
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
7496
8246
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
7497
8247
|
}
|
|
7498
8248
|
|
|
7499
|
-
# Integration Outputs
|
|
7500
|
-
output "
|
|
7501
|
-
description = "ID of
|
|
7502
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
8249
|
+
# Integration Outputs, keyed by operation name
|
|
8250
|
+
output "integration_ids" {
|
|
8251
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
8252
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
7503
8253
|
}
|
|
7504
8254
|
|
|
7505
|
-
output "
|
|
7506
|
-
description = "ID of
|
|
7507
|
-
value =
|
|
8255
|
+
output "method_ids" {
|
|
8256
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
8257
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
7508
8258
|
}
|
|
7509
8259
|
|
|
7510
|
-
output "
|
|
7511
|
-
description = "ID of
|
|
7512
|
-
value =
|
|
8260
|
+
output "resource_ids" {
|
|
8261
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
8262
|
+
value = local.resource_ids
|
|
7513
8263
|
}
|
|
7514
8264
|
|
|
7515
|
-
# CloudWatch Log Groups
|
|
7516
|
-
output "
|
|
7517
|
-
description = "Name of
|
|
7518
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
8265
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
8266
|
+
output "lambda_log_group_names" {
|
|
8267
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
8268
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
7519
8269
|
}
|
|
7520
8270
|
|
|
7521
|
-
output "
|
|
7522
|
-
description = "ARN of
|
|
7523
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
8271
|
+
output "lambda_log_group_arns" {
|
|
8272
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
8273
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
7524
8274
|
}
|
|
7525
8275
|
",
|
|
7526
8276
|
}
|
|
@@ -7961,6 +8711,235 @@ resource "random_string" "suffix" {
|
|
|
7961
8711
|
upper = false
|
|
7962
8712
|
}
|
|
7963
8713
|
|
|
8714
|
+
locals {
|
|
8715
|
+
# Generated at build time from the API definition
|
|
8716
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
8717
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
8718
|
+
|
|
8719
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
8720
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
8721
|
+
|
|
8722
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
8723
|
+
"\${substr("TestApi-\${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
8724
|
+
}
|
|
8725
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
8726
|
+
"\${substr("TestApi-\${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-\${local.operation_hash[op]}-\${random_string.suffix.result}"
|
|
8727
|
+
}
|
|
8728
|
+
|
|
8729
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
8730
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
8731
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
8732
|
+
|
|
8733
|
+
path_nodes = merge([
|
|
8734
|
+
for op, segments in local.operation_segments : {
|
|
8735
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
8736
|
+
depth = i
|
|
8737
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
8738
|
+
part = segment
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8741
|
+
]...)
|
|
8742
|
+
|
|
8743
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
8744
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
8745
|
+
max_path_depth = 16
|
|
8746
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
8747
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
8748
|
+
} }
|
|
8749
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
8750
|
+
|
|
8751
|
+
resource_ids = merge(
|
|
8752
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
8753
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
8754
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
8755
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
8756
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
8757
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
8758
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
8759
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
8760
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
8761
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
8762
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
8763
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
8764
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
8765
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
8766
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
8767
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
8768
|
+
)
|
|
8769
|
+
|
|
8770
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
8771
|
+
local.resource_ids[join("/", segments)]
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
8775
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8779
|
+
resource "terraform_data" "operations_metadata" {
|
|
8780
|
+
# Fails the plan when the operations metadata has not been generated
|
|
8781
|
+
input = local.operations_file
|
|
8782
|
+
|
|
8783
|
+
lifecycle {
|
|
8784
|
+
precondition {
|
|
8785
|
+
condition = fileexists(local.operations_file)
|
|
8786
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
8787
|
+
}
|
|
8788
|
+
precondition {
|
|
8789
|
+
condition = length(local.operations) > 0
|
|
8790
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
8791
|
+
}
|
|
8792
|
+
precondition {
|
|
8793
|
+
condition = length(local.paths_too_deep) == 0
|
|
8794
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
|
|
8799
|
+
# API Gateway resources at path depth 0
|
|
8800
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
8801
|
+
for_each = local.path_nodes_by_depth[0]
|
|
8802
|
+
|
|
8803
|
+
rest_api_id = module.rest_api.api_id
|
|
8804
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
8805
|
+
path_part = each.value.part
|
|
8806
|
+
}
|
|
8807
|
+
|
|
8808
|
+
# API Gateway resources at path depth 1
|
|
8809
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
8810
|
+
for_each = local.path_nodes_by_depth[1]
|
|
8811
|
+
|
|
8812
|
+
rest_api_id = module.rest_api.api_id
|
|
8813
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
8814
|
+
path_part = each.value.part
|
|
8815
|
+
}
|
|
8816
|
+
|
|
8817
|
+
# API Gateway resources at path depth 2
|
|
8818
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
8819
|
+
for_each = local.path_nodes_by_depth[2]
|
|
8820
|
+
|
|
8821
|
+
rest_api_id = module.rest_api.api_id
|
|
8822
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
8823
|
+
path_part = each.value.part
|
|
8824
|
+
}
|
|
8825
|
+
|
|
8826
|
+
# API Gateway resources at path depth 3
|
|
8827
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
8828
|
+
for_each = local.path_nodes_by_depth[3]
|
|
8829
|
+
|
|
8830
|
+
rest_api_id = module.rest_api.api_id
|
|
8831
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
8832
|
+
path_part = each.value.part
|
|
8833
|
+
}
|
|
8834
|
+
|
|
8835
|
+
# API Gateway resources at path depth 4
|
|
8836
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
8837
|
+
for_each = local.path_nodes_by_depth[4]
|
|
8838
|
+
|
|
8839
|
+
rest_api_id = module.rest_api.api_id
|
|
8840
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
8841
|
+
path_part = each.value.part
|
|
8842
|
+
}
|
|
8843
|
+
|
|
8844
|
+
# API Gateway resources at path depth 5
|
|
8845
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
8846
|
+
for_each = local.path_nodes_by_depth[5]
|
|
8847
|
+
|
|
8848
|
+
rest_api_id = module.rest_api.api_id
|
|
8849
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
8850
|
+
path_part = each.value.part
|
|
8851
|
+
}
|
|
8852
|
+
|
|
8853
|
+
# API Gateway resources at path depth 6
|
|
8854
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
8855
|
+
for_each = local.path_nodes_by_depth[6]
|
|
8856
|
+
|
|
8857
|
+
rest_api_id = module.rest_api.api_id
|
|
8858
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
8859
|
+
path_part = each.value.part
|
|
8860
|
+
}
|
|
8861
|
+
|
|
8862
|
+
# API Gateway resources at path depth 7
|
|
8863
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
8864
|
+
for_each = local.path_nodes_by_depth[7]
|
|
8865
|
+
|
|
8866
|
+
rest_api_id = module.rest_api.api_id
|
|
8867
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
8868
|
+
path_part = each.value.part
|
|
8869
|
+
}
|
|
8870
|
+
|
|
8871
|
+
# API Gateway resources at path depth 8
|
|
8872
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
8873
|
+
for_each = local.path_nodes_by_depth[8]
|
|
8874
|
+
|
|
8875
|
+
rest_api_id = module.rest_api.api_id
|
|
8876
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
8877
|
+
path_part = each.value.part
|
|
8878
|
+
}
|
|
8879
|
+
|
|
8880
|
+
# API Gateway resources at path depth 9
|
|
8881
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
8882
|
+
for_each = local.path_nodes_by_depth[9]
|
|
8883
|
+
|
|
8884
|
+
rest_api_id = module.rest_api.api_id
|
|
8885
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
8886
|
+
path_part = each.value.part
|
|
8887
|
+
}
|
|
8888
|
+
|
|
8889
|
+
# API Gateway resources at path depth 10
|
|
8890
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
8891
|
+
for_each = local.path_nodes_by_depth[10]
|
|
8892
|
+
|
|
8893
|
+
rest_api_id = module.rest_api.api_id
|
|
8894
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
8895
|
+
path_part = each.value.part
|
|
8896
|
+
}
|
|
8897
|
+
|
|
8898
|
+
# API Gateway resources at path depth 11
|
|
8899
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
8900
|
+
for_each = local.path_nodes_by_depth[11]
|
|
8901
|
+
|
|
8902
|
+
rest_api_id = module.rest_api.api_id
|
|
8903
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
8904
|
+
path_part = each.value.part
|
|
8905
|
+
}
|
|
8906
|
+
|
|
8907
|
+
# API Gateway resources at path depth 12
|
|
8908
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
8909
|
+
for_each = local.path_nodes_by_depth[12]
|
|
8910
|
+
|
|
8911
|
+
rest_api_id = module.rest_api.api_id
|
|
8912
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
8913
|
+
path_part = each.value.part
|
|
8914
|
+
}
|
|
8915
|
+
|
|
8916
|
+
# API Gateway resources at path depth 13
|
|
8917
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
8918
|
+
for_each = local.path_nodes_by_depth[13]
|
|
8919
|
+
|
|
8920
|
+
rest_api_id = module.rest_api.api_id
|
|
8921
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
8922
|
+
path_part = each.value.part
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8925
|
+
# API Gateway resources at path depth 14
|
|
8926
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
8927
|
+
for_each = local.path_nodes_by_depth[14]
|
|
8928
|
+
|
|
8929
|
+
rest_api_id = module.rest_api.api_id
|
|
8930
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
8931
|
+
path_part = each.value.part
|
|
8932
|
+
}
|
|
8933
|
+
|
|
8934
|
+
# API Gateway resources at path depth 15
|
|
8935
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
8936
|
+
for_each = local.path_nodes_by_depth[15]
|
|
8937
|
+
|
|
8938
|
+
rest_api_id = module.rest_api.api_id
|
|
8939
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
8940
|
+
path_part = each.value.part
|
|
8941
|
+
}
|
|
8942
|
+
|
|
7964
8943
|
# Resources
|
|
7965
8944
|
|
|
7966
8945
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -8035,8 +9014,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
8035
9014
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
8036
9015
|
}
|
|
8037
9016
|
|
|
8038
|
-
# Lambda function
|
|
8039
9017
|
resource "aws_lambda_function" "api_lambda" {
|
|
9018
|
+
# One lambda function per operation, each serving just that operation
|
|
9019
|
+
for_each = local.operations
|
|
9020
|
+
|
|
8040
9021
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
8041
9022
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
8042
9023
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -8045,8 +9026,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
8045
9026
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
8046
9027
|
s3_key = aws_s3_object.lambda_zip.key
|
|
8047
9028
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
8048
|
-
function_name =
|
|
8049
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
9029
|
+
function_name = local.function_name[each.key]
|
|
9030
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
8050
9031
|
handler = "index.handler"
|
|
8051
9032
|
runtime = "nodejs22.x"
|
|
8052
9033
|
timeout = 30
|
|
@@ -8082,7 +9063,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
8082
9063
|
|
|
8083
9064
|
# IAM role for Lambda execution
|
|
8084
9065
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
8085
|
-
|
|
9066
|
+
for_each = local.operations
|
|
9067
|
+
|
|
9068
|
+
name = local.role_name[each.key]
|
|
8086
9069
|
|
|
8087
9070
|
assume_role_policy = jsonencode({
|
|
8088
9071
|
Version = "2012-10-17"
|
|
@@ -8102,32 +9085,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
8102
9085
|
|
|
8103
9086
|
# Attach basic execution policy to Lambda role
|
|
8104
9087
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
9088
|
+
for_each = local.operations
|
|
9089
|
+
|
|
8105
9090
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
8106
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9091
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
8107
9092
|
}
|
|
8108
9093
|
|
|
8109
9094
|
# Attach X-Ray tracing policy to Lambda role
|
|
8110
9095
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
9096
|
+
for_each = local.operations
|
|
9097
|
+
|
|
8111
9098
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
8112
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9099
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
8113
9100
|
}
|
|
8114
9101
|
|
|
8115
9102
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
8116
9103
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
8117
|
-
|
|
9104
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
9105
|
+
|
|
8118
9106
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
8119
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
9107
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
8120
9108
|
}
|
|
8121
9109
|
|
|
8122
9110
|
# Additional IAM policies for Lambda (if provided)
|
|
8123
9111
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
9112
|
+
for_each = local.operations
|
|
9113
|
+
|
|
9114
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
9115
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
8127
9116
|
|
|
8128
9117
|
policy = jsonencode({
|
|
8129
9118
|
Version = "2012-10-17"
|
|
8130
|
-
Statement = local.
|
|
9119
|
+
Statement = local.lambda_policy_document_statements
|
|
8131
9120
|
})
|
|
8132
9121
|
}
|
|
8133
9122
|
|
|
@@ -8143,6 +9132,15 @@ locals {
|
|
|
8143
9132
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
8144
9133
|
}
|
|
8145
9134
|
] : [], var.additional_iam_policy_statements)
|
|
9135
|
+
|
|
9136
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
9137
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
9138
|
+
{
|
|
9139
|
+
Effect = "Deny"
|
|
9140
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
9141
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
9142
|
+
}
|
|
9143
|
+
]
|
|
8146
9144
|
}
|
|
8147
9145
|
|
|
8148
9146
|
# CloudWatch Log Group for Lambda
|
|
@@ -8150,8 +9148,10 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
8150
9148
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
8151
9149
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
8152
9150
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
8153
|
-
|
|
8154
|
-
|
|
9151
|
+
for_each = local.operations
|
|
9152
|
+
|
|
9153
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
9154
|
+
tags = var.tags
|
|
8155
9155
|
}
|
|
8156
9156
|
|
|
8157
9157
|
|
|
@@ -8248,59 +9248,63 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
8248
9248
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
8249
9249
|
}
|
|
8250
9250
|
|
|
8251
|
-
#
|
|
8252
|
-
resource "
|
|
8253
|
-
rest_api_id = module.rest_api.api_id
|
|
8254
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
8255
|
-
path_part = "{proxy+}"
|
|
8256
|
-
}
|
|
8257
|
-
|
|
8258
|
-
# Lambda integration for REST API
|
|
8259
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
8260
|
-
rest_api_id = module.rest_api.api_id
|
|
8261
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
8262
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
8263
|
-
|
|
8264
|
-
integration_http_method = "POST"
|
|
8265
|
-
type = "AWS_PROXY"
|
|
8266
|
-
uri = aws_lambda_function.api_lambda.response_streaming_invoke_arn
|
|
8267
|
-
response_transfer_mode = "STREAM"
|
|
8268
|
-
|
|
8269
|
-
depends_on = [aws_lambda_function.api_lambda]
|
|
8270
|
-
}
|
|
8271
|
-
|
|
8272
|
-
# Method for proxy integration
|
|
8273
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
9251
|
+
# Method per operation
|
|
9252
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
8274
9253
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
9254
|
+
for_each = local.operations
|
|
9255
|
+
|
|
8275
9256
|
rest_api_id = module.rest_api.api_id
|
|
8276
|
-
resource_id =
|
|
8277
|
-
http_method =
|
|
9257
|
+
resource_id = local.operation_resource_id[each.key]
|
|
9258
|
+
http_method = upper(each.value.method)
|
|
8278
9259
|
|
|
8279
9260
|
authorization = "CUSTOM"
|
|
8280
9261
|
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
8281
9262
|
|
|
9263
|
+
# Path parameters must be declared on the method
|
|
8282
9264
|
request_parameters = {
|
|
8283
|
-
|
|
9265
|
+
for segment in local.operation_segments[each.key] :
|
|
9266
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
9267
|
+
if startswith(segment, "{")
|
|
8284
9268
|
}
|
|
8285
9269
|
|
|
8286
9270
|
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
8287
9271
|
}
|
|
8288
9272
|
|
|
8289
|
-
#
|
|
9273
|
+
# Lambda integration per operation
|
|
9274
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
9275
|
+
for_each = local.operations
|
|
9276
|
+
|
|
9277
|
+
rest_api_id = module.rest_api.api_id
|
|
9278
|
+
resource_id = local.operation_resource_id[each.key]
|
|
9279
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
9280
|
+
|
|
9281
|
+
integration_http_method = "POST"
|
|
9282
|
+
type = "AWS_PROXY"
|
|
9283
|
+
uri = aws_lambda_function.api_lambda[each.key].response_streaming_invoke_arn
|
|
9284
|
+
response_transfer_mode = "STREAM"
|
|
9285
|
+
|
|
9286
|
+
depends_on = [aws_lambda_function.api_lambda]
|
|
9287
|
+
}
|
|
9288
|
+
|
|
9289
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
8290
9290
|
resource "aws_api_gateway_method" "options_method" {
|
|
8291
9291
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
8292
9292
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
9293
|
+
for_each = local.resource_ids
|
|
9294
|
+
|
|
8293
9295
|
rest_api_id = module.rest_api.api_id
|
|
8294
|
-
resource_id =
|
|
9296
|
+
resource_id = each.value
|
|
8295
9297
|
http_method = "OPTIONS"
|
|
8296
9298
|
authorization = "NONE"
|
|
8297
9299
|
}
|
|
8298
9300
|
|
|
8299
|
-
# CORS integration for OPTIONS
|
|
9301
|
+
# CORS integration for OPTIONS methods
|
|
8300
9302
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
9303
|
+
for_each = local.resource_ids
|
|
9304
|
+
|
|
8301
9305
|
rest_api_id = module.rest_api.api_id
|
|
8302
|
-
resource_id =
|
|
8303
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
9306
|
+
resource_id = each.value
|
|
9307
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
8304
9308
|
|
|
8305
9309
|
type = "MOCK"
|
|
8306
9310
|
request_templates = {
|
|
@@ -8308,11 +9312,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
8308
9312
|
}
|
|
8309
9313
|
}
|
|
8310
9314
|
|
|
8311
|
-
# OPTIONS method
|
|
9315
|
+
# OPTIONS method responses
|
|
8312
9316
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
9317
|
+
for_each = local.resource_ids
|
|
9318
|
+
|
|
8313
9319
|
rest_api_id = module.rest_api.api_id
|
|
8314
|
-
resource_id =
|
|
8315
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
9320
|
+
resource_id = each.value
|
|
9321
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
8316
9322
|
status_code = "204"
|
|
8317
9323
|
|
|
8318
9324
|
response_parameters = {
|
|
@@ -8322,18 +9328,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
8322
9328
|
}
|
|
8323
9329
|
}
|
|
8324
9330
|
|
|
8325
|
-
# OPTIONS integration
|
|
9331
|
+
# OPTIONS integration responses
|
|
8326
9332
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
9333
|
+
for_each = local.resource_ids
|
|
9334
|
+
|
|
8327
9335
|
rest_api_id = module.rest_api.api_id
|
|
8328
|
-
resource_id =
|
|
8329
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
8330
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
9336
|
+
resource_id = each.value
|
|
9337
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
9338
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
8331
9339
|
|
|
8332
9340
|
response_parameters = {
|
|
8333
9341
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
8334
9342
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
8335
9343
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
8336
9344
|
}
|
|
9345
|
+
|
|
9346
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
8337
9347
|
}
|
|
8338
9348
|
|
|
8339
9349
|
# API Gateway deployment
|
|
@@ -8341,13 +9351,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
8341
9351
|
rest_api_id = module.rest_api.api_id
|
|
8342
9352
|
|
|
8343
9353
|
triggers = {
|
|
8344
|
-
redeployment = sha1(jsonencode(
|
|
8345
|
-
aws_api_gateway_resource.
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
9354
|
+
redeployment = sha1(jsonencode(concat(
|
|
9355
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
9356
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
9357
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
9358
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
9359
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
9360
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
9361
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
9362
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
9363
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
9364
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
9365
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
9366
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
9367
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
9368
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
9369
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
9370
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
9371
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
9372
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
9373
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
9374
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
9375
|
+
)))
|
|
8351
9376
|
}
|
|
8352
9377
|
|
|
8353
9378
|
lifecycle {
|
|
@@ -8355,7 +9380,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
8355
9380
|
}
|
|
8356
9381
|
|
|
8357
9382
|
depends_on = [
|
|
8358
|
-
aws_api_gateway_method.
|
|
9383
|
+
aws_api_gateway_method.operation_methods,
|
|
8359
9384
|
aws_api_gateway_integration.lambda_integration,
|
|
8360
9385
|
aws_api_gateway_method.options_method,
|
|
8361
9386
|
aws_api_gateway_integration.options_integration,
|
|
@@ -8484,11 +9509,13 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
8484
9509
|
|
|
8485
9510
|
# Lambda permission for API Gateway to invoke the function
|
|
8486
9511
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
9512
|
+
for_each = local.operations
|
|
9513
|
+
|
|
8487
9514
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
8488
9515
|
action = "lambda:InvokeFunction"
|
|
8489
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
9516
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
8490
9517
|
principal = "apigateway.amazonaws.com"
|
|
8491
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
9518
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
8492
9519
|
|
|
8493
9520
|
depends_on = [module.rest_api, aws_lambda_function.api_lambda]
|
|
8494
9521
|
}
|
|
@@ -8553,83 +9580,83 @@ output "stage_id" {
|
|
|
8553
9580
|
value = aws_api_gateway_stage.api_stage.id
|
|
8554
9581
|
}
|
|
8555
9582
|
|
|
8556
|
-
# Lambda Function Outputs
|
|
8557
|
-
output "
|
|
8558
|
-
description = "
|
|
8559
|
-
value =
|
|
9583
|
+
# Lambda Function Outputs, keyed by operation name
|
|
9584
|
+
output "operations" {
|
|
9585
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
9586
|
+
value = keys(local.operations)
|
|
8560
9587
|
}
|
|
8561
9588
|
|
|
8562
|
-
output "
|
|
8563
|
-
description = "
|
|
8564
|
-
value = aws_lambda_function.api_lambda.
|
|
9589
|
+
output "lambda_function_names" {
|
|
9590
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
9591
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
8565
9592
|
}
|
|
8566
9593
|
|
|
8567
|
-
output "
|
|
8568
|
-
description = "
|
|
8569
|
-
value = aws_lambda_function.api_lambda.
|
|
9594
|
+
output "lambda_function_arns" {
|
|
9595
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
9596
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
8570
9597
|
}
|
|
8571
9598
|
|
|
8572
|
-
output "
|
|
8573
|
-
description = "
|
|
8574
|
-
value = aws_lambda_function.api_lambda.
|
|
9599
|
+
output "lambda_invoke_arns" {
|
|
9600
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
9601
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
8575
9602
|
}
|
|
8576
9603
|
|
|
8577
|
-
output "
|
|
8578
|
-
description = "
|
|
8579
|
-
value = aws_lambda_function.api_lambda.
|
|
9604
|
+
output "lambda_qualified_arns" {
|
|
9605
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
9606
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
8580
9607
|
}
|
|
8581
9608
|
|
|
8582
|
-
output "
|
|
8583
|
-
description = "
|
|
8584
|
-
value = aws_lambda_function.api_lambda.
|
|
9609
|
+
output "lambda_versions" {
|
|
9610
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
9611
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
8585
9612
|
}
|
|
8586
9613
|
|
|
8587
|
-
output "
|
|
8588
|
-
description = "
|
|
8589
|
-
value =
|
|
9614
|
+
output "lambda_source_code_hash" {
|
|
9615
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
9616
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
8590
9617
|
}
|
|
8591
9618
|
|
|
8592
|
-
# IAM Role Outputs
|
|
8593
|
-
output "
|
|
8594
|
-
description = "ARN of
|
|
8595
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
9619
|
+
# IAM Role Outputs, keyed by operation name
|
|
9620
|
+
output "lambda_execution_role_arns" {
|
|
9621
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
9622
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
8596
9623
|
}
|
|
8597
9624
|
|
|
8598
|
-
output "
|
|
8599
|
-
description = "Name of
|
|
8600
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
9625
|
+
output "lambda_execution_role_names" {
|
|
9626
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
9627
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
8601
9628
|
}
|
|
8602
9629
|
|
|
8603
9630
|
output "security_group_id" {
|
|
8604
|
-
description = "Security group ID
|
|
9631
|
+
description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
|
|
8605
9632
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
8606
9633
|
}
|
|
8607
9634
|
|
|
8608
|
-
# Integration Outputs
|
|
8609
|
-
output "
|
|
8610
|
-
description = "ID of
|
|
8611
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
9635
|
+
# Integration Outputs, keyed by operation name
|
|
9636
|
+
output "integration_ids" {
|
|
9637
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
9638
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
8612
9639
|
}
|
|
8613
9640
|
|
|
8614
|
-
output "
|
|
8615
|
-
description = "ID of
|
|
8616
|
-
value =
|
|
9641
|
+
output "method_ids" {
|
|
9642
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
9643
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
8617
9644
|
}
|
|
8618
9645
|
|
|
8619
|
-
output "
|
|
8620
|
-
description = "ID of
|
|
8621
|
-
value =
|
|
9646
|
+
output "resource_ids" {
|
|
9647
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
9648
|
+
value = local.resource_ids
|
|
8622
9649
|
}
|
|
8623
9650
|
|
|
8624
|
-
# CloudWatch Log Groups
|
|
8625
|
-
output "
|
|
8626
|
-
description = "Name of
|
|
8627
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
9651
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
9652
|
+
output "lambda_log_group_names" {
|
|
9653
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
9654
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
8628
9655
|
}
|
|
8629
9656
|
|
|
8630
|
-
output "
|
|
8631
|
-
description = "ARN of
|
|
8632
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
9657
|
+
output "lambda_log_group_arns" {
|
|
9658
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
9659
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
8633
9660
|
}
|
|
8634
9661
|
",
|
|
8635
9662
|
}
|