Before you start (checks)
• Have AWS CLI configured (aws configure) or set AWS_* env vars.
• Terraform installed (terraform -v).
• Recommended: use an S3 remote backend for state in real projects (not required for testing).
1) Create Terraform files (example structure)
day67-s3/
├─ main.tf
├─ variables.tf
├─ outputs.tf
└─ terraform.tfvars # optional: put bucket_name, region etc here
2) variables.tf
variable "region" {
type = string
default = "us-east-1"
}
variable "bucket_name" {
type = string
# bucket names must be globally unique
default = "my-unique-day67-bucket-<your-unique-suffix>"
}
variable "read_only_principal_arn" {
description = "ARN of IAM user or role to grant read-only access"
type = string
default = "arn:aws:iam::<ACCOUNT_ID>:user/<USERNAME>" # replace
}
3) main.tf — bucket, versioning, policy, (optional) public access block
provider "aws" {
region = var.region
}
# S3 bucket
resource "aws_s3_bucket" "site" {
bucket = var.bucket_name
# If you plan to host a static website, uncomment the website block and adjust
# website {
# index_document = "index.html"
# }
}
# IMPORTANT: If you want public access via ACL/policy, you may need to allow it:
# Create/Disable the account-level Block Public Access? Here we manage at bucket-level:
resource "aws_s3_bucket_public_access_block" "no_block" {
bucket = aws_s3_bucket.site.id
# To allow public access, set all to false. BE CAREFUL in production.
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
# Enable versioning
resource "aws_s3_bucket_versioning" "v" {
bucket = aws_s3_bucket.site.id
versioning_configuration {
status = "Enabled"
}
}
# Make bucket objects public-read by default using ACL on the bucket (optional)
# Note: Many modern setups prefer using bucket policy only. If you set ACL,
# ensure the public access block allows it (above).
resource "aws_s3_bucket_acl" "acl" {
bucket = aws_s3_bucket.site.id
acl = "public-read"
}
# Bucket policy to allow public GetObject (if you want fully public)
data "aws_iam_policy_document" "public_read" {
statement {
sid = "AllowPublicGetObject"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["*"]
}
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.site.arn}/*"]
}
}
resource "aws_s3_bucket_policy" "public_policy" {
bucket = aws_s3_bucket.site.id
policy = data.aws_iam_policy_document.public_read.json
}
# Bucket policy granting read-only to a specific IAM user/role
data "aws_iam_policy_document" "read_only_for_principal" {
statement {
sid = "AllowReadForSpecificPrincipal"
effect = "Allow"
principals {
type = "AWS"
identifiers = [var.read_only_principal_arn]
}
actions = [
"s3:GetObject",
"s3:ListBucket"
]
# ListBucket must reference bucket arn, GetObject references objects
resources = [
aws_s3_bucket.site.arn,
"${aws_s3_bucket.site.arn}/*"
]
}
}
resource "aws_s3_bucket_policy" "principal_policy" {
bucket = aws_s3_bucket.site.id
policy = data.aws_iam_policy_document.read_only_for_principal.json
}
Notes
• The above creates both a public policy (everyone) and a principal-specific policy. In practice use one or the other depending on your requirements. If the bucket should be fully public, keep public_policy; if you want only a specific IAM user/role to read, remove public_policy and aws_s3_bucket_acl and rely only on the principal_policy.
• Modern AWS best practice: avoid public buckets unless necessary. Instead, grant access to specific principals or use CloudFront with signed URLs.
4) outputs.tf
output "bucket_name" {
value = aws_s3_bucket.site.bucket
}
output "bucket_arn" {
value = aws_s3_bucket.site.arn
}
output "website_endpoint" {
value = aws_s3_bucket.site.website_endpoint
description = "Empty unless you enabled static website hosting."
sensitive = false
}
5) Initialize & apply (commands)
cd day67-s3
terraform init
terraform validate
terraform plan -out plan.tfplan
terraform apply "plan.tfplan"
# or terraform apply -auto-approve
Watch outputs for bucket name/ARN. If apply fails with public access errors, check AWS Account Public Access Block settings — account-level block may prevent public policy/ACL.
6) Test public read access
1. Upload a sample file (AWS CLI):
aws s3 cp sample.txt s3://<your-bucket-name>/sample.txt --acl public-read
- Open in browser:
https://<your-bucket-name>.s3.amazonaws.com/sample.txt
If publicly accessible you’ll see content.
If you used static website hosting, URL is:
http://<your-bucket-name>.s3-website-<region>.amazonaws.com/index.html
7) Verify versioning
Use AWS CLI:
aws s3api get-bucket-versioning --bucket <your-bucket-name>
# Expected: Status: Enabled
Upload an object, then upload again; list versions:
aws s3api list-object-versions --bucket <your-bucket-name> --prefix sample.txt
8) Clean up (optional)
# remove objects and versions first, then destroy
aws s3 rm s3://<your-bucket-name> --recursive
terraform destroy -auto-approve
S3 buckets with versioning require special handling to delete versions before bucket deletion.
Top comments (0)