Terraform’s S3 module can create buckets with versioning and policies, but the way it handles the versioning block is often misunderstood.
Here’s a real-world example of how you might use it:
module "my_secure_bucket" {
source = "./modules/s3" # Assuming a local module for demonstration
bucket_name = "my-super-secure-data-bucket-12345"
acl = "private"
versioning = {
enabled = true
}
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowInternalServiceAccess"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::111122223333:user/my-internal-service-user"
}
Action = "s3:GetObject"
Resource = "arn:aws:s3:::my-super-secure-data-bucket-12345/*"
}
]
})
tags = {
Environment = "Production"
Project = "DataLake"
}
}
This module definition sets up a bucket named my-super-secure-data-bucket-12345 with a private ACL. Crucially, it also enables versioning. The versioning block expects a map, and setting enabled = true is the key to turning on S3 versioning for this bucket. A JSON-encoded IAM policy is also attached, granting s3:GetObject permissions to a specific IAM user within your AWS account, but only for objects within this bucket. Finally, some tags are applied for organization.
Let’s dive into how this works under the hood and what you can control. The S3 module, in essence, orchestrates AWS API calls to create and configure your S3 bucket. When you specify versioning = { enabled = true }, Terraform translates this into an s3:PutBucketVersioning API call to AWS. This call modifies the bucket’s versioning configuration. The policy argument, when provided with a JSON string, is used in an s3:PutBucketPolicy API call. This attaches the specified policy to the bucket, controlling access.
The versioning block itself is quite simple in this context. You can also set mfa_delete = true within this block if you want to enforce multi-factor authentication for any delete operations on versioned objects. This adds an extra layer of security, preventing accidental or malicious deletion of object versions.
The policy argument is where most of the fine-grained access control happens. You are essentially writing an IAM policy document that applies directly to the bucket. This policy can grant or deny access to various S3 actions (s3:PutObject, s3:GetObject, s3:DeleteObject, etc.) for specific principals (users, roles, services) and resources (the bucket itself, or objects within it). The jsonencode function is vital here, as Terraform expects a string for the policy, and this function correctly formats your policy map into a JSON string.
What most people don’t realize is that the versioning block in Terraform’s S3 module doesn’t create versioning; it configures it. If versioning is already enabled or suspended on an existing bucket, and you apply this Terraform configuration, Terraform will update the bucket’s versioning state to match what’s defined in your .tf file. This means you can use the same block to enable, suspend, or resume versioning on a bucket.
The next step is often understanding how to manage object lifecycle rules in conjunction with versioning to automatically clean up old object versions.