diff --git a/s3-bucket/README.md b/s3-bucket/README.md new file mode 100644 index 0000000..d8f5305 --- /dev/null +++ b/s3-bucket/README.md @@ -0,0 +1,24 @@ +# S3 Bucket + +## About + +This module allows you to setup an S3 bucket with the following features@ + + +## Usage + +See `variables.tf` for the full argument reference. + +```hcl +module "s3_bucket" { + source = "github.com/script47/aws-tf-modules/s3-bucket" + + name = "my-bucket" + + tags = { + Project = "my-project" + Service = "my-service" + Environment = "production" + } +} +``` diff --git a/s3-bucket/data.tf b/s3-bucket/data.tf new file mode 100644 index 0000000..0c4f1b5 --- /dev/null +++ b/s3-bucket/data.tf @@ -0,0 +1,69 @@ +data "aws_iam_policy_document" "deny_insecure_requests" { + statement { + sid = "DenyInsecureRequests" + effect = "Deny" + + actions = ["s3:*"] + + resources = [ + aws_s3_bucket.this.arn, + "${aws_s3_bucket.this.arn}/*" + ] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = ["false"] + } + } +} + +data "aws_iam_policy_document" "enforce_sse" { + statement { + sid = "DenyObjectsThatAreNotSSE" + effect = "Deny" + + actions = [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:CopyObject", + ] + + resources = ["${aws_s3_bucket.this.arn}/*"] + + principals { + type = "*" + identifiers = ["*"] + } + + condition { + test = "StringNotEquals" + variable = "s3:x-amz-server-side-encryption" + values = var.sse.algo + } + + dynamic "condition" { + for_each = var.sse.kms_key_id != null ? [1] : [] + + content { + test = "StringNotEquals" + variable = "s3:x-amz-server-side-encryption-aws-kms-key-id" + values = [var.sse.kms_key_id] + } + } + } +} + +data "aws_iam_policy_document" "merged" { + source_policy_documents = compact([ + var.policy, + data.aws_iam_policy_document.deny_insecure_requests.json, + var.sse.enabled && var.sse.enforce ? data.aws_iam_policy_document.enforce_sse.json : null + ]) +} \ No newline at end of file diff --git a/s3-bucket/locals.tf b/s3-bucket/locals.tf new file mode 100644 index 0000000..066066c --- /dev/null +++ b/s3-bucket/locals.tf @@ -0,0 +1 @@ +locals {} diff --git a/s3-bucket/outputs.tf b/s3-bucket/outputs.tf new file mode 100644 index 0000000..ba84401 --- /dev/null +++ b/s3-bucket/outputs.tf @@ -0,0 +1,6 @@ +output "bucket" { + value = { + arn = aws_s3_bucket.this.arn + id = aws_s3_bucket.this.id + } +} diff --git a/s3-bucket/providers.tf b/s3-bucket/providers.tf new file mode 100644 index 0000000..502f7f9 --- /dev/null +++ b/s3-bucket/providers.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6" + } + } +} \ No newline at end of file diff --git a/s3-bucket/s3.tf b/s3-bucket/s3.tf new file mode 100644 index 0000000..fb2cf41 --- /dev/null +++ b/s3-bucket/s3.tf @@ -0,0 +1,72 @@ +resource "aws_s3_bucket" "this" { + bucket = var.name + force_destroy = true + tags = var.tags +} + +resource "aws_s3_bucket_policy" "this" { + bucket = aws_s3_bucket.this.id + policy = data.aws_iam_policy_document.merged.json +} + +resource "aws_s3_bucket_versioning" "this" { + bucket = aws_s3_bucket.this.id + + versioning_configuration { + status = var.versioned ? "Enabled" : "Suspended" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "this" { + count = var.sse.enabled ? 1 : 0 + + bucket = aws_s3_bucket.this.id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.sse.algo + kms_master_key_id = var.sse.kms_key_id + } + + bucket_key_enabled = var.sse.bucket_key_enabled + } +} + +resource "aws_s3_bucket_public_access_block" "this" { + bucket = aws_s3_bucket.this.id + + block_public_acls = var.public_access_block.block_public_acls + block_public_policy = var.public_access_block.block_public_policy + ignore_public_acls = var.public_access_block.ignore_public_acls + restrict_public_buckets = var.public_access_block.restrict_public_buckets +} + +# resource "aws_s3_bucket_lifecycle_configuration" "this" { +# count = length(var.lifecycle_rules) > 0 ? 1 : 0 + +# bucket = aws_s3_bucket.this.id + +# dynamic "rule" { +# for_each = var.lifecycle_rules + +# content { +# id = each.value.id + +# status = each.value.enabled ? "Enabled" : "Disabled" + +# dynamic "transition" { +# for_each = var.lifecycle_rules.transitions + +# content { +# days = each.value.days +# storage_class = each.value.storage_class +# } +# } +# } +# } +# } + +resource "aws_s3_bucket_notification" "this" { + bucket = aws_s3_bucket.this.id + eventbridge = var.eventbridge +} \ No newline at end of file diff --git a/s3-bucket/variables.tf b/s3-bucket/variables.tf new file mode 100644 index 0000000..31e7f71 --- /dev/null +++ b/s3-bucket/variables.tf @@ -0,0 +1,66 @@ +variable "name" { + type = string + description = "The name of the bucket" + default = null +} + +variable "policy" { + type = string + description = "" + default = null +} + +variable "versioned" { + type = bool + description = "Enable bucket versioning" + default = false +} + +variable "sse" { + type = object({ + enabled = optional(bool, true) + enforced = optional(bool, true) + algo = optional(string, "AES256") + kms_key_id = optional(string, null) + bucket_key_enabled = optional(bool, null) + }) + default = {} +} + +variable "public_access_block" { + type = object({ + block_public_acls = optional(bool, true) + block_public_policy = optional(bool, true) + ignore_public_acls = optional(bool, true) + restrict_public_buckets = optional(bool, true) + }) + default = {} +} + +# variable "lifecycle_rules" { +# type = list(object({ +# id = string +# enabled = bool +# expiration_days = optional(number) +# filter = optional(list(object({ +# prefix = optional(string, "") + +# }))) +# transitions = optional(list(object({ +# days = optional(number, 0) +# storage_class = string +# }))) +# })) +# default = [] +# } + +variable "eventbridge" { + type = bool + description = "Enable EventBridge events against this bucket" + default = false +} + +variable "tags" { + type = map(string) + description = "The tags to apply to all resources created" +}