@aws/nx-plugin 0.102.0 → 0.103.0

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.
@@ -1026,7 +1026,13 @@ import {
1026
1026
  IResource,
1027
1027
  Stage,
1028
1028
  } from 'aws-cdk-lib/aws-apigateway';
1029
- import { IAspect } from 'aws-cdk-lib';
1029
+ import { IAspect, RemovalPolicy } from 'aws-cdk-lib';
1030
+ import {
1031
+ CfnLoggingConfiguration,
1032
+ CfnWebACL,
1033
+ CfnWebACLAssociation,
1034
+ } from 'aws-cdk-lib/aws-wafv2';
1035
+ import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
1030
1036
  import { RuntimeConfig } from '../runtime-config.js';
1031
1037
  import {
1032
1038
  ApiIntegrations,
@@ -1057,6 +1063,14 @@ export interface RestApiProps<
1057
1063
  * Map of integration keys to their API Gateway integrations
1058
1064
  */
1059
1065
  readonly integrations: TIntegrations;
1066
+ /**
1067
+ * Whether to enable AWS WAFv2 with the default managed ruleset
1068
+ * (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet)
1069
+ * and associate it with the API's default stage.
1070
+ *
1071
+ * @default true
1072
+ */
1073
+ readonly enableWaf?: boolean;
1060
1074
  }
1061
1075
 
1062
1076
  /**
@@ -1080,6 +1094,9 @@ export class RestApi<
1080
1094
  /** Map of integration keys to their API Gateway integrations */
1081
1095
  public readonly integrations: TIntegrations;
1082
1096
 
1097
+ /** The WAFv2 Web ACL associated with the API's default stage, if WAF is enabled */
1098
+ public readonly webAcl?: CfnWebACL;
1099
+
1083
1100
  constructor(
1084
1101
  scope: Construct,
1085
1102
  id: string,
@@ -1087,6 +1104,7 @@ export class RestApi<
1087
1104
  apiName,
1088
1105
  operations,
1089
1106
  integrations,
1107
+ enableWaf = true,
1090
1108
  ...props
1091
1109
  }: RestApiProps<TIntegrations, TOperation>,
1092
1110
  ) {
@@ -1096,6 +1114,86 @@ export class RestApi<
1096
1114
  // Create the API Gateway REST API
1097
1115
  this.api = new _RestApi(this, 'Api', props);
1098
1116
 
1117
+ if (enableWaf) {
1118
+ this.webAcl = new CfnWebACL(this, 'WebAcl', {
1119
+ defaultAction: { allow: {} },
1120
+ scope: 'REGIONAL',
1121
+ visibilityConfig: {
1122
+ cloudWatchMetricsEnabled: true,
1123
+ metricName: \`\${apiName}WebAcl\`,
1124
+ sampledRequestsEnabled: true,
1125
+ },
1126
+ rules: [
1127
+ {
1128
+ name: 'CRSRule',
1129
+ priority: 0,
1130
+ statement: {
1131
+ managedRuleGroupStatement: {
1132
+ name: 'AWSManagedRulesCommonRuleSet',
1133
+ vendorName: 'AWS',
1134
+ // Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
1135
+ ruleActionOverrides: [
1136
+ {
1137
+ name: 'SizeRestrictions_BODY',
1138
+ actionToUse: { count: {} },
1139
+ },
1140
+ ],
1141
+ },
1142
+ },
1143
+ visibilityConfig: {
1144
+ cloudWatchMetricsEnabled: true,
1145
+ metricName: \`\${apiName}WebAcl-CRS\`,
1146
+ sampledRequestsEnabled: true,
1147
+ },
1148
+ overrideAction: {
1149
+ none: {},
1150
+ },
1151
+ },
1152
+ {
1153
+ name: 'KnownBadInputsRule',
1154
+ priority: 1,
1155
+ statement: {
1156
+ managedRuleGroupStatement: {
1157
+ name: 'AWSManagedRulesKnownBadInputsRuleSet',
1158
+ vendorName: 'AWS',
1159
+ },
1160
+ },
1161
+ visibilityConfig: {
1162
+ cloudWatchMetricsEnabled: true,
1163
+ metricName: \`\${apiName}WebAcl-KnownBadInputs\`,
1164
+ sampledRequestsEnabled: true,
1165
+ },
1166
+ overrideAction: {
1167
+ none: {},
1168
+ },
1169
+ },
1170
+ ],
1171
+ });
1172
+
1173
+ new CfnWebACLAssociation(this, 'WebAclAssociation', {
1174
+ resourceArn: this.api.deploymentStage.stageArn,
1175
+ webAclArn: this.webAcl.attrArn,
1176
+ });
1177
+
1178
+ // Send WAF request logs to CloudWatch. The log group name must start with
1179
+ // \`aws-waf-logs-\` to satisfy the WAFv2 logging destination requirement.
1180
+ const wafLogGroup = new LogGroup(this, 'WebAclLogs', {
1181
+ logGroupName: \`aws-waf-logs-\${apiName}-\${this.node.addr.slice(-8)}\`,
1182
+ retention: RetentionDays.ONE_MONTH,
1183
+ removalPolicy: RemovalPolicy.DESTROY,
1184
+ });
1185
+ suppressRules(
1186
+ wafLogGroup,
1187
+ ['CKV_AWS_158'],
1188
+ 'Using default CloudWatch log encryption for WAF logs',
1189
+ );
1190
+
1191
+ new CfnLoggingConfiguration(this, 'WebAclLoggingConfig', {
1192
+ resourceArn: this.webAcl.attrArn,
1193
+ logDestinationConfigs: [wafLogGroup.logGroupArn],
1194
+ });
1195
+ }
1196
+
1099
1197
  suppressRules(
1100
1198
  this.api,
1101
1199
  ['CKV_AWS_120'],
@@ -1239,6 +1337,12 @@ export interface TestApiProps<
1239
1337
  * Map of operation names to their API Gateway integrations
1240
1338
  */
1241
1339
  integrations: TIntegrations;
1340
+ /**
1341
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
1342
+ *
1343
+ * @default true
1344
+ */
1345
+ enableWaf?: boolean;
1242
1346
  }
1243
1347
 
1244
1348
  /**
@@ -3754,6 +3858,10 @@ terraform {
3754
3858
  source = "hashicorp/aws"
3755
3859
  version = "~> 6.33"
3756
3860
  }
3861
+ random = {
3862
+ source = "hashicorp/random"
3863
+ version = "~> 3.1"
3864
+ }
3757
3865
  }
3758
3866
  }
3759
3867
 
@@ -3782,6 +3890,12 @@ variable "stage_auto_deploy" {
3782
3890
  default = true
3783
3891
  }
3784
3892
 
3893
+ variable "enable_waf" {
3894
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
3895
+ type = bool
3896
+ default = true
3897
+ }
3898
+
3785
3899
  # CORS Configuration
3786
3900
 
3787
3901
  variable "cors_allow_headers" {
@@ -3863,6 +3977,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
3863
3977
  }
3864
3978
  }
3865
3979
 
3980
+ resource "aws_wafv2_web_acl" "api_waf" {
3981
+ #checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
3982
+ count = var.enable_waf ? 1 : 0
3983
+
3984
+ name = "\${var.api_name}-waf"
3985
+ scope = "REGIONAL"
3986
+
3987
+ default_action {
3988
+ allow {}
3989
+ }
3990
+
3991
+ rule {
3992
+ name = "CRSRule"
3993
+ priority = 0
3994
+
3995
+ override_action {
3996
+ none {}
3997
+ }
3998
+
3999
+ statement {
4000
+ managed_rule_group_statement {
4001
+ name = "AWSManagedRulesCommonRuleSet"
4002
+ vendor_name = "AWS"
4003
+
4004
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
4005
+ rule_action_override {
4006
+ name = "SizeRestrictions_BODY"
4007
+ action_to_use {
4008
+ count {}
4009
+ }
4010
+ }
4011
+ }
4012
+ }
4013
+
4014
+ visibility_config {
4015
+ cloudwatch_metrics_enabled = true
4016
+ metric_name = "\${var.api_name}WebAcl-CRS"
4017
+ sampled_requests_enabled = true
4018
+ }
4019
+ }
4020
+
4021
+ rule {
4022
+ name = "KnownBadInputsRule"
4023
+ priority = 1
4024
+
4025
+ override_action {
4026
+ none {}
4027
+ }
4028
+
4029
+ statement {
4030
+ managed_rule_group_statement {
4031
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
4032
+ vendor_name = "AWS"
4033
+ }
4034
+ }
4035
+
4036
+ visibility_config {
4037
+ cloudwatch_metrics_enabled = true
4038
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
4039
+ sampled_requests_enabled = true
4040
+ }
4041
+ }
4042
+
4043
+ visibility_config {
4044
+ cloudwatch_metrics_enabled = true
4045
+ metric_name = "\${var.api_name}WebAcl"
4046
+ sampled_requests_enabled = true
4047
+ }
4048
+
4049
+ tags = var.tags
4050
+
4051
+ lifecycle {
4052
+ create_before_destroy = true
4053
+ }
4054
+ }
4055
+
4056
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
4057
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
4058
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
4059
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
4060
+ count = var.enable_waf ? 1 : 0
4061
+
4062
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
4063
+ retention_in_days = 30
4064
+ tags = var.tags
4065
+ }
4066
+
4067
+ resource "random_id" "waf_log_suffix" {
4068
+ count = var.enable_waf ? 1 : 0
4069
+ byte_length = 4
4070
+ }
4071
+
4072
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
4073
+ count = var.enable_waf ? 1 : 0
4074
+
4075
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
4076
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
4077
+ }
4078
+
4079
+
3866
4080
  # Outputs
3867
4081
 
3868
4082
  output "api_id" {
@@ -3890,6 +4104,11 @@ output "api_root_resource_id" {
3890
4104
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
3891
4105
  }
3892
4106
 
4107
+ output "waf_web_acl_arn" {
4108
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
4109
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
4110
+ }
4111
+
3893
4112
  # Note: Stage and deployment outputs are provided by the consuming module
3894
4113
 
3895
4114
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -3951,6 +4170,13 @@ variable "cors_allow_origins" {
3951
4170
  default = ["*"]
3952
4171
  }
3953
4172
 
4173
+ # WAF Configuration
4174
+ variable "enable_waf" {
4175
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
4176
+ type = bool
4177
+ default = true
4178
+ }
4179
+
3954
4180
  # Tags
3955
4181
  variable "tags" {
3956
4182
  description = "Tags to apply to all resources"
@@ -3980,6 +4206,9 @@ module "rest_api" {
3980
4206
  stage_name = "prod"
3981
4207
  stage_auto_deploy = true
3982
4208
 
4209
+ # WAF Configuration
4210
+ enable_waf = var.enable_waf
4211
+
3983
4212
  # CORS Configuration
3984
4213
  cors_allow_headers = var.cors_allow_headers
3985
4214
  cors_allow_methods = var.cors_allow_methods
@@ -3989,6 +4218,15 @@ module "rest_api" {
3989
4218
  tags = var.tags
3990
4219
  }
3991
4220
 
4221
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
4222
+ count = var.enable_waf ? 1 : 0
4223
+
4224
+ resource_arn = aws_api_gateway_stage.api_stage.arn
4225
+ web_acl_arn = module.rest_api.waf_web_acl_arn
4226
+
4227
+ depends_on = [aws_api_gateway_stage.api_stage]
4228
+ }
4229
+
3992
4230
  # Lambda function
3993
4231
  resource "aws_lambda_function" "api_lambda" {
3994
4232
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -4223,6 +4461,7 @@ resource "aws_api_gateway_stage" "api_stage" {
4223
4461
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
4224
4462
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
4225
4463
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
4464
+ #checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
4226
4465
  deployment_id = aws_api_gateway_deployment.api_deployment.id
4227
4466
  rest_api_id = module.rest_api.api_id
4228
4467
  stage_name = "prod"
@@ -4432,6 +4671,10 @@ terraform {
4432
4671
  source = "hashicorp/aws"
4433
4672
  version = "~> 6.33"
4434
4673
  }
4674
+ random = {
4675
+ source = "hashicorp/random"
4676
+ version = "~> 3.1"
4677
+ }
4435
4678
  }
4436
4679
  }
4437
4680
 
@@ -4460,6 +4703,12 @@ variable "stage_auto_deploy" {
4460
4703
  default = true
4461
4704
  }
4462
4705
 
4706
+ variable "enable_waf" {
4707
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
4708
+ type = bool
4709
+ default = true
4710
+ }
4711
+
4463
4712
  # CORS Configuration
4464
4713
 
4465
4714
  variable "cors_allow_headers" {
@@ -4541,6 +4790,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
4541
4790
  }
4542
4791
  }
4543
4792
 
4793
+ resource "aws_wafv2_web_acl" "api_waf" {
4794
+ #checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
4795
+ count = var.enable_waf ? 1 : 0
4796
+
4797
+ name = "\${var.api_name}-waf"
4798
+ scope = "REGIONAL"
4799
+
4800
+ default_action {
4801
+ allow {}
4802
+ }
4803
+
4804
+ rule {
4805
+ name = "CRSRule"
4806
+ priority = 0
4807
+
4808
+ override_action {
4809
+ none {}
4810
+ }
4811
+
4812
+ statement {
4813
+ managed_rule_group_statement {
4814
+ name = "AWSManagedRulesCommonRuleSet"
4815
+ vendor_name = "AWS"
4816
+
4817
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
4818
+ rule_action_override {
4819
+ name = "SizeRestrictions_BODY"
4820
+ action_to_use {
4821
+ count {}
4822
+ }
4823
+ }
4824
+ }
4825
+ }
4826
+
4827
+ visibility_config {
4828
+ cloudwatch_metrics_enabled = true
4829
+ metric_name = "\${var.api_name}WebAcl-CRS"
4830
+ sampled_requests_enabled = true
4831
+ }
4832
+ }
4833
+
4834
+ rule {
4835
+ name = "KnownBadInputsRule"
4836
+ priority = 1
4837
+
4838
+ override_action {
4839
+ none {}
4840
+ }
4841
+
4842
+ statement {
4843
+ managed_rule_group_statement {
4844
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
4845
+ vendor_name = "AWS"
4846
+ }
4847
+ }
4848
+
4849
+ visibility_config {
4850
+ cloudwatch_metrics_enabled = true
4851
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
4852
+ sampled_requests_enabled = true
4853
+ }
4854
+ }
4855
+
4856
+ visibility_config {
4857
+ cloudwatch_metrics_enabled = true
4858
+ metric_name = "\${var.api_name}WebAcl"
4859
+ sampled_requests_enabled = true
4860
+ }
4861
+
4862
+ tags = var.tags
4863
+
4864
+ lifecycle {
4865
+ create_before_destroy = true
4866
+ }
4867
+ }
4868
+
4869
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
4870
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
4871
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
4872
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
4873
+ count = var.enable_waf ? 1 : 0
4874
+
4875
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
4876
+ retention_in_days = 30
4877
+ tags = var.tags
4878
+ }
4879
+
4880
+ resource "random_id" "waf_log_suffix" {
4881
+ count = var.enable_waf ? 1 : 0
4882
+ byte_length = 4
4883
+ }
4884
+
4885
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
4886
+ count = var.enable_waf ? 1 : 0
4887
+
4888
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
4889
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
4890
+ }
4891
+
4892
+
4544
4893
  # Outputs
4545
4894
 
4546
4895
  output "api_id" {
@@ -4568,6 +4917,11 @@ output "api_root_resource_id" {
4568
4917
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
4569
4918
  }
4570
4919
 
4920
+ output "waf_web_acl_arn" {
4921
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
4922
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
4923
+ }
4924
+
4571
4925
  # Note: Stage and deployment outputs are provided by the consuming module
4572
4926
 
4573
4927
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -4619,6 +4973,13 @@ variable "cors_allow_origins" {
4619
4973
  default = ["*"]
4620
4974
  }
4621
4975
 
4976
+ # WAF Configuration
4977
+ variable "enable_waf" {
4978
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
4979
+ type = bool
4980
+ default = true
4981
+ }
4982
+
4622
4983
  # Tags
4623
4984
  variable "tags" {
4624
4985
  description = "Tags to apply to all resources"
@@ -4648,6 +5009,9 @@ module "rest_api" {
4648
5009
  stage_name = "prod"
4649
5010
  stage_auto_deploy = true
4650
5011
 
5012
+ # WAF Configuration
5013
+ enable_waf = var.enable_waf
5014
+
4651
5015
  # CORS Configuration
4652
5016
  cors_allow_headers = var.cors_allow_headers
4653
5017
  cors_allow_methods = var.cors_allow_methods
@@ -4657,6 +5021,15 @@ module "rest_api" {
4657
5021
  tags = var.tags
4658
5022
  }
4659
5023
 
5024
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
5025
+ count = var.enable_waf ? 1 : 0
5026
+
5027
+ resource_arn = aws_api_gateway_stage.api_stage.arn
5028
+ web_acl_arn = module.rest_api.waf_web_acl_arn
5029
+
5030
+ depends_on = [aws_api_gateway_stage.api_stage]
5031
+ }
5032
+
4660
5033
  # Lambda function
4661
5034
  resource "aws_lambda_function" "api_lambda" {
4662
5035
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -4882,6 +5255,7 @@ resource "aws_api_gateway_stage" "api_stage" {
4882
5255
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
4883
5256
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
4884
5257
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
5258
+ #checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
4885
5259
  deployment_id = aws_api_gateway_deployment.api_deployment.id
4886
5260
  rest_api_id = module.rest_api.api_id
4887
5261
  stage_name = "prod"
@@ -5102,6 +5476,10 @@ terraform {
5102
5476
  source = "hashicorp/aws"
5103
5477
  version = "~> 6.33"
5104
5478
  }
5479
+ random = {
5480
+ source = "hashicorp/random"
5481
+ version = "~> 3.1"
5482
+ }
5105
5483
  }
5106
5484
  }
5107
5485
 
@@ -5130,6 +5508,12 @@ variable "stage_auto_deploy" {
5130
5508
  default = true
5131
5509
  }
5132
5510
 
5511
+ variable "enable_waf" {
5512
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet). The Web ACL is created here and associated with the stage in the consuming module."
5513
+ type = bool
5514
+ default = true
5515
+ }
5516
+
5133
5517
  # CORS Configuration
5134
5518
 
5135
5519
  variable "cors_allow_headers" {
@@ -5211,6 +5595,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
5211
5595
  }
5212
5596
  }
5213
5597
 
5598
+ resource "aws_wafv2_web_acl" "api_waf" {
5599
+ #checkov:skip=CKV2_AWS_31:Logging configuration is defined below in aws_wafv2_web_acl_logging_configuration.api_waf_logging; Checkov does not resolve the separate resource
5600
+ count = var.enable_waf ? 1 : 0
5601
+
5602
+ name = "\${var.api_name}-waf"
5603
+ scope = "REGIONAL"
5604
+
5605
+ default_action {
5606
+ allow {}
5607
+ }
5608
+
5609
+ rule {
5610
+ name = "CRSRule"
5611
+ priority = 0
5612
+
5613
+ override_action {
5614
+ none {}
5615
+ }
5616
+
5617
+ statement {
5618
+ managed_rule_group_statement {
5619
+ name = "AWSManagedRulesCommonRuleSet"
5620
+ vendor_name = "AWS"
5621
+
5622
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
5623
+ rule_action_override {
5624
+ name = "SizeRestrictions_BODY"
5625
+ action_to_use {
5626
+ count {}
5627
+ }
5628
+ }
5629
+ }
5630
+ }
5631
+
5632
+ visibility_config {
5633
+ cloudwatch_metrics_enabled = true
5634
+ metric_name = "\${var.api_name}WebAcl-CRS"
5635
+ sampled_requests_enabled = true
5636
+ }
5637
+ }
5638
+
5639
+ rule {
5640
+ name = "KnownBadInputsRule"
5641
+ priority = 1
5642
+
5643
+ override_action {
5644
+ none {}
5645
+ }
5646
+
5647
+ statement {
5648
+ managed_rule_group_statement {
5649
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
5650
+ vendor_name = "AWS"
5651
+ }
5652
+ }
5653
+
5654
+ visibility_config {
5655
+ cloudwatch_metrics_enabled = true
5656
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
5657
+ sampled_requests_enabled = true
5658
+ }
5659
+ }
5660
+
5661
+ visibility_config {
5662
+ cloudwatch_metrics_enabled = true
5663
+ metric_name = "\${var.api_name}WebAcl"
5664
+ sampled_requests_enabled = true
5665
+ }
5666
+
5667
+ tags = var.tags
5668
+
5669
+ lifecycle {
5670
+ create_before_destroy = true
5671
+ }
5672
+ }
5673
+
5674
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
5675
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
5676
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
5677
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
5678
+ count = var.enable_waf ? 1 : 0
5679
+
5680
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
5681
+ retention_in_days = 30
5682
+ tags = var.tags
5683
+ }
5684
+
5685
+ resource "random_id" "waf_log_suffix" {
5686
+ count = var.enable_waf ? 1 : 0
5687
+ byte_length = 4
5688
+ }
5689
+
5690
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
5691
+ count = var.enable_waf ? 1 : 0
5692
+
5693
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
5694
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
5695
+ }
5696
+
5697
+
5214
5698
  # Outputs
5215
5699
 
5216
5700
  output "api_id" {
@@ -5238,6 +5722,11 @@ output "api_root_resource_id" {
5238
5722
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
5239
5723
  }
5240
5724
 
5725
+ output "waf_web_acl_arn" {
5726
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
5727
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
5728
+ }
5729
+
5241
5730
  # Note: Stage and deployment outputs are provided by the consuming module
5242
5731
 
5243
5732
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -5289,6 +5778,13 @@ variable "cors_allow_origins" {
5289
5778
  default = ["*"]
5290
5779
  }
5291
5780
 
5781
+ # WAF Configuration
5782
+ variable "enable_waf" {
5783
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
5784
+ type = bool
5785
+ default = true
5786
+ }
5787
+
5292
5788
  # Tags
5293
5789
  variable "tags" {
5294
5790
  description = "Tags to apply to all resources"
@@ -5318,6 +5814,9 @@ module "rest_api" {
5318
5814
  stage_name = "prod"
5319
5815
  stage_auto_deploy = true
5320
5816
 
5817
+ # WAF Configuration
5818
+ enable_waf = var.enable_waf
5819
+
5321
5820
  # CORS Configuration
5322
5821
  cors_allow_headers = var.cors_allow_headers
5323
5822
  cors_allow_methods = var.cors_allow_methods
@@ -5327,6 +5826,15 @@ module "rest_api" {
5327
5826
  tags = var.tags
5328
5827
  }
5329
5828
 
5829
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
5830
+ count = var.enable_waf ? 1 : 0
5831
+
5832
+ resource_arn = aws_api_gateway_stage.api_stage.arn
5833
+ web_acl_arn = module.rest_api.waf_web_acl_arn
5834
+
5835
+ depends_on = [aws_api_gateway_stage.api_stage]
5836
+ }
5837
+
5330
5838
  # Lambda function
5331
5839
  resource "aws_lambda_function" "api_lambda" {
5332
5840
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -5554,6 +6062,7 @@ resource "aws_api_gateway_stage" "api_stage" {
5554
6062
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
5555
6063
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
5556
6064
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
6065
+ #checkov:skip=CKV2_AWS_77:WAFv2 Web ACL is attached via aws_wafv2_web_acl_association when var.enable_waf is true (default) and includes AWSManagedRulesKnownBadInputsRuleSet for Log4j protection; Checkov does not track the association across modules
5557
6066
  deployment_id = aws_api_gateway_deployment.api_deployment.id
5558
6067
  rest_api_id = module.rest_api.api_id
5559
6068
  stage_name = "prod"