S3 Bucket
A private, versioned S3 bucket with public access blocked and optional TLS only access.
GovCloudCMKView source
- Module
- s3-bucket
- Layer
- Data
- Interface
- 10 inputs, 4 outputs
- Used by
- 9 Kaizen apps
Why it matters
Uploads, exports, and logs all end up in S3, and a bucket left open to the public is one of the most common ways cloud data leaks. Every bucket from this module blocks public access, keeps old versions of each file, and encrypts objects at rest. Federal stacks add a customer managed key and a policy that rejects any request not sent over TLS.
Use it when
- The app stores user uploads, generated files, or exports.
- You need a log bucket, for example the load balancer's access_logs_bucket.
- You host a static site behind CloudFront with origin access control.
Reach for something else when
- You need to query the data with SQL or keep relational records. Use rds-postgres.
What it creates
aws_s3_bucket(force_destroy = false by default)aws_s3_bucket_versioning(enabled by default)aws_s3_bucket_server_side_encryption_configuration(AES256, or aws:kms with a bucket key)aws_s3_bucket_public_access_block(all four settings true)aws_s3_bucket_ownership_controls(BucketOwnerEnforced, optional)aws_s3_bucket_lifecycle_configuration(optional)aws_s3_bucket_policy(DenyInsecureTransport and caller JSON, optional)aws_s3_bucket_cors_configuration(optional, for browser uploads)
Secure by default
- All four public access block settings are on (not configurable).
- Versioning is on (versioning_enabled = true).
- Objects are encrypted at rest with AES256 when no key is given.
- Terraform refuses to delete a bucket that still holds objects (force_destroy = false).
Commercial and GovCloud
module "s3_uploads" {
source = "git::ssh://git@github.com/the-kaizen-labs/terraform-modules.git//s3-bucket?ref=v1.7.0"
bucket_name = "${local.app_name}-uploads"
cors_rules = [{
allowed_origins = [local.app_url]
allowed_methods = ["GET", "PUT"]
allowed_headers = ["*"]
expose_headers = ["ETag"]
max_age_seconds = 3000
}]
tags = local.tags
}| Setting | Commercial default | Federal setting | Note |
|---|---|---|---|
kms_key_id | null (AES256) | customer managed KMS key ARN | FedRAMP SC-13. |
enforce_tls_requests_only | false | true | FedRAMP SC-8. Also satisfies AWS Config rule s3-bucket-ssl-requests-only. |
enforce_bucket_owner | false | true | Disables ACLs entirely with BucketOwnerEnforced. |
How it connects
- To ecs-service: bucket_arn, in a policy on task_role_name
- To alb: bucket_name (as access_logs_bucket)
Inputs
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
bucket_name | string | yes | S3 bucket name (must be globally unique) | |
bucket_policy_json | string | null | no | Bucket policy JSON. Null skips the policy. Composes with enforce_tls_requests_only - both can be set. |
cors_rules | list(object({ allowed_origins = list(string) allowed_methods = list(string) allowed_headers = list(string) expose_headers = list(string) max_age_seconds = number })) | [] | no | CORS rules. Leave empty if the bucket is not accessed directly from a browser. |
enforce_bucket_owner | bool | false | no | Apply BucketOwnerEnforced ownership controls (disables ACLs entirely). Required for federal posture; gated to avoid state churn on pre-April-2023 buckets that still use ObjectWriter. |
enforce_tls_requests_only | bool | false | no | Attach a bucket policy denying any S3 request where aws:SecureTransport = false. Required for federal posture (FedRAMP SC-8) and to satisfy AWS Config rule s3-bucket-ssl-requests-only. |
force_destroy | bool | false | no | Allow Terraform to delete the bucket even if it contains objects. Disable in production. |
kms_key_id | string | null | no | Customer-managed KMS key ARN for default encryption. Null falls back to AES256 (AWS-managed). Federal posture (FedRAMP SC-13) requires a customer-managed key. |
lifecycle_rules | list(object({ id = string prefix = string expiration_days = number noncurrent_version_expiration_days = number abort_multipart_upload_days = number })) | [] | no | Lifecycle rules. Null fields skip the corresponding sub-block. |
tags | map(string) | {} | no | Tags to apply to all resources |
versioning_enabled | bool | true | no | Enable S3 versioning |
Outputs
| Name | Description |
|---|---|
bucket_arn | Value: aws_s3_bucket.this.arn |
bucket_id | Value: aws_s3_bucket.this.id |
bucket_name | Value: aws_s3_bucket.this.bucket |
bucket_regional_domain_name | Value: aws_s3_bucket.this.bucket_regional_domain_name |
Used by
| App | Pinned ref |
|---|---|
| BidBuddy | v1.0.0 |
| dust | v1.1.0 |
| evergreen | v1.1.0 |
| ginkgo | v1.3.0 |
| magnolia | v1.1.0 |
| oak | v1.1.0 |
| poppy | v1.1.0 |
| saplings | v1.3.0 |
| taproot | v1.1.0 |