Using S3 as a storage backend for your application
For local development we use LocalStack.cloud. It is utilized as a local emulation of S3 during development.This might be beneficial for testing and development purposes before transitioning to actual S3 service like in a production environment.
/** * This sets up an S3 bucket for notifications assets (eg photos, documents). The bucket is * PUBLIC for read access and needs to be opened up to the API (which is deployed as an ECS task). * * It also needs to be opened up to worker (to create pre-signed urls for uploads) * * This bucket is: * * public read access * * private write access to the credentials (added onto the user rather than onto the bucket) */## WARNING: this bucket uses IAM Policies and ACL for access## - @see https://binaryguy.tech/aws/s3/iam-policies-vs-s3-policies-vs-s3-bucket-acls/#:~:text=The%20biggest%20advantage%20of%20using,well%20as%20objects%20in%20it.# - @see https://aws.amazon.com/blogs/security/iam-policies-and-bucket-policies-and-acls-oh-my-controlling-access-to-s3-resources/# - @see https://stackoverflow.com/questions/47815526/s3-bucket-policy-vs-access-control-list### TF: https://www.terraform.io/docs/providers/aws/r/s3_bucket.html# AWS: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html# AWS CLI: http://docs.aws.amazon.com/cli/latest/reference/s3api/create-bucket.htmlresource "aws_s3_bucket" "notifications" { bucket = "${var.environment}-notifications-data" # removed for upgrade to aws provider 4.0 # see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-4-upgrade#acl-argument # acl = "private" tags = { Environment = var.environment Terraform = true }}# TF: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_blockresource "aws_s3_bucket_public_access_block" "notifications" { bucket = aws_s3_bucket.notifications.id block_public_acls = false # This feature protects your bucket from accidentally getting a policy that would enable public access # We WANT public access for read block_public_policy = false ignore_public_acls = false restrict_public_buckets = false}# TF: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls# this is required when setting the 'public-read' acl—without it it fails on applyresource "aws_s3_bucket_ownership_controls" "notifications" { bucket = aws_s3_bucket.notifications.id rule { object_ownership = "BucketOwnerPreferred" }}# TF: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_aclresource "aws_s3_bucket_acl" "notifications" { bucket = aws_s3_bucket.notifications.id # Defaults to private # ["private" "public-read" "public-read-write" "authenticated-read" "aws-exec-read" "log-delivery-write"] acl = "public-read" depends_on = [ aws_s3_bucket_ownership_controls.notifications, aws_s3_bucket_public_access_block.notifications, ]}## Note: no point on having vesrioning enabled### Restricted CORS policy but increased headers exposed (that could be further restrictred)## TF: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration# AWS: https://docs.amplify.aws/lib/storage/getting-started/q/platform/js/#amazon-s3-bucket-cors-policy-setupresource "aws_s3_bucket_cors_configuration" "notifications" { bucket = aws_s3_bucket.notifications.id cors_rule { allowed_headers = ["*"] allowed_methods = [ "GET", "HEAD", "PUT", ] allowed_origins = ["/* OWN FQDN */"] expose_headers = [ "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2", "x-amz-meta-tag", "ETag" ] max_age_seconds = 3000 }}resource "aws_s3_bucket_policy" "notifications" { bucket = aws_s3_bucket.notifications.id policy = data.aws_iam_policy_document.notifications-user.json}# [Data] IAM policy to define S3 permissions## Restricting users files types to avoid problems (note: hand helds love to upload PDFs)## TF: https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html# AWS: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html# AWS CLI: http://docs.aws.amazon.com/cli/latest/reference/iam/create-policy.htmldata "aws_iam_policy_document" "notifications-user" { statement { sid = "DenyNonImage" effect = "Deny" principals { identifiers = [aws_iam_user.notifications.arn] type = "AWS" } actions = [ # NOTE: this is an ACL permissions "s3:PutObjectAcl", ] not_resources = [ "${aws_s3_bucket.notifications.arn}/*.png", "${aws_s3_bucket.notifications.arn}/*.jpg", "${aws_s3_bucket.notifications.arn}/*.jpeg", "${aws_s3_bucket.notifications.arn}/*.gif" ] } # NOTE: these rules exist on the user policy/* statement { effect = "Allow" principals { type = "AWS" identifiers = [aws_iam_user.notifications.arn] } actions = [ "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:PutObject", # this is the magic spice, because the perms are created via ACL (rather than roles) "s3:PutObjectAcl" ] resources = [ "${aws_s3_bucket.notifications.arn}*//*", ] }*/}######################## TF: https://www.terraform.io/docs/providers/aws/r/iam_policy.html# AWS: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html# AWS CLI: http://docs.aws.amazon.com/cli/latest/reference/iam/create-policy.htmlresource "aws_iam_policy" "s3" { name = "ecs-app-${var.environment}-s3" description = "Access from the ecs tasks to s3 buckets" policy = data.aws_iam_policy_document.s3.json}# [Data] IAM policy to define S3 permissions# TF: https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html# AWS: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html# AWS CLI: http://docs.aws.amazon.com/cli/latest/reference/iam/create-policy.htmldata "aws_iam_policy_document" "s3" { statement { sid = "" effect = "Allow" actions = [ "s3:DeleteObject", "s3:GetObject", "s3:PutObject", # this is the magic spice, because the perms are created via ACL (rather than roles) "s3:PutObjectAcl", "s3:ListBucket" ] resources = [ "arn:aws:s3:::${var.assets_bucket}/*" ] }}####################### Attaches a managed IAM policy to an IAM role for the ecs tasks# TF: https://www.terraform.io/docs/providers/aws/r/iam_role_policy_attachment.html# AWS: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html# AWS CLI: http://docs.aws.amazon.com/cli/latest/reference/iam/attach-role-policy.htmlresource "aws_iam_role_policy_attachment" "s3" { role = aws_iam_role.app_task.name # need your role attached to whatever is doing the execution (eg workload task) policy_arn = aws_iam_policy.s3.arn}