1. Create an S3 Bucket for Static Hosting
Amazon S3 will be used to store the React build files.
| resource "aws_s3_bucket" "frontend" {
bucket = 'frontend_bucket_name'
}
|
We need to block public access to ensure security.
| resource "aws_s3_bucket_public_access_block" "frontend" {
bucket = aws_s3_bucket.frontend.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
|
Versioning allows you to recover previous versions of your files in case of accidental deletions or modifications.
| resource "aws_s3_bucket_versioning" "frontend" {
bucket = aws_s3_bucket.frontend.id
versioning_configuration {
status = "Enabled"
}
}
|
Enable static website hosting
| resource "aws_s3_bucket_website_configuration" "frontend_static_site" {
bucket = aws_s3_bucket.frontend.id
index_document {
suffix = "index.html"
}
}
|
2. Create an SSL Certificate with ACM
AWS ACM provides an SSL certificate to secure the application over HTTPS.
1
2
3
4
5
6
7
8
9
10
11
12 | resource "aws_acm_certificate" "frontend" {
domain_name = local.frontend_domain_name
subject_alternative_names = ["*.${local.frontend_domain_name}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
# The certificate must be created in the us-east-1 region to be used with CloudFront
provider = aws.virginia
}
|
Fetch the Hosted Zone
| data "aws_route53_zone" "base_zone_name" {
name = var.base_zone_name
}
|
Create DNS Records for ACM Validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | resource "aws_route53_record" "acm_validation" {
for_each = {
for dvo in aws_acm_certificate.frontend.domain_validation_options :
dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 300
type = each.value.type
zone_id = data.aws_route53_zone.base_zone_name.zone_id
}
|
Validate ACM Certificate
1
2
3
4
5
6
7
8
9
10
11
12 | resource "aws_acm_certificate_validation" "frontend" {
certificate_arn = aws_acm_certificate.frontend.arn
validation_record_fqdns = [for record in aws_route53_record.acm_validation : record.fqdn]
depends_on = [
aws_acm_certificate.frontend,
aws_route53_record.acm_validation,
]
# The certificate must be created in the us-east-1 region to be used with CloudFront
provider = aws.virginia
}
|
4. Deploy CloudFront as a CDN
Create an Origin Access Identity (OAI)
| resource "aws_cloudfront_origin_access_identity" "frontend" {
comment = "Origin access identity for frontend"
}
|
Fetch AWS-Managed Cache and Origin Request Policies
| # AWS Managed Caching Policy - Found in AWS Management Console at CloudFront > Policies > Cache
data "aws_cloudfront_cache_policy" "caching_optimised" {
name = "Managed-CachingOptimized"
}
# AWS Managed Caching Policy - Found in AWS Management Console at CloudFront > Policies > Origin request
data "aws_cloudfront_origin_request_policy" "cors_s3_origin" {
name = "Managed-CORS-S3Origin"
}
|
Set Security Headers in CloudFront
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | resource "aws_cloudfront_response_headers_policy" "frontend_distribution" {
name = "frontend-response-headers-policy"
security_headers_config {
strict_transport_security {
access_control_max_age_sec = "31536000"
include_subdomains = true
override = true
preload = true
}
content_type_options {
override = false
}
frame_options {
frame_option = "SAMEORIGIN"
override = false
}
}
}
|
Enable WAF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 | resource "aws_wafv2_web_acl" "waf_cloudfront" {
name = "frontend-enable-aws-managed-rules"
description = "Enable AWS managed ruleset and attached to Cloudfront distribution"
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "AWS-AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesCommonRuleSet"
sampled_requests_enabled = false
}
}
rule {
name = "AWS-AWSManagedRulesAmazonIpReputationList"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesAmazonIpReputationList"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesAmazonIpReputationList"
sampled_requests_enabled = false
}
}
rule {
name = "AWS-AWSManagedRulesAnonymousIpList"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesAnonymousIpList"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesAnonymousIpList"
sampled_requests_enabled = false
}
}
rule {
name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
priority = 4
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet"
sampled_requests_enabled = false
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "AWS-WAF"
sampled_requests_enabled = false
}
provider = aws.virginia
}
|
Create CloudFront distribution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | resource "aws_cloudfront_distribution" "frontend_distribution" {
aliases = [
local.frontend_domain_name,
"www.${local.frontend_domain_name}",
]
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "s3-frontend"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.frontend.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
comment = "CloudFront distribution for frontend"
default_root_object = "index.html"
default_cache_behavior {
cache_policy_id = data.aws_cloudfront_cache_policy.caching_optimised.id
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.cors_s3_origin.id
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3-frontend"
viewer_protocol_policy = "redirect-to-https"
response_headers_policy_id = aws_cloudfront_response_headers_policy.frontend_distribution.id
}
price_class = "PriceClass_100"
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
web_acl_id = aws_wafv2_web_acl.waf_cloudfront.arn
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.frontend.arn
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1.2_2021"
ssl_support_method = "sni-only"
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | resource "aws_route53_record" "frontend_record_ipv4" {
zone_id = data.aws_route53_zone.base_zone_name.zone_id
name = local.frontend_domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.frontend_distribution.domain_name
zone_id = aws_cloudfront_distribution.frontend_distribution.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "frontend_record_ipv6" {
zone_id = data.aws_route53_zone.base_zone_name.zone_id
name = local.frontend_domain_name
type = "AAAA"
alias {
name = aws_cloudfront_distribution.frontend_distribution.domain_name
zone_id = aws_cloudfront_distribution.frontend_distribution.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "frontend_record_cname" {
zone_id = data.aws_route53_zone.base_zone_name.zone_id
name = "www"
type = "CNAME"
ttl = "300"
records = [local.frontend_domain_name]
}
|
6. Update S3 Bucket Policy to Allow CloudFront Access
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | data "aws_iam_policy_document" "frontend_policy_document" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.frontend.iam_arn]
}
actions = [
"s3:GetObject",
]
resources = [
"${aws_s3_bucket.frontend.arn}/*"
]
}
}
resource "aws_s3_bucket_policy" "frontend_policy" {
bucket = aws_s3_bucket.frontend.id
policy = data.aws_iam_policy_document.frontend_policy_document.json
}
|