@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.
@@ -76,6 +76,12 @@ export interface <%= apiNameClassName %>Props<
76
76
  userPool: IUserPool;
77
77
  };
78
78
  <%_ } _%>
79
+ /**
80
+ * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage.
81
+ *
82
+ * @default true
83
+ */
84
+ enableWaf?: boolean;
79
85
  }
80
86
 
81
87
  /**
@@ -7,7 +7,13 @@ import {
7
7
  IResource,
8
8
  Stage,
9
9
  } from 'aws-cdk-lib/aws-apigateway';
10
- import { IAspect } from 'aws-cdk-lib';
10
+ import { IAspect, RemovalPolicy } from 'aws-cdk-lib';
11
+ import {
12
+ CfnLoggingConfiguration,
13
+ CfnWebACL,
14
+ CfnWebACLAssociation,
15
+ } from 'aws-cdk-lib/aws-wafv2';
16
+ import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
11
17
  import { RuntimeConfig } from '../runtime-config.js';
12
18
  import {
13
19
  ApiIntegrations,
@@ -38,6 +44,14 @@ export interface RestApiProps<
38
44
  * Map of integration keys to their API Gateway integrations
39
45
  */
40
46
  readonly integrations: TIntegrations;
47
+ /**
48
+ * Whether to enable AWS WAFv2 with the default managed ruleset
49
+ * (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet)
50
+ * and associate it with the API's default stage.
51
+ *
52
+ * @default true
53
+ */
54
+ readonly enableWaf?: boolean;
41
55
  }
42
56
 
43
57
  /**
@@ -61,6 +75,9 @@ export class RestApi<
61
75
  /** Map of integration keys to their API Gateway integrations */
62
76
  public readonly integrations: TIntegrations;
63
77
 
78
+ /** The WAFv2 Web ACL associated with the API's default stage, if WAF is enabled */
79
+ public readonly webAcl?: CfnWebACL;
80
+
64
81
  constructor(
65
82
  scope: Construct,
66
83
  id: string,
@@ -68,6 +85,7 @@ export class RestApi<
68
85
  apiName,
69
86
  operations,
70
87
  integrations,
88
+ enableWaf = true,
71
89
  ...props
72
90
  }: RestApiProps<TIntegrations, TOperation>,
73
91
  ) {
@@ -77,6 +95,86 @@ export class RestApi<
77
95
  // Create the API Gateway REST API
78
96
  this.api = new _RestApi(this, 'Api', props);
79
97
 
98
+ if (enableWaf) {
99
+ this.webAcl = new CfnWebACL(this, 'WebAcl', {
100
+ defaultAction: { allow: {} },
101
+ scope: 'REGIONAL',
102
+ visibilityConfig: {
103
+ cloudWatchMetricsEnabled: true,
104
+ metricName: `${apiName}WebAcl`,
105
+ sampledRequestsEnabled: true,
106
+ },
107
+ rules: [
108
+ {
109
+ name: 'CRSRule',
110
+ priority: 0,
111
+ statement: {
112
+ managedRuleGroupStatement: {
113
+ name: 'AWSManagedRulesCommonRuleSet',
114
+ vendorName: 'AWS',
115
+ // Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
116
+ ruleActionOverrides: [
117
+ {
118
+ name: 'SizeRestrictions_BODY',
119
+ actionToUse: { count: {} },
120
+ },
121
+ ],
122
+ },
123
+ },
124
+ visibilityConfig: {
125
+ cloudWatchMetricsEnabled: true,
126
+ metricName: `${apiName}WebAcl-CRS`,
127
+ sampledRequestsEnabled: true,
128
+ },
129
+ overrideAction: {
130
+ none: {},
131
+ },
132
+ },
133
+ {
134
+ name: 'KnownBadInputsRule',
135
+ priority: 1,
136
+ statement: {
137
+ managedRuleGroupStatement: {
138
+ name: 'AWSManagedRulesKnownBadInputsRuleSet',
139
+ vendorName: 'AWS',
140
+ },
141
+ },
142
+ visibilityConfig: {
143
+ cloudWatchMetricsEnabled: true,
144
+ metricName: `${apiName}WebAcl-KnownBadInputs`,
145
+ sampledRequestsEnabled: true,
146
+ },
147
+ overrideAction: {
148
+ none: {},
149
+ },
150
+ },
151
+ ],
152
+ });
153
+
154
+ new CfnWebACLAssociation(this, 'WebAclAssociation', {
155
+ resourceArn: this.api.deploymentStage.stageArn,
156
+ webAclArn: this.webAcl.attrArn,
157
+ });
158
+
159
+ // Send WAF request logs to CloudWatch. The log group name must start with
160
+ // `aws-waf-logs-` to satisfy the WAFv2 logging destination requirement.
161
+ const wafLogGroup = new LogGroup(this, 'WebAclLogs', {
162
+ logGroupName: `aws-waf-logs-${apiName}-${this.node.addr.slice(-8)}`,
163
+ retention: RetentionDays.ONE_MONTH,
164
+ removalPolicy: RemovalPolicy.DESTROY,
165
+ });
166
+ suppressRules(
167
+ wafLogGroup,
168
+ ['CKV_AWS_158'],
169
+ 'Using default CloudWatch log encryption for WAF logs',
170
+ );
171
+
172
+ new CfnLoggingConfiguration(this, 'WebAclLoggingConfig', {
173
+ resourceArn: this.webAcl.attrArn,
174
+ logDestinationConfigs: [wafLogGroup.logGroupArn],
175
+ });
176
+ }
177
+
80
178
  suppressRules(
81
179
  this.api,
82
180
  ['CKV_AWS_120'],
@@ -58,6 +58,13 @@ variable "cors_allow_origins" {
58
58
  default = ["*"]
59
59
  }
60
60
 
61
+ # WAF Configuration
62
+ variable "enable_waf" {
63
+ description = "Whether to enable AWS WAFv2 with the default managed ruleset on the API stage"
64
+ type = bool
65
+ default = true
66
+ }
67
+
61
68
  # Tags
62
69
  variable "tags" {
63
70
  description = "Tags to apply to all resources"
@@ -87,6 +94,9 @@ module "rest_api" {
87
94
  stage_name = "prod"
88
95
  stage_auto_deploy = true
89
96
 
97
+ # WAF Configuration
98
+ enable_waf = var.enable_waf
99
+
90
100
  # CORS Configuration
91
101
  cors_allow_headers = var.cors_allow_headers
92
102
  cors_allow_methods = var.cors_allow_methods
@@ -96,6 +106,15 @@ module "rest_api" {
96
106
  tags = var.tags
97
107
  }
98
108
 
109
+ resource "aws_wafv2_web_acl_association" "api_waf_association" {
110
+ count = var.enable_waf ? 1 : 0
111
+
112
+ resource_arn = aws_api_gateway_stage.api_stage.arn
113
+ web_acl_arn = module.rest_api.waf_web_acl_arn
114
+
115
+ depends_on = [aws_api_gateway_stage.api_stage]
116
+ }
117
+
99
118
  # Lambda function
100
119
  resource "aws_lambda_function" "api_lambda" {
101
120
  #checkov:skip=CKV_AWS_117:Lambda function does not need to be in VPC for this use case
@@ -364,6 +383,7 @@ resource "aws_api_gateway_stage" "api_stage" {
364
383
  #checkov:skip=CKV_AWS_76:API Gateway access logging disabled due to account-level CloudWatch Logs role ARN requirement
365
384
  #checkov:skip=CKV2_AWS_4:API Gateway logging level not applicable as access logging is disabled
366
385
  #checkov:skip=CKV2_AWS_51:Client certificate authentication not required for this use case
386
+ #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
367
387
  deployment_id = aws_api_gateway_deployment.api_deployment.id
368
388
  rest_api_id = module.rest_api.api_id
369
389
  stage_name = "prod"
@@ -9,6 +9,10 @@ terraform {
9
9
  source = "hashicorp/aws"
10
10
  version = "~> 6.33"
11
11
  }
12
+ random = {
13
+ source = "hashicorp/random"
14
+ version = "~> 3.1"
15
+ }
12
16
  }
13
17
  }
14
18
 
@@ -37,6 +41,12 @@ variable "stage_auto_deploy" {
37
41
  default = true
38
42
  }
39
43
 
44
+ variable "enable_waf" {
45
+ 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."
46
+ type = bool
47
+ default = true
48
+ }
49
+
40
50
  # CORS Configuration
41
51
 
42
52
  variable "cors_allow_headers" {
@@ -118,6 +128,106 @@ resource "aws_api_gateway_gateway_response" "cors_5xx" {
118
128
  }
119
129
  }
120
130
 
131
+ resource "aws_wafv2_web_acl" "api_waf" {
132
+ #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
133
+ count = var.enable_waf ? 1 : 0
134
+
135
+ name = "${var.api_name}-waf"
136
+ scope = "REGIONAL"
137
+
138
+ default_action {
139
+ allow {}
140
+ }
141
+
142
+ rule {
143
+ name = "CRSRule"
144
+ priority = 0
145
+
146
+ override_action {
147
+ none {}
148
+ }
149
+
150
+ statement {
151
+ managed_rule_group_statement {
152
+ name = "AWSManagedRulesCommonRuleSet"
153
+ vendor_name = "AWS"
154
+
155
+ # Count instead of Block: the CRS 8 KB body limit is too restrictive for most APIs.
156
+ rule_action_override {
157
+ name = "SizeRestrictions_BODY"
158
+ action_to_use {
159
+ count {}
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ visibility_config {
166
+ cloudwatch_metrics_enabled = true
167
+ metric_name = "${var.api_name}WebAcl-CRS"
168
+ sampled_requests_enabled = true
169
+ }
170
+ }
171
+
172
+ rule {
173
+ name = "KnownBadInputsRule"
174
+ priority = 1
175
+
176
+ override_action {
177
+ none {}
178
+ }
179
+
180
+ statement {
181
+ managed_rule_group_statement {
182
+ name = "AWSManagedRulesKnownBadInputsRuleSet"
183
+ vendor_name = "AWS"
184
+ }
185
+ }
186
+
187
+ visibility_config {
188
+ cloudwatch_metrics_enabled = true
189
+ metric_name = "${var.api_name}WebAcl-KnownBadInputs"
190
+ sampled_requests_enabled = true
191
+ }
192
+ }
193
+
194
+ visibility_config {
195
+ cloudwatch_metrics_enabled = true
196
+ metric_name = "${var.api_name}WebAcl"
197
+ sampled_requests_enabled = true
198
+ }
199
+
200
+ tags = var.tags
201
+
202
+ lifecycle {
203
+ create_before_destroy = true
204
+ }
205
+ }
206
+
207
+ # CloudWatch Log Group for WAF request logs. Name must start with `aws-waf-logs-`.
208
+ resource "aws_cloudwatch_log_group" "api_waf_logs" {
209
+ #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
210
+ #checkov:skip=CKV_AWS_338:Log retention set to one month which is sufficient for WAF logs
211
+ count = var.enable_waf ? 1 : 0
212
+
213
+ name = "aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}"
214
+ retention_in_days = 30
215
+ tags = var.tags
216
+ }
217
+
218
+ resource "random_id" "waf_log_suffix" {
219
+ count = var.enable_waf ? 1 : 0
220
+ byte_length = 4
221
+ }
222
+
223
+ resource "aws_wafv2_web_acl_logging_configuration" "api_waf_logging" {
224
+ count = var.enable_waf ? 1 : 0
225
+
226
+ log_destination_configs = [aws_cloudwatch_log_group.api_waf_logs[0].arn]
227
+ resource_arn = aws_wafv2_web_acl.api_waf[0].arn
228
+ }
229
+
230
+
121
231
  # Outputs
122
232
 
123
233
  output "api_id" {
@@ -145,6 +255,11 @@ output "api_root_resource_id" {
145
255
  value = aws_api_gateway_rest_api.rest_api.root_resource_id
146
256
  }
147
257
 
258
+ output "waf_web_acl_arn" {
259
+ description = "ARN of the WAFv2 Web ACL associated with the API, or null if WAF is disabled"
260
+ value = var.enable_waf ? aws_wafv2_web_acl.api_waf[0].arn : null
261
+ }
262
+
148
263
  # Note: Stage and deployment outputs are provided by the consuming module
149
264
 
150
265
  # Note: CloudWatch log group outputs removed due to account-level CloudWatch Logs role ARN requirement