mock_provider "aws" { override_during = plan mock_resource "aws_s3_bucket" { defaults = { id = "example-static-lab-origin" arn = "arn:aws:s3:::example-static-lab-origin" bucket_regional_domain_name = "example-static-lab-origin.s3.eu-west-1.amazonaws.com" } } mock_resource "aws_cloudfront_distribution" { defaults = { id = "E000000EXAMPLE" arn = "arn:aws:cloudfront::111122223333:distribution/E000000EXAMPLE" domain_name = "d000000example.cloudfront.net" } } mock_resource "aws_cloudfront_origin_access_control" { defaults = { id = "E000000OACEXAMPLE" } } mock_resource "aws_cloudfront_cache_policy" { defaults = { id = "00000000-0000-0000-0000-000000000001" } } } variables { bucket_name = "example-static-lab-origin" site_domain = "static.example.invalid" certificate_arn = "arn:aws:acm:us-east-1:111122223333:certificate/00000000-0000-0000-0000-000000000001" } run "private_encrypted_versioned_origin" { command = plan assert { condition = ( aws_s3_bucket_public_access_block.site.block_public_acls && aws_s3_bucket_public_access_block.site.ignore_public_acls && aws_s3_bucket_public_access_block.site.block_public_policy && aws_s3_bucket_public_access_block.site.restrict_public_buckets ) error_message = "All four S3 public-access blocks must be enabled." } assert { condition = one(aws_s3_bucket_ownership_controls.site.rule).object_ownership == "BucketOwnerEnforced" error_message = "ACL-based ownership and public ACLs must be disabled." } assert { condition = one(one(aws_s3_bucket_server_side_encryption_configuration.site.rule).apply_server_side_encryption_by_default).sse_algorithm == "AES256" error_message = "The bucket must declare SSE-S3 encryption." } assert { condition = one(aws_s3_bucket_versioning.site.versioning_configuration).status == "Enabled" && !aws_s3_bucket.site.force_destroy error_message = "Versioning must be enabled and object deletion on destroy must not be forced." } } run "signed_origin_and_secure_viewer" { command = plan assert { condition = ( aws_cloudfront_origin_access_control.site.origin_access_control_origin_type == "s3" && aws_cloudfront_origin_access_control.site.signing_behavior == "always" && aws_cloudfront_origin_access_control.site.signing_protocol == "sigv4" ) error_message = "The S3 REST origin must use always-on SigV4 OAC signing." } assert { condition = ( one(aws_cloudfront_distribution.site.origin).domain_name == aws_s3_bucket.site.bucket_regional_domain_name && one(aws_cloudfront_distribution.site.origin).origin_access_control_id == aws_cloudfront_origin_access_control.site.id && length(one(aws_cloudfront_distribution.site.origin).custom_origin_config) == 0 ) error_message = "CloudFront must use the regional S3 REST endpoint and its OAC, not a website/custom origin." } assert { condition = ( one(aws_cloudfront_distribution.site.viewer_certificate).acm_certificate_arn == var.certificate_arn && one(aws_cloudfront_distribution.site.viewer_certificate).minimum_protocol_version == "TLSv1.2_2021" && one(aws_cloudfront_distribution.site.viewer_certificate).ssl_support_method == "sni-only" && toset(aws_cloudfront_distribution.site.aliases) == toset([var.site_domain]) ) error_message = "The custom-domain distribution must enforce the declared ACM certificate and TLS policy." } assert { condition = ( one(aws_cloudfront_distribution.site.default_cache_behavior).viewer_protocol_policy == "redirect-to-https" && toset(one(aws_cloudfront_distribution.site.default_cache_behavior).allowed_methods) == toset(["GET", "HEAD"]) && toset(one(aws_cloudfront_distribution.site.default_cache_behavior).cached_methods) == toset(["GET", "HEAD"]) && aws_cloudfront_distribution.site.default_root_object == "index.html" && aws_cloudfront_distribution.site.price_class == "PriceClass_100" ) error_message = "Only GET/HEAD over the HTTPS redirect policy are permitted; the root object and price class must remain explicit." } } run "least_privilege_bucket_policy" { command = plan assert { condition = length([for statement in jsondecode(aws_s3_bucket_policy.site.policy).Statement : statement if statement.Effect == "Allow"]) == 1 error_message = "The bucket policy must contain exactly one permission-granting statement." } assert { condition = alltrue([ for statement in jsondecode(aws_s3_bucket_policy.site.policy).Statement : statement.Principal.Service == "cloudfront.amazonaws.com" && toset(statement.Action) == toset(["s3:GetObject"]) && toset(statement.Resource) == toset(["${aws_s3_bucket.site.arn}/*"]) && statement.Condition.StringEquals["AWS:SourceArn"] == aws_cloudfront_distribution.site.arn if statement.Effect == "Allow" ]) error_message = "Only this distribution may receive read-only object access from the bucket policy." } assert { condition = length([ for statement in jsondecode(aws_s3_bucket_policy.site.policy).Statement : statement if statement.Effect == "Deny" && try( statement.Principal == "*" && toset(statement.Action) == toset(["s3:*"]) && toset(statement.Resource) == toset([aws_s3_bucket.site.arn, "${aws_s3_bucket.site.arn}/*"]) && statement.Condition.Bool["aws:SecureTransport"] == "false" && statement.Condition.Bool["aws:PrincipalIsAWSService"] == "false", false ) ]) == 1 error_message = "Plain HTTP must be denied to non-service principals without blocking redacted AWS service network context." } } run "bounded_public_content_cache" { command = plan assert { condition = ( aws_cloudfront_cache_policy.site.min_ttl == 0 && aws_cloudfront_cache_policy.site.default_ttl == 60 && aws_cloudfront_cache_policy.site.max_ttl == 300 && one(aws_cloudfront_distribution.site.default_cache_behavior).cache_policy_id == aws_cloudfront_cache_policy.site.id ) error_message = "The public-content cache must use the explicitly bounded 0/60/300 second policy." } assert { condition = ( one(one(aws_cloudfront_cache_policy.site.parameters_in_cache_key_and_forwarded_to_origin).cookies_config).cookie_behavior == "none" && one(one(aws_cloudfront_cache_policy.site.parameters_in_cache_key_and_forwarded_to_origin).headers_config).header_behavior == "none" && one(one(aws_cloudfront_cache_policy.site.parameters_in_cache_key_and_forwarded_to_origin).query_strings_config).query_string_behavior == "none" ) error_message = "User cookies, query strings and selected request headers must not become origin/cache inputs for this public static-content design." } assert { condition = output.site_url == "https://static.example.invalid" && output.distribution_id == "E000000EXAMPLE" error_message = "Outputs must match the declared URL and synthetic distribution fixture." } } run "reject_certificate_outside_us_east_1" { command = plan variables { certificate_arn = "arn:aws:acm:eu-west-1:111122223333:certificate/00000000-0000-0000-0000-000000000001" } expect_failures = [var.certificate_arn] } run "reject_malformed_certificate" { command = plan variables { certificate_arn = "arn:aws:acm:us-east-1:111122223333:certificate/not-a-uuid" } expect_failures = [var.certificate_arn] } run "reject_url_instead_of_hostname" { command = plan variables { site_domain = "https://static.example.invalid/path" } expect_failures = [var.site_domain] } run "reject_ip_address_hostname" { command = plan variables { site_domain = "192.0.2.1" } expect_failures = [var.site_domain] } run "reject_invalid_bucket_name" { command = plan variables { bucket_name = "Invalid_bucket_name" } expect_failures = [var.bucket_name] } run "reject_reserved_bucket_suffix" { command = plan variables { bucket_name = "example-static--x-s3" } expect_failures = [var.bucket_name] } run "reject_reserved_demo_bucket_prefix" { command = plan variables { bucket_name = "amzn-s3-demo-assets" } expect_failures = [var.bucket_name] } run "reject_account_regional_bucket_suffix" { command = plan variables { bucket_name = "example-static-an" } expect_failures = [var.bucket_name] }