DEV Community

Cover image for Implementing Cross-Origin Resource Sharing (CORS) with Terraform and AWS S3
Chinmay Tonape
Chinmay Tonape

Posted on

Implementing Cross-Origin Resource Sharing (CORS) with Terraform and AWS S3

In this technical blog post, we will explore how to set up Cross-Origin Resource Sharing (CORS) for AWS S3 buckets using Terraform. CORS is essential for allowing web applications to make requests to a domain that is different from the one serving the web page, enabling secure and controlled data sharing across origins.

Architecture Overview

Before diving into the implementation details, let's outline the architecture we will be working with:

Architecture

Step 1: Create S3 Bucket with HTML Pages

We will create an Amazon S3 bucket that hosts our HTML pages. These pages will fetch resources (like images) from other S3 buckets , demonstrating the need for CORS.

################################################################################
# S3 static website bucket for html pages
################################################################################
resource "aws_s3_bucket" "my-static-website-html" {
  bucket = var.bucket_name_html
  tags = merge(local.common_tags, {
    Name = "${local.naming_prefix}-s3-bucket-html"
  })
}

################################################################################
# S3 public access settings
################################################################################
resource "aws_s3_bucket_public_access_block" "static_site_bucket_public_access" {
  bucket = aws_s3_bucket.my-static-website-html.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

################################################################################
# S3 bucket policy
################################################################################
resource "aws_s3_bucket_policy" "static_site_bucket_policy" {
  bucket = var.bucket_name_html

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "PublicReadGetObject"
        Principal = "*"
        Action = [
          "s3:GetObject",
        ]
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${var.bucket_name_html}",
          "arn:aws:s3:::${var.bucket_name_html}/*"
        ]
      },
    ]
  })

  depends_on = [aws_s3_bucket_public_access_block.static_site_bucket_public_access]
}


################################################################################
# S3 bucket static website configuration
################################################################################
resource "aws_s3_bucket_website_configuration" "static_site_bucket_website_config" {
  bucket = aws_s3_bucket.my-static-website-html.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

################################################################################
# Upload files to S3 Bucket - html files
################################################################################
resource "aws_s3_object" "provision_source_files" {
  bucket = aws_s3_bucket.my-static-website-html.id

  # webfiles/ is the Directory contains files to be uploaded to S3
  for_each = fileset("webfiles/", "**/*.html*")

  key          = each.value
  source       = "webfiles/${each.value}"
  content_type = "text/html"
  #acl          = "public-read" #use this only if you are using Bucket and Object ACLs, defaults to private
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Create S3 Bucket with Images

Additionally, we will set up another S3 bucket dedicated to hosting images. These images are static assets that our web pages hosted in the first S3 bucket will request.

################################################################################
# S3 static website bucket for images
################################################################################
resource "aws_s3_bucket" "my-static-website-images" {
  bucket = var.bucket_name_images
  tags = merge(local.common_tags, {
    Name = "${local.naming_prefix}-s3-bucket-images"
  })
}

################################################################################
# S3 public access settings
################################################################################
resource "aws_s3_bucket_public_access_block" "static_site_bucket_public_access_images" {
  bucket = aws_s3_bucket.my-static-website-images.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

################################################################################
# S3 bucket policy
################################################################################
resource "aws_s3_bucket_policy" "static_site_bucket_policy_images" {
  bucket = var.bucket_name_images

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "PublicReadGetObject"
        Principal = "*"
        Action = [
          "s3:GetObject",
        ]
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${var.bucket_name_images}",
          "arn:aws:s3:::${var.bucket_name_images}/*"
        ]
      },
    ]
  })

  depends_on = [aws_s3_bucket_public_access_block.static_site_bucket_public_access_images]
}


################################################################################
# S3 bucket static website configuration
################################################################################
resource "aws_s3_bucket_website_configuration" "static_site_bucket_website_config_images" {
  bucket = aws_s3_bucket.my-static-website-images.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

################################################################################
# Upload files to S3 Bucket - html files
################################################################################
resource "aws_s3_object" "provision_image_files" {
  bucket = aws_s3_bucket.my-static-website-images.id

  # webfiles/ is the Directory contains files to be uploaded to S3
  for_each = fileset("webfiles/", "**/*.jpg")

  key          = each.value
  source       = "webfiles/${each.value}"
  content_type = "image/jpg"
  #acl          = "public-read" #use this only if you are using Bucket and Object ACLs, defaults to private
}

Enter fullscreen mode Exit fullscreen mode

Step 3: CORS Configuration

This involves specifying which origins (domains) are allowed to access resources in our images S3 buckets.

################################################################################
# Setup Cross Origin Resource Sharing CORS for Images website
################################################################################
resource "aws_s3_bucket_cors_configuration" "example" {
  bucket = aws_s3_bucket.my-static-website-images.id

  cors_rule {
    allowed_headers = ["Authorization"]
    allowed_methods = ["GET"]
    allowed_origins = ["http://${var.bucket_name_html}.s3-website-us-east-1.amazonaws.com"]
    max_age_seconds = 3000
  }
}
Enter fullscreen mode Exit fullscreen mode

Steps to Run Terraform

Follow these steps to execute the Terraform configuration:

terraform init
terraform plan 
terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Upon successful completion, Terraform will provide relevant outputs.

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

Outputs:

static_site_endpoint = "http://my-s3-static-bucket-html-v1.s3-website-us-east-1.amazonaws.com"
Enter fullscreen mode Exit fullscreen mode

Testing

S3 buckets
S3 buckets

S3 Static Website:

S3 website

CORS details showing image loaded from CORS enabled S3 bucket
CORS Details

Cleanup

Remember to stop AWS components to avoid large bills.

terraform destroy -auto-approve
Enter fullscreen mode Exit fullscreen mode

Conclusion

In conclusion, leveraging Terraform to automate the setup of CORS in AWS S3 buckets allows for efficient and repeatable management of cross-origin resource sharing policies. By following the steps outlined in this post and utilizing the provided resources, you can ensure secure and controlled data sharing across different origins in your web applications.

Happy Coding!

Resources

CORS: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/cors.html

Github Link: https://github.com/chinmayto/terraform-aws-s3-website-with-cors

Top comments (0)