locals { origin_id = "private-s3-origin" tags = { Purpose = "Demonstration" ManagedBy = "Terraform" } } resource "aws_s3_bucket" "site" { bucket = var.bucket_name force_destroy = false tags = local.tags } resource "aws_s3_bucket_ownership_controls" "site" { bucket = aws_s3_bucket.site.id rule { object_ownership = "BucketOwnerEnforced" } } resource "aws_s3_bucket_public_access_block" "site" { bucket = aws_s3_bucket.site.id block_public_acls = true ignore_public_acls = true block_public_policy = true restrict_public_buckets = true } resource "aws_s3_bucket_server_side_encryption_configuration" "site" { bucket = aws_s3_bucket.site.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } resource "aws_s3_bucket_versioning" "site" { bucket = aws_s3_bucket.site.id versioning_configuration { status = "Enabled" } } resource "aws_cloudfront_origin_access_control" "site" { name = "static-${substr(sha256(var.bucket_name), 0, 16)}" description = "Signed access to the private S3 origin" origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" } resource "aws_cloudfront_cache_policy" "site" { name = "${var.bucket_name}-public-cache" comment = "Short-lived public static content; no user-specific cache inputs" min_ttl = 0 default_ttl = 60 max_ttl = 300 parameters_in_cache_key_and_forwarded_to_origin { enable_accept_encoding_brotli = true enable_accept_encoding_gzip = true cookies_config { cookie_behavior = "none" } headers_config { header_behavior = "none" } query_strings_config { query_string_behavior = "none" } } } resource "aws_cloudfront_distribution" "site" { enabled = true is_ipv6_enabled = true default_root_object = "index.html" aliases = [var.site_domain] price_class = "PriceClass_100" http_version = "http2" tags = local.tags origin { domain_name = aws_s3_bucket.site.bucket_regional_domain_name origin_id = local.origin_id origin_access_control_id = aws_cloudfront_origin_access_control.site.id } default_cache_behavior { target_origin_id = local.origin_id viewer_protocol_policy = "redirect-to-https" allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] compress = true cache_policy_id = aws_cloudfront_cache_policy.site.id } restrictions { geo_restriction { restriction_type = "none" } } viewer_certificate { acm_certificate_arn = var.certificate_arn minimum_protocol_version = "TLSv1.2_2021" ssl_support_method = "sni-only" } depends_on = [ aws_s3_bucket_ownership_controls.site, aws_s3_bucket_public_access_block.site, aws_s3_bucket_server_side_encryption_configuration.site, aws_s3_bucket_versioning.site, ] } resource "aws_s3_bucket_policy" "site" { bucket = aws_s3_bucket.site.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "AllowThisCloudFrontDistributionReadOnly" Effect = "Allow" Principal = { Service = "cloudfront.amazonaws.com" } Action = ["s3:GetObject"] Resource = ["${aws_s3_bucket.site.arn}/*"] Condition = { StringEquals = { "AWS:SourceArn" = aws_cloudfront_distribution.site.arn } } }, { Sid = "DenyInsecureTransportForNonServicePrincipals" Effect = "Deny" Principal = "*" Action = ["s3:*"] Resource = [aws_s3_bucket.site.arn, "${aws_s3_bucket.site.arn}/*"] Condition = { Bool = { "aws:SecureTransport" = "false" "aws:PrincipalIsAWSService" = "false" } } }, ] }) depends_on = [aws_s3_bucket_public_access_block.site] }