-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from mineiros-io/mariux/initial-features
initial secure s3-bucket setup
- Loading branch information
Showing
5 changed files
with
446 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,41 @@ | ||
# aws-s3-bucket | ||
# Terraform Module for creating a secure S3-Bucket | ||
|
||
## Features | ||
In contrast to the plain `aws_s3_bucket` resource this module creates secure | ||
buckets by default. While all security features can be disabled as needed best practices | ||
are pre-configured. | ||
|
||
In addition to security easy cross-account access can be granted to the objects | ||
of the bucket enforcing `bucket-owner-full-control` acl for objects created by other accounts. | ||
|
||
### Default Security Settings | ||
- :heavy_check_mark: Bucket public access blocking all set to `true` by default | ||
- :heavy_check_mark: Server-Side-Encryption (SSE) at rest `enabled` by default (AES256) | ||
- :heavy_check_mark: Bucket ACL defaults to canned `private` ACL | ||
|
||
### Standard S3 Features | ||
- :heavy_check_mark: Server-Side-Encryption (SSE) enabled by default | ||
- :heavy_check_mark: Versioning | ||
- :heavy_check_mark: Bucket Logging | ||
- :heavy_check_mark: Lifecycle Rules | ||
- :heavy_check_mark: Request Payer | ||
- :heavy_check_mark: Cross-Origin Resource Sharing (CORS) | ||
- :heavy_check_mark: Acceleration Status | ||
- :heavy_check_mark: Bucket Policy | ||
- :heavy_check_mark: Tags | ||
- :x: Replication Configuration (not yet implemented) | ||
- :x: Website Configuration (not yet implemented) | ||
- :x: S3 Object Locking (not yet implemented) | ||
|
||
### Extended S3 Features | ||
- :heavy_check_mark: Bucket Public Access Blocking defaulting to `true` | ||
- :x: Bucket Notifications (not yet implemented) | ||
- :x: Bucket Metrics (not yet implemented) | ||
- :x: Bucket Inventory (not yet implemented) | ||
- :x: S3 Access Points (not yet supported by terraform aws provider :warning:) | ||
|
||
### Additional Features | ||
- :heavy_check_mark: Cross-Account access policy with forced `bucket-owner-full-control` ACL for direct access | ||
- :x: Cloudfront Origin Access Identity (OAI) policy | ||
- :x: Generate Cross-Account role for OAI enabled buckets if desired | ||
- :x: Generate KMS key to encrypt objects at rest if desired |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
locals { | ||
cors_enabled = length(keys(var.cors_rule)) > 0 | ||
versioning_enabled = length(keys(var.versioning)) > 0 | ||
logging_enabled = length(keys(var.logging)) > 0 | ||
sse_enabled = length(keys(var.apply_server_side_encryption_by_default)) > 0 | ||
|
||
cors = local.cors_enabled ? [var.cors_rule] : [] | ||
versioning = local.versioning_enabled ? [var.versioning] : [] | ||
logging = local.logging_enabled ? [var.logging] : [] | ||
encryption = local.sse_enabled ? [var.apply_server_side_encryption_by_default] : [] | ||
} | ||
|
||
resource "aws_s3_bucket" "bucket" { | ||
count = var.create ? 1 : 0 | ||
|
||
bucket = var.bucket | ||
bucket_prefix = var.bucket_prefix | ||
acl = var.acl | ||
tags = var.tags | ||
force_destroy = var.force_destroy | ||
acceleration_status = var.acceleration_status | ||
region = var.region | ||
request_payer = var.request_payer | ||
|
||
dynamic "cors_rule" { | ||
for_each = local.cors | ||
|
||
content { | ||
allowed_headers = lookup(cors_rule.value, "allowed_headers", null) | ||
allowed_methods = cors_rule.value.allowed_methods | ||
allowed_origins = cors_rule.value.allowed_origins | ||
expose_headers = lookup(cors_rule.value, "expose_headers", null) | ||
max_age_seconds = lookup(cors_rule.value, "max_age_seconds", null) | ||
} | ||
} | ||
|
||
dynamic "versioning" { | ||
for_each = local.versioning | ||
|
||
content { | ||
enabled = lookup(versioning.value, "enabled", null) | ||
mfa_delete = lookup(versioning.value, "mfa_delete", null) | ||
} | ||
} | ||
|
||
dynamic "logging" { | ||
for_each = local.logging | ||
|
||
content { | ||
target_bucket = logging.value.target_bucket | ||
target_prefix = lookup(logging.value, "target_prefix", null) | ||
} | ||
} | ||
|
||
dynamic "server_side_encryption_configuration" { | ||
for_each = local.encryption | ||
iterator = sse | ||
|
||
content { | ||
rule { | ||
apply_server_side_encryption_by_default { | ||
sse_algorithm = sse.value.sse_algorithm | ||
kms_master_key_id = lookup(sse.value, "kms_master_key_id", null) | ||
} | ||
} | ||
} | ||
} | ||
|
||
dynamic "lifecycle_rule" { | ||
for_each = var.lifecycle_rules | ||
iterator = rule | ||
|
||
content { | ||
id = lookup(rule.value, "id", null) | ||
prefix = lookup(rule.value, "prefix", null) | ||
tags = lookup(rule.value, "tags", null) | ||
abort_incomplete_multipart_upload_days = lookup(rule.value, "abort_incomplete_multipart_upload_days", null) | ||
enabled = rule.value.enabled | ||
|
||
dynamic "expiration" { | ||
for_each = length(keys(lookup(rule.value, "expiration", {}))) == 0 ? [] : [rule.value.expiration] | ||
|
||
content { | ||
date = lookup(expiration.value, "date", null) | ||
days = lookup(expiration.value, "days", null) | ||
expired_object_delete_marker = lookup(expiration.value, "expired_object_delete_marker", null) | ||
} | ||
} | ||
|
||
dynamic "transition" { | ||
for_each = lookup(rule.value, "transition", []) | ||
|
||
content { | ||
date = lookup(transition.value, "date", null) | ||
days = lookup(transition.value, "days", null) | ||
storage_class = transition.value.storage_class | ||
} | ||
} | ||
|
||
dynamic "noncurrent_version_expiration" { | ||
for_each = length(keys(lookup(rule.value, "noncurrent_version_expiration", {}))) == 0 ? [] : [rule.value.noncurrent_version_expiration] | ||
iterator = expiration | ||
|
||
content { | ||
days = lookup(expiration.value, "days", null) | ||
} | ||
} | ||
|
||
dynamic "noncurrent_version_transition" { | ||
for_each = lookup(rule.value, "noncurrent_version_transition", []) | ||
iterator = transition | ||
|
||
content { | ||
days = lookup(transition.value, "days", null) | ||
storage_class = transition.value.storage_class | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
locals { | ||
bucket_id = join("", aws_s3_bucket.bucket.*.id) | ||
bucket_arn = join("", aws_s3_bucket.bucket.*.arn) | ||
|
||
cross_account_bucket_actions_enabled = length(var.cross_account_bucket_actions) > 0 | ||
cross_account_object_actions_enabled = length(var.cross_account_object_actions) > 0 | ||
cross_account_object_actions_with_forced_acl_enabled = length(var.cross_account_object_actions_with_forced_acl) > 0 | ||
|
||
cross_account_actions_enabled = local.cross_account_bucket_actions_enabled || local.cross_account_object_actions_enabled || local.cross_account_object_actions_with_forced_acl_enabled | ||
|
||
cross_account_enabled = length(var.cross_account_identifiers) > 0 && local.cross_account_actions_enabled | ||
|
||
policy_enabled = var.create && (var.policy != null || local.cross_account_enabled) | ||
} | ||
|
||
resource "aws_s3_bucket_public_access_block" "bucket" { | ||
count = var.create ? 1 : 0 | ||
|
||
bucket = local.bucket_id | ||
|
||
block_public_acls = var.block_public_acls | ||
block_public_policy = var.block_public_policy | ||
ignore_public_acls = var.ignore_public_acls | ||
restrict_public_buckets = var.restrict_public_buckets | ||
} | ||
|
||
resource "aws_s3_bucket_policy" "bucket" { | ||
count = local.policy_enabled ? 1 : 0 | ||
|
||
depends_on = [ | ||
aws_s3_bucket_public_access_block.bucket | ||
] | ||
|
||
bucket = local.bucket_id | ||
policy = join("", data.aws_iam_policy_document.bucket.*.json) | ||
} | ||
|
||
data "aws_iam_policy_document" "bucket" { | ||
count = local.policy_enabled ? 1 : 0 | ||
|
||
source_json = var.policy | ||
|
||
dynamic "statement" { | ||
for_each = length(var.cross_account_bucket_actions) == 0 ? [] : [1] | ||
|
||
content { | ||
actions = var.cross_account_bucket_actions | ||
resources = [local.bucket_arn] | ||
|
||
principals { | ||
type = "AWS" | ||
identifiers = var.cross_account_identifiers | ||
} | ||
} | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = length(var.cross_account_object_actions) == 0 ? [] : [1] | ||
|
||
content { | ||
actions = var.cross_account_object_actions | ||
resources = ["${local.bucket_arn}/*"] | ||
|
||
principals { | ||
type = "AWS" | ||
identifiers = var.cross_account_identifiers | ||
} | ||
} | ||
} | ||
|
||
dynamic "statement" { | ||
for_each = length(var.cross_account_object_actions_with_forced_acl) == 0 ? [] : [1] | ||
|
||
content { | ||
actions = var.cross_account_object_actions_with_forced_acl | ||
resources = ["${local.bucket_arn}/*"] | ||
|
||
principals { | ||
type = "AWS" | ||
identifiers = var.cross_account_identifiers | ||
} | ||
|
||
dynamic "condition" { | ||
for_each = length(var.cross_account_forced_acls) == 0 ? [] : [1] | ||
|
||
content { | ||
test = "StringEquals" | ||
variable = "s3:x-amz-acl" | ||
values = var.cross_account_forced_acls | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
output "id" { | ||
description = "The name of the bucket." | ||
value = join("", aws_s3_bucket.bucket.*.id) | ||
} | ||
|
||
output "arn" { | ||
description = "The ARN of the bucket." | ||
value = join("", aws_s3_bucket.bucket.*.arn) | ||
} | ||
|
||
output "bucket_domain_name" { | ||
description = "The domain name of the bucket." | ||
value = join("", aws_s3_bucket.bucket.*.bucket_domain_name) | ||
} | ||
|
||
output "bucket_regional_domain_name" { | ||
description = "The region-specific domain name of the bucket." | ||
value = join("", aws_s3_bucket.bucket.*.bucket_regional_domain_name) | ||
} | ||
|
||
output "hosted_zone_id" { | ||
description = "The Route 53 Hosted Zone ID for this bucket's region." | ||
value = join("", aws_s3_bucket.bucket.*.hosted_zone_id) | ||
} | ||
|
||
output "region" { | ||
description = "The AWS region this bucket resides in." | ||
value = join("", aws_s3_bucket.bucket.*.region) | ||
} |
Oops, something went wrong.