diff --git a/static-site/acm.tf b/static-site/acm.tf index f542091..fbfc968 100644 --- a/static-site/acm.tf +++ b/static-site/acm.tf @@ -1,13 +1,20 @@ -resource "aws_acm_certificate" "cloudfront_cert" { +resource "aws_acm_certificate" "this" { domain_name = local.primary_domain - subject_alternative_names = local.domains + subject_alternative_names = var.domains validation_method = "DNS" tags = var.tags provider = aws.acm + + lifecycle { + create_before_destroy = true + } } -resource "aws_acm_certificate_validation" "cloudfront_cert_validation" { - certificate_arn = aws_acm_certificate.cloudfront_cert.arn - validation_record_fqdns = [for record in aws_route53_record.acm_records : record.fqdn] - provider = aws.acm -} \ No newline at end of file +resource "aws_acm_certificate_validation" "this" { + certificate_arn = aws_acm_certificate.this.arn + validation_record_fqdns = [ + for record in aws_route53_record.acm_records : record.fqdn + if contains(local.internal_domains, record.fqdn) + ] + provider = aws.acm +} diff --git a/static-site/cloudfront.tf b/static-site/cloudfront.tf index c501165..8030fde 100644 --- a/static-site/cloudfront.tf +++ b/static-site/cloudfront.tf @@ -1,16 +1,24 @@ locals { - origin_id = "S3-${aws_s3_bucket.static_site.bucket}" + origin_id = "s3-static-site" + origin_path = var.origin_path == "" ? "" : var.origin_path + bucket_regional_domain_name = var.create_bucket ? aws_s3_bucket.this[0].bucket_regional_domain_name : data.aws_s3_bucket.user_created[0].bucket_regional_domain_name + oac_name = replace( + replace("${local.bucket_name}-${replace(local.origin_path, "/", "-")}-oac", "--", "-"), + "/[^a-zA-Z0-9.-]/", + "" + ) + rhp_name = "${local.primary_domain_normalised}-cf-rhp" } -resource "aws_cloudfront_distribution" "static_site" { +resource "aws_cloudfront_distribution" "this" { comment = "Distribution for ${local.primary_domain}" - aliases = local.domains + aliases = var.domains enabled = true is_ipv6_enabled = true default_root_object = "index.html" viewer_certificate { - acm_certificate_arn = aws_acm_certificate.cloudfront_cert.arn + acm_certificate_arn = aws_acm_certificate.this.arn ssl_support_method = "sni-only" minimum_protocol_version = var.viewer_certificate.minimum_protocol_version cloudfront_default_certificate = false @@ -25,14 +33,14 @@ resource "aws_cloudfront_distribution" "static_site" { origin { origin_id = local.origin_id - origin_access_control_id = aws_cloudfront_origin_access_control.oac.id - domain_name = aws_s3_bucket.static_site.bucket_regional_domain_name - origin_path = var.origin_path != "" ? "/${var.origin_path}" : "" + origin_access_control_id = aws_cloudfront_origin_access_control.this.id + domain_name = local.bucket_regional_domain_name + origin_path = local.origin_path } default_cache_behavior { target_origin_id = local.origin_id - response_headers_policy_id = aws_cloudfront_response_headers_policy.cloudfront.id + response_headers_policy_id = aws_cloudfront_response_headers_policy.this.id allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] @@ -63,21 +71,21 @@ resource "aws_cloudfront_distribution" "static_site" { } tags = var.tags - depends_on = [aws_acm_certificate_validation.cloudfront_cert_validation] + depends_on = [aws_acm_certificate_validation.this] provider = aws.default } -resource "aws_cloudfront_origin_access_control" "oac" { - name = "oac-for-${aws_s3_bucket.static_site.bucket}" - description = "OAC for ${aws_s3_bucket.static_site.bucket}" +resource "aws_cloudfront_origin_access_control" "this" { + name = local.oac_name + description = "OAC for ${local.bucket_name}" origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" provider = aws.default } -resource "aws_cloudfront_response_headers_policy" "cloudfront" { - name = "cf-resp-hdrs-${local.primary_domain_normalised}" +resource "aws_cloudfront_response_headers_policy" "this" { + name = local.rhp_name comment = "Response headers policy for ${local.primary_domain}" cors_config { diff --git a/static-site/data.tf b/static-site/data.tf index c0a8c67..36512fb 100644 --- a/static-site/data.tf +++ b/static-site/data.tf @@ -1,16 +1,25 @@ +locals { + custom_prefix = startswith(local.origin_path, "/") ? substr(local.origin_path, 1, length(local.origin_path)) : local.origin_path + bucket_prefix = local.origin_path == "" ? "*" : "${local.custom_prefix}/*" +} data "aws_caller_identity" "current" {} -data "aws_route53_zone" "hosted_zone" { +data "aws_route53_zone" "this" { count = local.create_hosted_zone ? 0 : 1 name = var.hosted_zone private_zone = false } +data "aws_s3_bucket" "user_created" { + count = !var.create_bucket ? 1 : 0 + bucket = var.bucket_name +} + data "aws_iam_policy_document" "cloudfront_to_s3" { statement { sid = "AllowCloudFrontToAccessBucket" actions = ["s3:GetObject"] - resources = ["${aws_s3_bucket.static_site.arn}/*"] + resources = ["${local.bucket_arn}/${local.bucket_prefix}"] principals { type = "Service" @@ -20,7 +29,7 @@ data "aws_iam_policy_document" "cloudfront_to_s3" { condition { test = "StringEquals" variable = "AWS:SourceArn" - values = [aws_cloudfront_distribution.static_site.arn] + values = [aws_cloudfront_distribution.this.arn] } condition { @@ -29,4 +38,4 @@ data "aws_iam_policy_document" "cloudfront_to_s3" { values = [data.aws_caller_identity.current.account_id] } } -} \ No newline at end of file +} diff --git a/static-site/locals.tf b/static-site/locals.tf index b661838..2bf73fa 100644 --- a/static-site/locals.tf +++ b/static-site/locals.tf @@ -1,10 +1,20 @@ locals { - domains = distinct(var.domains) - primary_domain = local.domains[0] - - primary_domain_normalised = replace(local.primary_domain, ".", "-") - create_hosted_zone = var.hosted_zone == "" + hosted_zone_domain = local.create_hosted_zone ? local.primary_domain : data.aws_route53_zone.this[0].name + primary_domain = var.domains[0] + primary_domain_normalised = replace( + replace(local.primary_domain, "/\\*\\./", ""), # remove wildcard prefix + "/[^a-zA-Z0-9-]/", "-" # replace anything else with - + ) + internal_domains = [ + for d in var.domains : d + if endswith(d, local.hosted_zone_domain) + ] bucket_name = var.bucket_name == "" ? local.primary_domain : var.bucket_name + bucket_arn = var.create_bucket ? aws_s3_bucket.this[0].arn : data.aws_s3_bucket.user_created[0].arn +} + +output "internal_domains" { + value = local.internal_domains } diff --git a/static-site/outputs.tf b/static-site/outputs.tf index 763f348..27464c3 100644 --- a/static-site/outputs.tf +++ b/static-site/outputs.tf @@ -1,15 +1,27 @@ output "bucket" { value = { - arn = aws_s3_bucket.static_site.arn - id = aws_s3_bucket.static_site.id + arn = var.create_bucket ? aws_s3_bucket.this[0].arn : data.aws_s3_bucket.user_created[0].arn + id = local.bucket_name } } output "cloudfront" { value = { - arn = aws_cloudfront_distribution.static_site.arn - id = aws_cloudfront_distribution.static_site.id - domain_name = aws_cloudfront_distribution.static_site.domain_name - aliases = aws_cloudfront_distribution.static_site.aliases + arn = aws_cloudfront_distribution.this.arn + id = aws_cloudfront_distribution.this.id + domain_name = aws_cloudfront_distribution.this.domain_name + aliases = aws_cloudfront_distribution.this.aliases } } + +output "external_validation_records" { + value = { + for dvo in aws_acm_certificate.this.domain_validation_options : + dvo.domain_name => { + name = dvo.resource_record_name + value = dvo.resource_record_value + type = dvo.resource_record_type + } + if !contains(local.internal_domains, dvo.domain_name) + } +} \ No newline at end of file diff --git a/static-site/route53.tf b/static-site/route53.tf index 29e8585..1fcdcec 100644 --- a/static-site/route53.tf +++ b/static-site/route53.tf @@ -1,4 +1,4 @@ -resource "aws_route53_zone" "hosted_zone" { +resource "aws_route53_zone" "this" { count = local.create_hosted_zone ? 1 : 0 name = local.primary_domain tags = var.tags @@ -9,37 +9,42 @@ resource "aws_route53_zone" "hosted_zone" { ############################################# resource "aws_route53_record" "acm_records" { for_each = { - for dvo in aws_acm_certificate.cloudfront_cert.domain_validation_options : dvo.domain_name => { + for dvo in aws_acm_certificate.this.domain_validation_options : + dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } - zone_id = local.create_hosted_zone ? aws_route53_zone.hosted_zone[0].zone_id : data.aws_route53_zone.hosted_zone[0].zone_id - type = each.value.type - name = each.value.name - records = [each.value.record] - ttl = 60 + zone_id = try(aws_route53_zone.this[0].zone_id, data.aws_route53_zone.this[0].zone_id) + type = each.value.type + name = each.value.name + records = [each.value.record] + ttl = 60 + allow_overwrite = true - provider = aws.default + + provider = aws.default } ############################################# # Setup the A record for your custom domain ############################################# resource "aws_route53_record" "static_site_a_record" { - count = length(local.domains) + count = length(var.domains) - zone_id = local.create_hosted_zone ? aws_route53_zone.hosted_zone[0].zone_id : data.aws_route53_zone.hosted_zone[0].zone_id + zone_id = try(aws_route53_zone.this[0].zone_id, data.aws_route53_zone.this[0].zone_id) type = "A" - name = local.domains[count.index] + name = var.domains[count.index] alias { - name = aws_cloudfront_distribution.static_site.domain_name - zone_id = aws_cloudfront_distribution.static_site.hosted_zone_id + name = aws_cloudfront_distribution.this.domain_name + zone_id = aws_cloudfront_distribution.this.hosted_zone_id evaluate_target_health = false } + allow_overwrite = true + provider = aws.default } diff --git a/static-site/s3.tf b/static-site/s3.tf index 3a3d346..3b70367 100644 --- a/static-site/s3.tf +++ b/static-site/s3.tf @@ -1,4 +1,4 @@ -resource "aws_s3_bucket" "static_site" { +resource "aws_s3_bucket" "this" { count = var.create_bucket ? 1 : 0 bucket = local.bucket_name @@ -7,8 +7,8 @@ resource "aws_s3_bucket" "static_site" { provider = aws.default } -resource "aws_s3_bucket_public_access_block" "static_site" { - bucket = var.bucket_name != "" ? var.bucket_name : aws_s3_bucket.static_site[0].id +resource "aws_s3_bucket_public_access_block" "this" { + bucket = var.bucket_name != "" ? var.bucket_name : aws_s3_bucket.this[0].id block_public_acls = true block_public_policy = true ignore_public_acls = true @@ -16,8 +16,8 @@ resource "aws_s3_bucket_public_access_block" "static_site" { provider = aws.default } -resource "aws_s3_bucket_policy" "static_site" { - bucket = var.bucket_name != "" ? var.bucket_name : aws_s3_bucket.static_site[0].bucket +resource "aws_s3_bucket_policy" "this" { + bucket = var.bucket_name != "" ? var.bucket_name : aws_s3_bucket.this[0].bucket policy = data.aws_iam_policy_document.cloudfront_to_s3.json provider = aws.default -} \ No newline at end of file +}