@aws/nx-plugin 1.0.0-rc.77 → 1.0.0-rc.78

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.
@@ -31,7 +31,7 @@ import {
31
31
  } from 'aws-cdk-lib/aws-s3-deployment';
32
32
  import { Construct } from 'constructs';
33
33
  import { RuntimeConfig } from './runtime-config<% if (esm) { %>.js<% } %>';
34
- import { Key } from 'aws-cdk-lib/aws-kms';
34
+ import { IKey, Key } from 'aws-cdk-lib/aws-kms';
35
35
  import {
36
36
  CfnDelivery,
37
37
  CfnDeliveryDestination,
@@ -74,6 +74,33 @@ export interface StaticWebsiteProps {
74
74
  * When provided, viewers are required to use TLS 1.2 or later.
75
75
  */
76
76
  readonly certificate?: ICertificate;
77
+ /**
78
+ * Whether to protect the CloudFront distribution with an AWS WAF Web ACL.
79
+ *
80
+ * @default true
81
+ */
82
+ readonly enableWaf?: boolean;
83
+ /**
84
+ * Server-side encryption for the website and distribution log buckets.
85
+ *
86
+ * @default BucketEncryption.KMS
87
+ */
88
+ readonly encryption?: BucketEncryption;
89
+ /**
90
+ * KMS key used to encrypt the website and distribution log buckets. Only used when `encryption` is
91
+ * `BucketEncryption.KMS`. When not provided, a new key is created. Note that a key imported via
92
+ * `Key.fromKeyArn` must already grant the CloudWatch Logs, S3 and CloudFront service principals the
93
+ * necessary permissions in its own key policy - `addToResourcePolicy` is a no-op on an imported key,
94
+ * so this construct cannot grant them on your behalf.
95
+ */
96
+ readonly encryptionKey?: IKey;
97
+ /**
98
+ * Whether the automatically created KMS key has rotation enabled. Only applies when `encryption` is
99
+ * `BucketEncryption.KMS` and no `encryptionKey` is supplied.
100
+ *
101
+ * @default true
102
+ */
103
+ readonly enableKeyRotation?: boolean;
77
104
  }
78
105
 
79
106
  /**
@@ -92,17 +119,27 @@ export class StaticWebsite extends Construct {
92
119
  constructor(
93
120
  scope: Construct,
94
121
  id: string,
95
- { websiteFilePath, websiteName, domainNames, certificate }: StaticWebsiteProps
122
+ {
123
+ websiteFilePath,
124
+ websiteName,
125
+ domainNames,
126
+ certificate,
127
+ enableWaf = true,
128
+ encryption = BucketEncryption.KMS,
129
+ encryptionKey,
130
+ enableKeyRotation = true,
131
+ }: StaticWebsiteProps
96
132
  ) {
97
133
  super(scope, id);
98
134
 
99
- const websiteKey = new Key(this, 'WebsiteKey', {
100
- enableKeyRotation: true,
101
- });
135
+ const websiteKey: IKey | undefined =
136
+ encryption === BucketEncryption.KMS
137
+ ? (encryptionKey ?? new Key(this, 'WebsiteKey', { enableKeyRotation }))
138
+ : undefined;
102
139
 
103
140
  // Allow CloudWatch Logs to use the website key for server access log delivery.
104
141
  const stack = Stack.of(this);
105
- websiteKey.addToResourcePolicy(
142
+ websiteKey?.addToResourcePolicy(
106
143
  new PolicyStatement({
107
144
  effect: Effect.ALLOW,
108
145
  principals: [
@@ -136,7 +173,7 @@ export class StaticWebsite extends Construct {
136
173
  enforceSSL: true,
137
174
  autoDeleteObjects: true,
138
175
  removalPolicy: RemovalPolicy.DESTROY,
139
- encryption: BucketEncryption.KMS,
176
+ encryption,
140
177
  encryptionKey: websiteKey,
141
178
  objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,
142
179
  publicReadAccess: false,
@@ -153,7 +190,7 @@ export class StaticWebsite extends Construct {
153
190
  accessLogs,
154
191
  );
155
192
  // Web ACL
156
- const wafStack = new CloudfrontWebAcl(this, 'waf');
193
+ const wafStack = enableWaf ? new CloudfrontWebAcl(this, 'waf') : undefined;
157
194
 
158
195
  // Bucket holding CloudFront standard access logs. CloudFront delivers its
159
196
  // own logs to S3 only, so this bucket is retained; its S3 server access
@@ -162,7 +199,7 @@ export class StaticWebsite extends Construct {
162
199
  enforceSSL: true,
163
200
  autoDeleteObjects: true,
164
201
  removalPolicy: RemovalPolicy.DESTROY,
165
- encryption: BucketEncryption.KMS,
202
+ encryption,
166
203
  encryptionKey: websiteKey,
167
204
  objectOwnership: ObjectOwnership.BUCKET_OWNER_PREFERRED,
168
205
  publicReadAccess: false,
@@ -214,7 +251,7 @@ export class StaticWebsite extends Construct {
214
251
  this,
215
252
  'CloudfrontDistribution',
216
253
  {
217
- webAclId: wafStack.wafArn,
254
+ webAclId: wafStack?.wafArn,
218
255
  enableLogging: true,
219
256
  logBucket: logBucket,
220
257
  ...(certificate
@@ -8,6 +8,53 @@ terraform {
8
8
  }
9
9
  }
10
10
 
11
+ variable "custom_domain_names" {
12
+ description = "Custom domain names (aliases) for the CloudFront distribution. Requires acm_certificate_arn."
13
+ type = list(string)
14
+ default = []
15
+ }
16
+
17
+ variable "acm_certificate_arn" {
18
+ description = "ARN of an ACM certificate (in us-east-1) for the custom domain names. When set, viewers are required to use TLS 1.2 or later."
19
+ type = string
20
+ default = null
21
+ }
22
+
23
+ variable "enable_waf" {
24
+ description = "Whether to protect the CloudFront distribution with an AWS WAF Web ACL."
25
+ type = bool
26
+ default = true
27
+ }
28
+
29
+ variable "encryption" {
30
+ description = "Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED."
31
+ type = string
32
+ default = "KMS"
33
+
34
+ validation {
35
+ condition = contains(["KMS", "S3_MANAGED"], var.encryption)
36
+ error_message = "encryption must be one of KMS or S3_MANAGED."
37
+ }
38
+ }
39
+
40
+ variable "kms_key_arn" {
41
+ description = "ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy."
42
+ type = string
43
+ default = null
44
+ }
45
+
46
+ variable "create_kms_key" {
47
+ description = "Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn."
48
+ type = bool
49
+ default = true
50
+ }
51
+
52
+ variable "enable_key_rotation" {
53
+ description = "Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true."
54
+ type = bool
55
+ default = true
56
+ }
57
+
11
58
  # Static website module configured for the web package
12
59
  module "static_website" {
13
60
  source = "../../../core/static-website"
@@ -15,6 +62,14 @@ module "static_website" {
15
62
  website_name = "<%= websiteNameKebabCase %>"
16
63
  website_file_path = "${path.module}/../../../../../../../<%= websiteContentPath %>/bundle"
17
64
 
65
+ custom_domain_names = var.custom_domain_names
66
+ acm_certificate_arn = var.acm_certificate_arn
67
+ enable_waf = var.enable_waf
68
+ encryption = var.encryption
69
+ kms_key_arn = var.kms_key_arn
70
+ create_kms_key = var.create_kms_key
71
+ enable_key_rotation = var.enable_key_rotation
72
+
18
73
  providers = {
19
74
  aws.us_east_1 = aws.us_east_1
20
75
  }
@@ -35,6 +35,41 @@ variable "acm_certificate_arn" {
35
35
  default = null
36
36
  }
37
37
 
38
+ variable "enable_waf" {
39
+ description = "Whether to protect the CloudFront distribution with an AWS WAF Web ACL."
40
+ type = bool
41
+ default = true
42
+ }
43
+
44
+ variable "encryption" {
45
+ description = "Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED."
46
+ type = string
47
+ default = "KMS"
48
+
49
+ validation {
50
+ condition = contains(["KMS", "S3_MANAGED"], var.encryption)
51
+ error_message = "encryption must be one of KMS or S3_MANAGED."
52
+ }
53
+ }
54
+
55
+ variable "kms_key_arn" {
56
+ description = "ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy."
57
+ type = string
58
+ default = null
59
+ }
60
+
61
+ variable "create_kms_key" {
62
+ description = "Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn."
63
+ type = bool
64
+ default = true
65
+ }
66
+
67
+ variable "enable_key_rotation" {
68
+ description = "Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true."
69
+ type = bool
70
+ default = true
71
+ }
72
+
38
73
  # Content-Security-Policy enforced on all responses. Restricts scripts and
39
74
  # framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
40
75
  # (connect-src) to AWS service endpoints such as API Gateway, Cognito and
@@ -46,6 +81,13 @@ locals {
46
81
  # Delivery source/destination names have a 60 character maximum. Truncate the
47
82
  # website name so the longest delivery name stays within the limit.
48
83
  access_logs_name_prefix = substr(lower(var.website_name), 0, 16)
84
+
85
+ create_website_key = var.encryption == "KMS" && var.create_kms_key
86
+ website_kms_key_arn = (
87
+ var.encryption != "KMS" ? null :
88
+ local.create_website_key ? aws_kms_key.website_key[0].arn :
89
+ var.kms_key_arn
90
+ )
49
91
  }
50
92
 
51
93
 
@@ -56,9 +98,11 @@ data "aws_region" "current" {}
56
98
 
57
99
  # KMS Key for encryption
58
100
  resource "aws_kms_key" "website_key" {
101
+ count = local.create_website_key ? 1 : 0
102
+
59
103
  description = "KMS key for ${var.website_name} website encryption"
60
104
  deletion_window_in_days = 7
61
- enable_key_rotation = true
105
+ enable_key_rotation = var.enable_key_rotation
62
106
 
63
107
  policy = jsonencode({
64
108
  Version = "2012-10-17"
@@ -131,8 +175,10 @@ resource "aws_kms_key" "website_key" {
131
175
  }
132
176
 
133
177
  resource "aws_kms_alias" "website_key_alias" {
178
+ count = local.create_website_key ? 1 : 0
179
+
134
180
  name = "alias/${lower(var.website_name)}-website-key-${random_id.unique_suffix.hex}"
135
- target_key_id = aws_kms_key.website_key.key_id
181
+ target_key_id = aws_kms_key.website_key[0].key_id
136
182
  }
137
183
 
138
184
  # CloudWatch log group receiving S3 server access logs for the website and
@@ -140,7 +186,7 @@ resource "aws_kms_alias" "website_key_alias" {
140
186
  resource "aws_cloudwatch_log_group" "access_logs" {
141
187
  name = "/aws/s3/${lower(var.website_name)}-access-logs-${random_id.bucket_suffix.hex}"
142
188
  retention_in_days = 365
143
- kms_key_id = aws_kms_key.website_key.arn
189
+ kms_key_id = local.website_kms_key_arn
144
190
  }
145
191
 
146
192
  resource "random_id" "unique_suffix" {
@@ -177,8 +223,8 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "website_encryptio
177
223
 
178
224
  rule {
179
225
  apply_server_side_encryption_by_default {
180
- kms_master_key_id = aws_kms_key.website_key.arn
181
- sse_algorithm = "aws:kms"
226
+ kms_master_key_id = var.encryption == "KMS" ? local.website_kms_key_arn : null
227
+ sse_algorithm = var.encryption == "KMS" ? "aws:kms" : "AES256"
182
228
  }
183
229
  }
184
230
  }
@@ -242,8 +288,8 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "distribution_logs
242
288
 
243
289
  rule {
244
290
  apply_server_side_encryption_by_default {
245
- kms_master_key_id = aws_kms_key.website_key.arn
246
- sse_algorithm = "aws:kms"
291
+ kms_master_key_id = var.encryption == "KMS" ? local.website_kms_key_arn : null
292
+ sse_algorithm = var.encryption == "KMS" ? "aws:kms" : "AES256"
247
293
  }
248
294
  }
249
295
  }
@@ -313,6 +359,8 @@ resource "aws_s3_bucket_policy" "distribution_logs_ssl_policy" {
313
359
 
314
360
  # WAF Web ACL (must be in us-east-1 for CloudFront)
315
361
  resource "aws_wafv2_web_acl" "cloudfront_waf" {
362
+ count = var.enable_waf ? 1 : 0
363
+
316
364
  #checkov:skip=CKV2_AWS_31:WAF logging disabled
317
365
  provider = aws.us_east_1
318
366
  name = "${lower(var.website_name)}-cloudfront-waf-${random_id.unique_suffix.hex}"
@@ -437,7 +485,7 @@ resource "aws_cloudfront_distribution" "website" {
437
485
  enabled = true
438
486
  is_ipv6_enabled = true
439
487
  default_root_object = "index.html"
440
- web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn
488
+ web_acl_id = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null
441
489
  aliases = var.custom_domain_names
442
490
 
443
491
  logging_config {
@@ -495,12 +543,6 @@ resource "aws_cloudfront_distribution" "website" {
495
543
  Name = "${lower(var.website_name)}-distribution-${random_id.unique_suffix.hex}"
496
544
  }
497
545
 
498
- lifecycle {
499
- replace_triggered_by = [
500
- aws_wafv2_web_acl.cloudfront_waf
501
- ]
502
- }
503
-
504
546
  # CloudFront validates access to the distribution logs bucket when it
505
547
  # creates the distribution.
506
548
  depends_on = [
@@ -773,5 +815,5 @@ output "cloudfront_domain_name" {
773
815
 
774
816
  output "waf_web_acl_arn" {
775
817
  description = "ARN of the WAF Web ACL"
776
- value = aws_wafv2_web_acl.cloudfront_waf.arn
818
+ value = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null
777
819
  }