@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.
- package/migrations.json +6 -1
- package/package.json +1 -1
- package/src/migrations/latest/static-website-configurable-waf-encryption/metadata.json +3 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js +418 -0
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js.map +1 -0
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +259 -48
- package/src/utils/website-constructs/files/cdk/app/static-websites/__websiteNameKebabCase__.ts.template +11 -2
- package/src/utils/website-constructs/files/cdk/core/static-website.ts.template +47 -10
- package/src/utils/website-constructs/files/terraform/app/static-websites/__websiteNameKebabCase__/__websiteNameKebabCase__.tf.template +55 -0
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +57 -15
|
@@ -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
|
-
{
|
|
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
|
|
100
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
}
|