@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.
@@ -405,6 +405,12 @@ export interface TestApiProps<
405
405
  identity: {
406
406
  userPool: IUserPool;
407
407
  };
408
+ /**
409
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
410
+ *
411
+ * @default true
412
+ */
413
+ enableWaf?: boolean;
408
414
  }
409
415
 
410
416
  /**
@@ -813,6 +819,12 @@ export interface TestApiProps<
813
819
  * Map of operation names to their API Gateway integrations
814
820
  */
815
821
  integrations: TIntegrations;
822
+ /**
823
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
824
+ *
825
+ * @default true
826
+ */
827
+ enableWaf?: boolean;
816
828
  }
817
829
 
818
830
  /**
@@ -1960,7 +1972,13 @@ import {
1960
1972
  IResource,
1961
1973
  Stage,
1962
1974
  } from 'aws-cdk-lib/aws-apigateway';
1963
- import { IAspect } from 'aws-cdk-lib';
1975
+ import { IAspect, RemovalPolicy } from 'aws-cdk-lib';
1976
+ import {
1977
+ CfnLoggingConfiguration,
1978
+ CfnWebACL,
1979
+ CfnWebACLAssociation,
1980
+ } from 'aws-cdk-lib/aws-wafv2';
1981
+ import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
1964
1982
  import { RuntimeConfig } from '../runtime-config.js';
1965
1983
  import {
1966
1984
  ApiIntegrations,
@@ -1991,6 +2009,14 @@ export interface RestApiProps<
1991
2009
  * Map of integration keys to their API Gateway integrations
1992
2010
  */
1993
2011
  readonly integrations: TIntegrations;
2012
+ /**
2013
+ * Whether to enable AWS WAFv2 with the default managed ruleset
2014
+ * (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet)
2015
+ * and associate it with the API's default stage.
2016
+ *
2017
+ * @default true
2018
+ */
2019
+ readonly enableWaf?: boolean;
1994
2020
  }
1995
2021
 
1996
2022
  /**
@@ -2014,6 +2040,9 @@ export class RestApi<
2014
2040
  /** Map of integration keys to their API Gateway integrations */
2015
2041
  public readonly integrations: TIntegrations;
2016
2042
 
2043
+ /** The WAFv2 Web ACL associated with the API's default stage, if WAF is enabled */
2044
+ public readonly webAcl?: CfnWebACL;
2045
+
2017
2046
  constructor(
2018
2047
  scope: Construct,
2019
2048
  id: string,
@@ -2021,6 +2050,7 @@ export class RestApi<
2021
2050
  apiName,
2022
2051
  operations,
2023
2052
  integrations,
2053
+ enableWaf = true,
2024
2054
  ...props
2025
2055
  }: RestApiProps<TIntegrations, TOperation>,
2026
2056
  ) {
@@ -2030,6 +2060,86 @@ export class RestApi<
2030
2060
  // Create the API Gateway REST API
2031
2061
  this.api = new _RestApi(this, 'Api', props);
2032
2062
 
2063
+ if (enableWaf) {
2064
+ this.webAcl = new CfnWebACL(this, 'WebAcl', {
2065
+ defaultAction: { allow: {} },
2066
+ scope: 'REGIONAL',
2067
+ visibilityConfig: {
2068
+ cloudWatchMetricsEnabled: true,
2069
+ metricName: \`\${apiName}WebAcl\`,
2070
+ sampledRequestsEnabled: true,
2071
+ },
2072
+ rules: [
2073
+ {
2074
+ name: 'CRSRule',
2075
+ priority: 0,
2076
+ statement: {
2077
+ managedRuleGroupStatement: {
2078
+ name: 'AWSManagedRulesCommonRuleSet',
2079
+ vendorName: 'AWS',
2080
+ // Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
2081
+ ruleActionOverrides: [
2082
+ {
2083
+ name: 'SizeRestrictions_BODY',
2084
+ actionToUse: { count: {} },
2085
+ },
2086
+ ],
2087
+ },
2088
+ },
2089
+ visibilityConfig: {
2090
+ cloudWatchMetricsEnabled: true,
2091
+ metricName: \`\${apiName}WebAcl-CRS\`,
2092
+ sampledRequestsEnabled: true,
2093
+ },
2094
+ overrideAction: {
2095
+ none: {},
2096
+ },
2097
+ },
2098
+ {
2099
+ name: 'KnownBadInputsRule',
2100
+ priority: 1,
2101
+ statement: {
2102
+ managedRuleGroupStatement: {
2103
+ name: 'AWSManagedRulesKnownBadInputsRuleSet',
2104
+ vendorName: 'AWS',
2105
+ },
2106
+ },
2107
+ visibilityConfig: {
2108
+ cloudWatchMetricsEnabled: true,
2109
+ metricName: \`\${apiName}WebAcl-KnownBadInputs\`,
2110
+ sampledRequestsEnabled: true,
2111
+ },
2112
+ overrideAction: {
2113
+ none: {},
2114
+ },
2115
+ },
2116
+ ],
2117
+ });
2118
+
2119
+ new CfnWebACLAssociation(this, 'WebAclAssociation', {
2120
+ resourceArn: this.api.deploymentStage.stageArn,
2121
+ webAclArn: this.webAcl.attrArn,
2122
+ });
2123
+
2124
+ // Send WAF request logs to CloudWatch. The log group name must start with
2125
+ // \`aws-waf-logs-\` to satisfy the WAFv2 logging destination requirement.
2126
+ const wafLogGroup = new LogGroup(this, 'WebAclLogs', {
2127
+ logGroupName: \`aws-waf-logs-\${apiName}-\${this.node.addr.slice(-8)}\`,
2128
+ retention: RetentionDays.ONE_MONTH,
2129
+ removalPolicy: RemovalPolicy.DESTROY,
2130
+ });
2131
+ suppressRules(
2132
+ wafLogGroup,
2133
+ ['CKV_AWS_158'],
2134
+ 'Using default CloudWatch log encryption for WAF logs',
2135
+ );
2136
+
2137
+ new CfnLoggingConfiguration(this, 'WebAclLoggingConfig', {
2138
+ resourceArn: this.webAcl.attrArn,
2139
+ logDestinationConfigs: [wafLogGroup.logGroupArn],
2140
+ });
2141
+ }
2142
+
2033
2143
  suppressRules(
2034
2144
  this.api,
2035
2145
  ['CKV_AWS_120'],
@@ -2172,6 +2282,12 @@ export interface TestApiProps<
2172
2282
  * Map of operation names to their API Gateway integrations
2173
2283
  */
2174
2284
  integrations: TIntegrations;
2285
+ /**
2286
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
2287
+ *
2288
+ * @default true
2289
+ */
2290
+ enableWaf?: boolean;
2175
2291
  }
2176
2292
 
2177
2293
  /**
@@ -4692,6 +4808,10 @@ terraform {
4692
4808
  source = "hashicorp/aws"
4693
4809
  version = "~> 6.33"
4694
4810
  }
4811
+ random = {
4812
+ source = "hashicorp/random"
4813
+ version = "~> 3.1"
4814
+ }
4695
4815
  }
4696
4816
  }
4697
4817
 
@@ -4720,6 +4840,12 @@ variable "stage_auto_deploy" {
4720
4840
  default = true
4721
4841
  }
4722
4842
 
4843
+ variable "enable_waf" {
4844
+ 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."
4845
+ type = bool
4846
+ default = true
4847
+ }
4848
+
4723
4849
  # CORS Configuration
4724
4850
 
4725
4851
  variable "cors_allow_headers" {
@@ -4801,6 +4927,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
4801
4927
  }
4802
4928
  }
4803
4929
 
4930
+ resource "aws_wafv2_web_acl" "api_waf" {
4931
+ #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
4932
+ count = var.enable_waf ? 1 : 0
4933
+
4934
+ name = "\${var.api_name}-waf"
4935
+ scope = "REGIONAL"
4936
+
4937
+ default_action {
4938
+ allow {}
4939
+ }
4940
+
4941
+ rule {
4942
+ name = "CRSRule"
4943
+ priority = 0
4944
+
4945
+ override_action {
4946
+ none {}
4947
+ }
4948
+
4949
+ statement {
4950
+ managed_rule_group_statement {
4951
+ name = "AWSManagedRulesCommonRuleSet"
4952
+ vendor_name = "AWS"
4953
+
4954
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
4955
+ rule_action_override {
4956
+ name = "SizeRestrictions_BODY"
4957
+ action_to_use {
4958
+ count {}
4959
+ }
4960
+ }
4961
+ }
4962
+ }
4963
+
4964
+ visibility_config {
4965
+ cloudwatch_metrics_enabled = true
4966
+ metric_name = "\${var.api_name}WebAcl-CRS"
4967
+ sampled_requests_enabled = true
4968
+ }
4969
+ }
4970
+
4971
+ rule {
4972
+ name = "KnownBadInputsRule"
4973
+ priority = 1
4974
+
4975
+ override_action {
4976
+ none {}
4977
+ }
4978
+
4979
+ statement {
4980
+ managed_rule_group_statement {
4981
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
4982
+ vendor_name = "AWS"
4983
+ }
4984
+ }
4985
+
4986
+ visibility_config {
4987
+ cloudwatch_metrics_enabled = true
4988
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
4989
+ sampled_requests_enabled = true
4990
+ }
4991
+ }
4992
+
4993
+ visibility_config {
4994
+ cloudwatch_metrics_enabled = true
4995
+ metric_name = "\${var.api_name}WebAcl"
4996
+ sampled_requests_enabled = true
4997
+ }
4998
+
4999
+ tags = var.tags
5000
+
5001
+ lifecycle {
5002
+ create_before_destroy = true
5003
+ }
5004
+ }
5005
+
5006
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
5007
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
5008
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
5009
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
5010
+ count = var.enable_waf ? 1 : 0
5011
+
5012
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
5013
+ retention_in_days = 30
5014
+ tags = var.tags
5015
+ }
5016
+
5017
+ resource "random_id" "waf_log_suffix" {
5018
+ count = var.enable_waf ? 1 : 0
5019
+ byte_length = 4
5020
+ }
5021
+
5022
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
5023
+ count = var.enable_waf ? 1 : 0
5024
+
5025
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
5026
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
5027
+ }
5028
+
5029
+
4804
5030
  # Outputs
4805
5031
 
4806
5032
  output "api_id" {
@@ -4828,6 +5054,11 @@ output "api_root_resource_id" {
4828
5054
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
4829
5055
  }
4830
5056
 
5057
+ output "waf_web_acl_arn" {
5058
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
5059
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
5060
+ }
5061
+
4831
5062
  # Note: Stage and deployment outputs are provided by the consuming module
4832
5063
 
4833
5064
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -4889,6 +5120,13 @@ variable "cors_allow_origins" {
4889
5120
  default = ["*"]
4890
5121
  }
4891
5122
 
5123
+ # WAF Configuration
5124
+ variable "enable_waf" {
5125
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
5126
+ type = bool
5127
+ default = true
5128
+ }
5129
+
4892
5130
  # Tags
4893
5131
  variable "tags" {
4894
5132
  description = "Tags to apply to all resources"
@@ -4918,6 +5156,9 @@ module "rest_api" {
4918
5156
  stage_name = "prod"
4919
5157
  stage_auto_deploy = true
4920
5158
 
5159
+ # WAF Configuration
5160
+ enable_waf = var.enable_waf
5161
+
4921
5162
  # CORS Configuration
4922
5163
  cors_allow_headers = var.cors_allow_headers
4923
5164
  cors_allow_methods = var.cors_allow_methods
@@ -4927,6 +5168,15 @@ module "rest_api" {
4927
5168
  tags = var.tags
4928
5169
  }
4929
5170
 
5171
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
5172
+ count = var.enable_waf ? 1 : 0
5173
+
5174
+ resource_arn = aws_api_gateway_stage.api_stage.arn
5175
+ web_acl_arn = module.rest_api.waf_web_acl_arn
5176
+
5177
+ depends_on = [aws_api_gateway_stage.api_stage]
5178
+ }
5179
+
4930
5180
  # Lambda function
4931
5181
  resource "aws_lambda_function" "api_lambda" {
4932
5182
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -5143,6 +5393,7 @@ resource "aws_api_gateway_stage" "api_stage" {
5143
5393
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
5144
5394
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
5145
5395
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
5396
+ #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
5146
5397
  deployment_id = aws_api_gateway_deployment.api_deployment.id
5147
5398
  rest_api_id = module.rest_api.api_id
5148
5399
  stage_name = "prod"
@@ -5340,6 +5591,10 @@ terraform {
5340
5591
  source = "hashicorp/aws"
5341
5592
  version = "~> 6.33"
5342
5593
  }
5594
+ random = {
5595
+ source = "hashicorp/random"
5596
+ version = "~> 3.1"
5597
+ }
5343
5598
  }
5344
5599
  }
5345
5600
 
@@ -5368,6 +5623,12 @@ variable "stage_auto_deploy" {
5368
5623
  default = true
5369
5624
  }
5370
5625
 
5626
+ variable "enable_waf" {
5627
+ 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."
5628
+ type = bool
5629
+ default = true
5630
+ }
5631
+
5371
5632
  # CORS Configuration
5372
5633
 
5373
5634
  variable "cors_allow_headers" {
@@ -5449,6 +5710,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
5449
5710
  }
5450
5711
  }
5451
5712
 
5713
+ resource "aws_wafv2_web_acl" "api_waf" {
5714
+ #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
5715
+ count = var.enable_waf ? 1 : 0
5716
+
5717
+ name = "\${var.api_name}-waf"
5718
+ scope = "REGIONAL"
5719
+
5720
+ default_action {
5721
+ allow {}
5722
+ }
5723
+
5724
+ rule {
5725
+ name = "CRSRule"
5726
+ priority = 0
5727
+
5728
+ override_action {
5729
+ none {}
5730
+ }
5731
+
5732
+ statement {
5733
+ managed_rule_group_statement {
5734
+ name = "AWSManagedRulesCommonRuleSet"
5735
+ vendor_name = "AWS"
5736
+
5737
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
5738
+ rule_action_override {
5739
+ name = "SizeRestrictions_BODY"
5740
+ action_to_use {
5741
+ count {}
5742
+ }
5743
+ }
5744
+ }
5745
+ }
5746
+
5747
+ visibility_config {
5748
+ cloudwatch_metrics_enabled = true
5749
+ metric_name = "\${var.api_name}WebAcl-CRS"
5750
+ sampled_requests_enabled = true
5751
+ }
5752
+ }
5753
+
5754
+ rule {
5755
+ name = "KnownBadInputsRule"
5756
+ priority = 1
5757
+
5758
+ override_action {
5759
+ none {}
5760
+ }
5761
+
5762
+ statement {
5763
+ managed_rule_group_statement {
5764
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
5765
+ vendor_name = "AWS"
5766
+ }
5767
+ }
5768
+
5769
+ visibility_config {
5770
+ cloudwatch_metrics_enabled = true
5771
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
5772
+ sampled_requests_enabled = true
5773
+ }
5774
+ }
5775
+
5776
+ visibility_config {
5777
+ cloudwatch_metrics_enabled = true
5778
+ metric_name = "\${var.api_name}WebAcl"
5779
+ sampled_requests_enabled = true
5780
+ }
5781
+
5782
+ tags = var.tags
5783
+
5784
+ lifecycle {
5785
+ create_before_destroy = true
5786
+ }
5787
+ }
5788
+
5789
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
5790
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
5791
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
5792
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
5793
+ count = var.enable_waf ? 1 : 0
5794
+
5795
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
5796
+ retention_in_days = 30
5797
+ tags = var.tags
5798
+ }
5799
+
5800
+ resource "random_id" "waf_log_suffix" {
5801
+ count = var.enable_waf ? 1 : 0
5802
+ byte_length = 4
5803
+ }
5804
+
5805
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
5806
+ count = var.enable_waf ? 1 : 0
5807
+
5808
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
5809
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
5810
+ }
5811
+
5812
+
5452
5813
  # Outputs
5453
5814
 
5454
5815
  output "api_id" {
@@ -5476,6 +5837,11 @@ output "api_root_resource_id" {
5476
5837
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
5477
5838
  }
5478
5839
 
5840
+ output "waf_web_acl_arn" {
5841
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
5842
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
5843
+ }
5844
+
5479
5845
  # Note: Stage and deployment outputs are provided by the consuming module
5480
5846
 
5481
5847
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -5527,6 +5893,13 @@ variable "cors_allow_origins" {
5527
5893
  default = ["*"]
5528
5894
  }
5529
5895
 
5896
+ # WAF Configuration
5897
+ variable "enable_waf" {
5898
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
5899
+ type = bool
5900
+ default = true
5901
+ }
5902
+
5530
5903
  # Tags
5531
5904
  variable "tags" {
5532
5905
  description = "Tags to apply to all resources"
@@ -5556,6 +5929,9 @@ module "rest_api" {
5556
5929
  stage_name = "prod"
5557
5930
  stage_auto_deploy = true
5558
5931
 
5932
+ # WAF Configuration
5933
+ enable_waf = var.enable_waf
5934
+
5559
5935
  # CORS Configuration
5560
5936
  cors_allow_headers = var.cors_allow_headers
5561
5937
  cors_allow_methods = var.cors_allow_methods
@@ -5565,6 +5941,15 @@ module "rest_api" {
5565
5941
  tags = var.tags
5566
5942
  }
5567
5943
 
5944
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
5945
+ count = var.enable_waf ? 1 : 0
5946
+
5947
+ resource_arn = aws_api_gateway_stage.api_stage.arn
5948
+ web_acl_arn = module.rest_api.waf_web_acl_arn
5949
+
5950
+ depends_on = [aws_api_gateway_stage.api_stage]
5951
+ }
5952
+
5568
5953
  # Lambda function
5569
5954
  resource "aws_lambda_function" "api_lambda" {
5570
5955
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -5772,6 +6157,7 @@ resource "aws_api_gateway_stage" "api_stage" {
5772
6157
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
5773
6158
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
5774
6159
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
6160
+ #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
5775
6161
  deployment_id = aws_api_gateway_deployment.api_deployment.id
5776
6162
  rest_api_id = module.rest_api.api_id
5777
6163
  stage_name = "prod"
@@ -5980,6 +6366,10 @@ terraform {
5980
6366
  source = "hashicorp/aws"
5981
6367
  version = "~> 6.33"
5982
6368
  }
6369
+ random = {
6370
+ source = "hashicorp/random"
6371
+ version = "~> 3.1"
6372
+ }
5983
6373
  }
5984
6374
  }
5985
6375
 
@@ -6008,6 +6398,12 @@ variable "stage_auto_deploy" {
6008
6398
  default = true
6009
6399
  }
6010
6400
 
6401
+ variable "enable_waf" {
6402
+ 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."
6403
+ type = bool
6404
+ default = true
6405
+ }
6406
+
6011
6407
  # CORS Configuration
6012
6408
 
6013
6409
  variable "cors_allow_headers" {
@@ -6089,6 +6485,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
6089
6485
  }
6090
6486
  }
6091
6487
 
6488
+ resource "aws_wafv2_web_acl" "api_waf" {
6489
+ #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
6490
+ count = var.enable_waf ? 1 : 0
6491
+
6492
+ name = "\${var.api_name}-waf"
6493
+ scope = "REGIONAL"
6494
+
6495
+ default_action {
6496
+ allow {}
6497
+ }
6498
+
6499
+ rule {
6500
+ name = "CRSRule"
6501
+ priority = 0
6502
+
6503
+ override_action {
6504
+ none {}
6505
+ }
6506
+
6507
+ statement {
6508
+ managed_rule_group_statement {
6509
+ name = "AWSManagedRulesCommonRuleSet"
6510
+ vendor_name = "AWS"
6511
+
6512
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
6513
+ rule_action_override {
6514
+ name = "SizeRestrictions_BODY"
6515
+ action_to_use {
6516
+ count {}
6517
+ }
6518
+ }
6519
+ }
6520
+ }
6521
+
6522
+ visibility_config {
6523
+ cloudwatch_metrics_enabled = true
6524
+ metric_name = "\${var.api_name}WebAcl-CRS"
6525
+ sampled_requests_enabled = true
6526
+ }
6527
+ }
6528
+
6529
+ rule {
6530
+ name = "KnownBadInputsRule"
6531
+ priority = 1
6532
+
6533
+ override_action {
6534
+ none {}
6535
+ }
6536
+
6537
+ statement {
6538
+ managed_rule_group_statement {
6539
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
6540
+ vendor_name = "AWS"
6541
+ }
6542
+ }
6543
+
6544
+ visibility_config {
6545
+ cloudwatch_metrics_enabled = true
6546
+ metric_name = "\${var.api_name}WebAcl-KnownBadInputs"
6547
+ sampled_requests_enabled = true
6548
+ }
6549
+ }
6550
+
6551
+ visibility_config {
6552
+ cloudwatch_metrics_enabled = true
6553
+ metric_name = "\${var.api_name}WebAcl"
6554
+ sampled_requests_enabled = true
6555
+ }
6556
+
6557
+ tags = var.tags
6558
+
6559
+ lifecycle {
6560
+ create_before_destroy = true
6561
+ }
6562
+ }
6563
+
6564
+ # CloudWatch Log Group for WAF request logs. Name must start with \`aws-waf-logs-\`.
6565
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
6566
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
6567
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
6568
+ count = var.enable_waf ? 1 : 0
6569
+
6570
+ name = "aws-waf-logs-\${var.api_name}-\${random_id.waf_log_suffix[0].hex}"
6571
+ retention_in_days = 30
6572
+ tags = var.tags
6573
+ }
6574
+
6575
+ resource "random_id" "waf_log_suffix" {
6576
+ count = var.enable_waf ? 1 : 0
6577
+ byte_length = 4
6578
+ }
6579
+
6580
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
6581
+ count = var.enable_waf ? 1 : 0
6582
+
6583
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
6584
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
6585
+ }
6586
+
6587
+
6092
6588
  # Outputs
6093
6589
 
6094
6590
  output "api_id" {
@@ -6116,6 +6612,11 @@ output "api_root_resource_id" {
6116
6612
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
6117
6613
  }
6118
6614
 
6615
+ output "waf_web_acl_arn" {
6616
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
6617
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
6618
+ }
6619
+
6119
6620
  # Note: Stage and deployment outputs are provided by the consuming module
6120
6621
 
6121
6622
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement",
@@ -6167,6 +6668,13 @@ variable "cors_allow_origins" {
6167
6668
  default = ["*"]
6168
6669
  }
6169
6670
 
6671
+ # WAF Configuration
6672
+ variable "enable_waf" {
6673
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
6674
+ type = bool
6675
+ default = true
6676
+ }
6677
+
6170
6678
  # Tags
6171
6679
  variable "tags" {
6172
6680
  description = "Tags to apply to all resources"
@@ -6196,6 +6704,9 @@ module "rest_api" {
6196
6704
  stage_name = "prod"
6197
6705
  stage_auto_deploy = true
6198
6706
 
6707
+ # WAF Configuration
6708
+ enable_waf = var.enable_waf
6709
+
6199
6710
  # CORS Configuration
6200
6711
  cors_allow_headers = var.cors_allow_headers
6201
6712
  cors_allow_methods = var.cors_allow_methods
@@ -6205,6 +6716,15 @@ module "rest_api" {
6205
6716
  tags = var.tags
6206
6717
  }
6207
6718
 
6719
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
6720
+ count = var.enable_waf ? 1 : 0
6721
+
6722
+ resource_arn = aws_api_gateway_stage.api_stage.arn
6723
+ web_acl_arn = module.rest_api.waf_web_acl_arn
6724
+
6725
+ depends_on = [aws_api_gateway_stage.api_stage]
6726
+ }
6727
+
6208
6728
  # Lambda function
6209
6729
  resource "aws_lambda_function" "api_lambda" {
6210
6730
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -6414,6 +6934,7 @@ resource "aws_api_gateway_stage" "api_stage" {
6414
6934
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
6415
6935
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
6416
6936
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
6937
+ #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
6417
6938
  deployment_id = aws_api_gateway_deployment.api_deployment.id
6418
6939
  rest_api_id = module.rest_api.api_id
6419
6940
  stage_name = "prod"