6

I've been attempting to recreate an existing infrastructure using Terraform and one of the required services is an S3 bucket which should contain publicly accessible images.

Here is the Terraform code for the bucket:

resource "aws_s3_bucket" "foo_icons" {

  bucket = join("-", [local.prefix, "foo", "icons"])
  tags = {
    Name        = join("-", [local.prefix, "foo", "icons"])
    Environment = var.environment
  }
}


resource "aws_s3_bucket_acl" "icons_bucket_acl" {
  bucket = aws_s3_bucket.foo_icons.id
  acl    = "public-read"
}

The bucket is populated as follows:

resource "aws_s3_object" "icon_repository_files" {
  for_each = fileset("../files/icon-repository/", "**")
  bucket = aws_s3_bucket.foo_icons.id
  key = each.value
  source = "../files/icon-repository/${each.value}"
  etag = filemd5("../files/icon-repository/${each.value}")
}

The result I can see on the console is that the bucket is in fact publicly accessible, but that each object in the bucket is not public according to the ACL shown. I also can't reach the S3 objects with the displayed url: this results in access denied.

What is the best way to create a bucket with publicly accessible objects in Terraform? I read that ACL is no longer "modern" so if there is a better approach to achieve this, I'd be happy to hear it.

Rich Churcher
  • 7,361
  • 3
  • 37
  • 60
  • 1
    IMO you should use TF to create the bucket and settings, but objects within the bucket should be managed differently as they are not infrastructure. I would also recommend serving your s3 files through a CDN instead of a public bucket. – jordanm Jul 06 '22 at 15:34
  • You should use CloudFront to serve static content to web. CloudFront offers edge locations, caching, security, protection against DDoS attacks and it will cost you less in the long run. – victor m Jul 18 '23 at 16:26

1 Answers1

0

A public bucket does not imply that all objects within it are also public. The permissions are more fine-grained than that. To allow blanket access to every object within the bucket by anyone at all, you can use the aws_s3_bucket_policy resource to give the s3:GetObject permission to everyone.

Here's an example of a public bucket, using the more recent aws_s3_bucket_public_access_block resource which, as you mention, is intended to replace the acl argument.

resource "aws_s3_bucket" "foo_icons" {
  bucket = join("-", [local.prefix, "foo", "icons"])
}

resource "aws_s3_bucket_ownership_controls" "foo_icons" {
  bucket = aws_s3_bucket.foo_icons.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_public_access_block" "foo_icons" {
  bucket = aws_s3_bucket.foo_icons.id

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

resource "aws_s3_bucket_acl" "foo_icons" {
  bucket = aws_s3_bucket.foo_icons.id
  acl    = "public-read"

  depends_on = [
    aws_s3_bucket_ownership_controls.foo_icons,
    aws_s3_bucket_public_access_block.foo_icons,
  ]
}

data "aws_iam_policy_document" "s3_bucket_foo_icons" {
  policy_id = "s3_bucket_foo_icons"

  statement {
    actions = [
      "s3:GetObject"
    ]
    effect = "Allow"
    resources = [
      "${aws_s3_bucket.foo_icons.arn}/*"
    ]
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    sid = "S3IconsBucketPublicAccess"
  }
}

resource "aws_s3_bucket_policy" "foo_icons" {
  bucket = aws_s3_bucket.foo_icons.id
  policy = data.aws_iam_policy_document.s3_bucket.foo_icons.json
}

Care should be taken not to accidentally apply the policy to other buckets, as we're deliberately overriding pretty much every precaution and permission S3 buckets get by default when they're created.

Rich Churcher
  • 7,361
  • 3
  • 37
  • 60