diff --git a/_example/complete/example.tf b/_example/complete/example.tf new file mode 100644 index 0000000..ffbbad3 --- /dev/null +++ b/_example/complete/example.tf @@ -0,0 +1,152 @@ +provider "aws" { + region = "eu-west-1" +} + +module "logging_bucket" { + source = "./../../" + + name = "logging" + environment = "test" + attributes = ["public"] + label_order = ["name", "environment"] + acl = "log-delivery-write" +} + +module "kms_key" { + source = "clouddrove/kms/aws" + version = "0.15.0" + name = "kms" + environment = "test" + label_order = ["name", "environment"] + + enabled = true + description = "KMS key for s3" + deletion_window_in_days = 7 + enable_key_rotation = true + alias = "alias/s3" + policy = data.aws_iam_policy_document.default.json +} + +data "aws_iam_policy_document" "default" { + version = "2012-10-17" + statement { + sid = "Enable IAM User Permissions" + effect = "Allow" + principals { + type = "AWS" + identifiers = ["*"] + } + actions = ["kms:*"] + resources = ["*"] + } +} + +module "s3_bucket" { + source = "./../../" + + name = "clouddrove-secure-bucket-new-version" + environment = "test" + attributes = ["private"] + label_order = ["name", "environment"] + + acl = "" + #enable of disable versioning of s3 + versioning = true + + #acceleration and request payer enable or disable. + acceleration_status = true + request_payer = true + + # logging of s3 bucket to destination bucket. + logging = true + target_bucket = module.logging_bucket.id + target_prefix = "logs" + + #encrption on s3 with default encryption and kms encryption . + enable_server_side_encryption = true + enable_kms = true + kms_master_key_id = module.kms_key.key_arn + + #object locking of s3. + object_lock_configuration = { + mode = "GOVERNANCE" + days = 366 + years = null + } + + #cross replicaton of s3 + cors_rule = [{ + allowed_headers = ["*"], + allowed_methods = ["PUT", "POST"], + allowed_origins = ["https://s3-website-test.hashicorp.com"], + expose_headers = ["ETag"], + max_age_seconds = 3000 + }] + + #acl grant permission + grants = [ + { + id = null + type = "Group" + permissions = ["READ", "WRITE"] + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + }, + ] + owner_id = data.aws_canonical_user_id.current.id + + + #lifecycle rule for s3 + enable_lifecycle_configuration_rules = true + lifecycle_configuration_rules = [ + { + id = "log" + prefix = null + enabled = true + tags = { "temp" : "true" } + + enable_glacier_transition = false + enable_deeparchive_transition = false + enable_standard_ia_transition = false + enable_current_object_expiration = true + enable_noncurrent_version_expiration = true + + abort_incomplete_multipart_upload_days = null + noncurrent_version_glacier_transition_days = 0 + noncurrent_version_deeparchive_transition_days = 0 + noncurrent_version_expiration_days = 30 + + standard_transition_days = 0 + glacier_transition_days = 0 + deeparchive_transition_days = 0 + expiration_days = 365 + }, + { + id = "log1" + prefix = null + enabled = true + tags = {} + + enable_glacier_transition = false + enable_deeparchive_transition = false + enable_standard_ia_transition = false + enable_current_object_expiration = true + enable_noncurrent_version_expiration = true + + abort_incomplete_multipart_upload_days = 1 + noncurrent_version_glacier_transition_days = 0 + noncurrent_version_deeparchive_transition_days = 0 + noncurrent_version_expiration_days = 30 + + standard_transition_days = 0 + glacier_transition_days = 0 + deeparchive_transition_days = 0 + expiration_days = 365 + } + ] + + #static website on s3 + website_config_enable = true + +} + +data "aws_canonical_user_id" "current" {} \ No newline at end of file diff --git a/_example/complete/output.tf b/_example/complete/output.tf new file mode 100644 index 0000000..c91eacb --- /dev/null +++ b/_example/complete/output.tf @@ -0,0 +1,9 @@ +output "id" { + value = module.s3_bucket.*.id + description = "The ID of the s3 bucket." +} + +output "tags" { + value = module.s3_bucket.tags + description = "A mapping of tags to assign to the S3." +} \ No newline at end of file diff --git a/_example/cors_s3/example.tf b/_example/cors_s3/example.tf index ecb5b19..1459f6a 100644 --- a/_example/cors_s3/example.tf +++ b/_example/cors_s3/example.tf @@ -13,9 +13,10 @@ module "s3_bucket" { versioning = true acl = "private" cors_rule = [{ - "allowed_headers" : ["*"] + allowed_headers = ["*"], allowed_methods = ["PUT", "POST"], allowed_origins = ["https://s3-website-test.hashicorp.com"], expose_headers = ["ETag"], - max_age_seconds = 3000 }] + max_age_seconds = 3000 + }] } diff --git a/_example/default-s3/example.tf b/_example/default-s3/example.tf index 091ab90..21d8feb 100644 --- a/_example/default-s3/example.tf +++ b/_example/default-s3/example.tf @@ -12,4 +12,4 @@ module "s3_bucket" { versioning = true acl = "private" -} +} \ No newline at end of file diff --git a/_example/encryption-s3/example.tf b/_example/encryption-s3/example.tf index 3a82db5..7b09d05 100644 --- a/_example/encryption-s3/example.tf +++ b/_example/encryption-s3/example.tf @@ -40,8 +40,10 @@ module "s3_bucket" { attributes = ["public"] label_order = ["name", "environment"] - versioning = true - acl = "private" - sse_algorithm = "aws:kms" + versioning = true + acl = "private" + enable_server_side_encryption = true + + enable_kms = true kms_master_key_id = module.kms_key.key_arn } diff --git a/_example/logging-encryption-s3/example.tf b/_example/logging-encryption-s3/example.tf index 47b672f..38636cc 100644 --- a/_example/logging-encryption-s3/example.tf +++ b/_example/logging-encryption-s3/example.tf @@ -13,6 +13,34 @@ module "logging_bucket" { acl = "log-delivery-write" } +module "kms_key" { + source = "clouddrove/kms/aws" + version = "0.15.0" + name = "kms" + environment = "test" + label_order = ["name", "environment"] + + enabled = true + description = "KMS key for s3" + deletion_window_in_days = 7 + enable_key_rotation = true + alias = "alias/s3" + policy = data.aws_iam_policy_document.default.json +} + +data "aws_iam_policy_document" "default" { + version = "2012-10-17" + statement { + sid = "Enable IAM User Permissions" + effect = "Allow" + principals { + type = "AWS" + identifiers = ["*"] + } + actions = ["kms:*"] + resources = ["*"] + } +} module "s3_bucket" { source = "./../../" @@ -22,10 +50,16 @@ module "s3_bucket" { attributes = ["public"] label_order = ["name", "environment"] - versioning = true - acl = "private" - sse_algorithm = "AES256" - logging = { target_bucket : module.logging_bucket.id, target_prefix = "logs" } + versioning = true + acl = "private" + + enable_server_side_encryption = true + enable_kms = true + kms_master_key_id = module.kms_key.key_arn + + logging = true + target_bucket = module.logging_bucket.id + target_prefix = "logs" depends_on = [module.logging_bucket] } \ No newline at end of file diff --git a/_example/logging-s3/example.tf b/_example/logging-s3/example.tf index d25d950..ae44e47 100644 --- a/_example/logging-s3/example.tf +++ b/_example/logging-s3/example.tf @@ -20,9 +20,11 @@ module "s3_bucket" { attributes = ["public"] label_order = ["name", "environment"] - versioning = true - acl = "private" - logging = { target_bucket : module.logging_bucket.id, target_prefix = "logs" } + versioning = true + acl = "private" + logging = true + target_bucket = module.logging_bucket.id + target_prefix = "logs" depends_on = [module.logging_bucket] diff --git a/_example/website-s3/example.tf b/_example/website-s3/example.tf index d66f1a5..d14bbd7 100644 --- a/_example/website-s3/example.tf +++ b/_example/website-s3/example.tf @@ -10,12 +10,58 @@ module "s3_bucket" { attributes = ["public"] label_order = ["name", "environment"] - versioning = true - acl = "private" - website = { index_document : "index.html", error_document : "error.html" } - lifecycle_expiration_enabled = true - lifecycle_expiration_object_prefix = "test" - lifecycle_days_to_expiration = 10 + versioning = true + acl = "private" + + website_config_enable = true + + enable_lifecycle_configuration_rules = true + lifecycle_configuration_rules = [ + { + id = "log" + prefix = null + enabled = true + tags = { "temp" : "true" } + + enable_glacier_transition = false + enable_deeparchive_transition = false + enable_standard_ia_transition = false + enable_current_object_expiration = true + enable_noncurrent_version_expiration = true + + abort_incomplete_multipart_upload_days = null + noncurrent_version_glacier_transition_days = 0 + noncurrent_version_deeparchive_transition_days = 0 + noncurrent_version_expiration_days = 30 + + standard_transition_days = 0 + glacier_transition_days = 0 + deeparchive_transition_days = 0 + expiration_days = 365 + }, + { + id = "log1" + prefix = null + enabled = true + tags = {} + + enable_glacier_transition = false + enable_deeparchive_transition = false + enable_standard_ia_transition = false + enable_current_object_expiration = true + enable_noncurrent_version_expiration = true + + abort_incomplete_multipart_upload_days = 1 + noncurrent_version_glacier_transition_days = 0 + noncurrent_version_deeparchive_transition_days = 0 + noncurrent_version_expiration_days = 30 + + standard_transition_days = 0 + glacier_transition_days = 0 + deeparchive_transition_days = 0 + expiration_days = 365 + } + ] bucket_policy = true aws_iam_policy_document = data.aws_iam_policy_document.default.json diff --git a/main.tf b/main.tf index d582007..f518705 100644 --- a/main.tf +++ b/main.tf @@ -25,70 +25,92 @@ module "labels" { resource "aws_s3_bucket" "s3_default" { count = var.create_bucket == true ? 1 : 0 - bucket = module.labels.id - bucket_prefix = var.bucket_prefix - force_destroy = var.force_destroy - acl = var.acl - acceleration_status = var.acceleration_status - request_payer = var.request_payer - - versioning { - enabled = var.versioning - mfa_delete = var.mfa_delete - } - dynamic "website" { - for_each = length(keys(var.website)) == 0 ? [] : [var.website] + bucket = module.labels.id + bucket_prefix = var.bucket_prefix + force_destroy = var.force_destroy + + dynamic "object_lock_configuration" { + for_each = var.object_lock_configuration != null ? [1] : [] content { - index_document = lookup(website.value, "index_document", null) - error_document = lookup(website.value, "error_document", null) - redirect_all_requests_to = lookup(website.value, "redirect_all_requests_to", null) - routing_rules = lookup(website.value, "routing_rules", null) + object_lock_enabled = "Enabled" + } } +} + +# Module : S3 BUCKET POLICY +# Description : Terraform module which creates policy for S3 bucket on AWS +resource "aws_s3_bucket_policy" "s3_default" { + # count = var.create_bucket && var.bucket_policy && var.bucket_enabled == true ? 1 : 0 + count = var.bucket_policy == true ? 1 : 0 + bucket = join("", aws_s3_bucket.s3_default.*.id) + policy = var.aws_iam_policy_document +} - dynamic "logging" { - for_each = length(keys(var.logging)) == 0 ? [] : [var.logging] +resource "aws_s3_bucket_accelerate_configuration" "example" { + count = var.create_bucket && var.acceleration_status == true ? 1 : 0 - content { - target_bucket = logging.value.target_bucket - target_prefix = lookup(logging.value, "target_prefix", null) - } - } + bucket = join("", aws_s3_bucket.s3_default.*.id) + status = "Enabled" +} - server_side_encryption_configuration { - rule { - apply_server_side_encryption_by_default { - sse_algorithm = var.sse_algorithm - kms_master_key_id = var.kms_master_key_id - } - } +resource "aws_s3_bucket_request_payment_configuration" "example" { + count = var.create_bucket && var.request_payer == true ? 1 : 0 + + bucket = join("", aws_s3_bucket.s3_default.*.id) + payer = "Requester" +} + +resource "aws_s3_bucket_versioning" "example" { + count = var.create_bucket && var.versioning == true ? 1 : 0 + + bucket = join("", aws_s3_bucket.s3_default.*.id) + versioning_configuration { + status = "Enabled" } +} - dynamic "grant" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants - content { - id = grant.value.id - type = grant.value.type - permissions = grant.value.permissions - uri = grant.value.uri +resource "aws_s3_bucket_logging" "example" { + count = var.create_bucket && var.logging == true ? 1 : 0 + bucket = join("", aws_s3_bucket.s3_default.*.id) + + target_bucket = var.target_bucket + target_prefix = var.target_prefix +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + count = var.create_bucket && var.enable_server_side_encryption == true ? 1 : 0 + bucket = join("", aws_s3_bucket.s3_default.*.id) + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = var.enable_kms == true ? "aws:kms" : var.sse_algorithm + kms_master_key_id = var.kms_master_key_id } } +} - dynamic "object_lock_configuration" { - for_each = var.object_lock_configuration != null ? [1] : [] +resource "aws_s3_bucket_object_lock_configuration" "example" { + count = var.create_bucket && var.object_lock_configuration != null ? 1 : 0 - content { - object_lock_enabled = "Enabled" - rule { - default_retention { - mode = var.object_lock_configuration.mode - days = var.object_lock_configuration.days - years = var.object_lock_configuration.years - } - } + bucket = join("", aws_s3_bucket.s3_default.*.id) + + object_lock_enabled = "Enabled" + + rule { + default_retention { + mode = var.object_lock_configuration.mode + days = var.object_lock_configuration.days + years = var.object_lock_configuration.years } } +} + +resource "aws_s3_bucket_cors_configuration" "example" { + count = var.create_bucket && var.cors_rule != null ? 1 : 0 + + bucket = join("", aws_s3_bucket.s3_default.*.id) dynamic "cors_rule" { for_each = var.cors_rule == null ? [] : var.cors_rule @@ -101,66 +123,174 @@ resource "aws_s3_bucket" "s3_default" { max_age_seconds = cors_rule.value.max_age_seconds } } +} - lifecycle_rule { - id = "transition-to-infrequent-access-storage" - enabled = var.lifecycle_infrequent_storage_transition_enabled - prefix = var.lifecycle_infrequent_storage_object_prefix - tags = module.labels.tags +resource "aws_s3_bucket_website_configuration" "example" { + count = var.create_bucket && var.website_config_enable == true ? 1 : 0 - transition { - days = var.lifecycle_days_to_infrequent_storage_transition - storage_class = "STANDARD_IA" - } - } + bucket = join("", aws_s3_bucket.s3_default.*.id) - lifecycle_rule { - id = "transition-to-glacier" - enabled = var.lifecycle_glacier_transition_enabled - prefix = var.lifecycle_glacier_object_prefix - tags = module.labels.tags + index_document { + suffix = "index.html" + } + error_document { + key = "error.html" + } - transition { - days = var.lifecycle_days_to_glacier_transition - storage_class = "GLACIER" + routing_rule { + condition { + key_prefix_equals = "docs/" + } + redirect { + replace_key_prefix_with = "documents/" } } +} + +locals { + acl_grants = var.grants == null ? var.acl_grants : flatten( + [ + for g in var.grants : [ + for p in g.permissions : { + id = g.id + type = g.type + permission = p + uri = g.uri + } + ] + ]) +} + + +resource "aws_s3_bucket_acl" "default" { + + count = var.create_bucket ? var.grants != null ? var.acl != null ? 1 : 0 : 0 : 0 + bucket = join("", aws_s3_bucket.s3_default.*.id) - lifecycle_rule { - id = "transition-to-deep-archive" - enabled = var.lifecycle_deep_archive_transition_enabled - prefix = var.lifecycle_deep_archive_object_prefix - tags = module.labels.tags + acl = try(length(local.acl_grants), 0) == 0 ? var.acl : null - transition { - days = var.lifecycle_days_to_deep_archive_transition - storage_class = "DEEP_ARCHIVE" + dynamic "access_control_policy" { + for_each = try(length(local.acl_grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] + + content { + dynamic "grant" { + for_each = local.acl_grants + + content { + grantee { + id = grant.value.id + type = grant.value.type + uri = grant.value.uri + } + permission = grant.value.permission + } + } + + owner { + id = var.owner_id + } } } +} + + +resource "aws_s3_bucket_lifecycle_configuration" "default" { + count = var.create_bucket && var.enable_lifecycle_configuration_rules == true ? 1 : 0 + bucket = join("", aws_s3_bucket.s3_default.*.id) + + dynamic "rule" { + for_each = var.lifecycle_configuration_rules + + content { + id = rule.value.id + status = rule.value.enabled == true ? "Enabled" : "Disabled" + + # Filter is always required due to https://github.com/hashicorp/terraform-provider-aws/issues/23299 + filter { + dynamic "and" { + for_each = (try(length(rule.value.prefix), 0) + try(length(rule.value.tags), 0)) > 0 ? [1] : [] + content { + prefix = rule.value.prefix == null ? "" : rule.value.prefix + tags = try(length(rule.value.tags), 0) > 0 ? rule.value.tags : {} + } + } + } + + dynamic "abort_incomplete_multipart_upload" { + for_each = try(tonumber(rule.value.abort_incomplete_multipart_upload_days), null) != null ? [1] : [] + content { + days_after_initiation = rule.value.abort_incomplete_multipart_upload_days + } + } + + dynamic "noncurrent_version_expiration" { + for_each = rule.value.enable_noncurrent_version_expiration ? [1] : [] + + content { + noncurrent_days = rule.value.noncurrent_version_expiration_days + } + } - lifecycle_rule { - id = "expire-objects" - enabled = var.lifecycle_expiration_enabled - prefix = var.lifecycle_expiration_object_prefix - tags = module.labels.tags + dynamic "noncurrent_version_transition" { + for_each = rule.value.enable_glacier_transition ? [1] : [] + content { + noncurrent_days = rule.value.noncurrent_version_glacier_transition_days + storage_class = "GLACIER" + } + } + + dynamic "noncurrent_version_transition" { + for_each = rule.value.enable_deeparchive_transition ? [1] : [] + + content { + noncurrent_days = rule.value.noncurrent_version_deeparchive_transition_days + storage_class = "DEEP_ARCHIVE" + } + } + + dynamic "transition" { + for_each = rule.value.enable_glacier_transition ? [1] : [] + + content { + days = rule.value.glacier_transition_days + storage_class = "GLACIER" + } + } + + dynamic "transition" { + for_each = rule.value.enable_deeparchive_transition ? [1] : [] + + content { + days = rule.value.deeparchive_transition_days + storage_class = "DEEP_ARCHIVE" + } + } + + dynamic "transition" { + for_each = rule.value.enable_standard_ia_transition ? [1] : [] - expiration { - days = var.lifecycle_days_to_expiration + content { + days = rule.value.standard_transition_days + storage_class = "STANDARD_IA" + } + } + + dynamic "expiration" { + for_each = rule.value.enable_current_object_expiration ? [1] : [] + + content { + days = rule.value.expiration_days + } + } } } - tags = module.labels.tags - + depends_on = [ + # versioning must be set before lifecycle configuration + aws_s3_bucket_versioning.example + ] } -# Module : S3 BUCKET POLICY -# Description : Terraform module which creates policy for S3 bucket on AWS -resource "aws_s3_bucket_policy" "s3_default" { - # count = var.create_bucket && var.bucket_policy && var.bucket_enabled == true ? 1 : 0 - count = var.bucket_policy == true ? 1 : 0 - bucket = join("", aws_s3_bucket.s3_default.*.id) - policy = var.aws_iam_policy_document -} diff --git a/variables.tf b/variables.tf index ac63740..fee3e2c 100644 --- a/variables.tf +++ b/variables.tf @@ -74,18 +74,63 @@ variable "mfa_delete" { description = "Enable MFA delete for either Change the versioning state of your bucket or Permanently delete an object version." } +variable "enable_server_side_encryption" { + type = bool + default = false + description = "Enable enable_server_side_encryption" +} + variable "sse_algorithm" { type = string default = "AES256" description = "The server-side encryption algorithm to use. Valid values are AES256 and aws:kms." } +variable "enable_kms" { + type = bool + default = false + description = "Enable enable_server_side_encryption" +} + variable "kms_master_key_id" { type = string default = "" description = "The AWS KMS master key ID used for the SSE-KMS encryption. This can only be used when you set the value of sse_algorithm as aws:kms. The default aws/s3 AWS KMS master key is used if this element is absent while the sse_algorithm is aws:kms." } +variable "enable_lifecycle_configuration_rules" { + type = bool + default = false + description = "enable or disable lifecycle_configuration_rules" +} + +variable "lifecycle_configuration_rules" { + type = list(object({ + id = string + prefix = string + enabled = bool + tags = map(string) + + enable_glacier_transition = bool + enable_deeparchive_transition = bool + enable_standard_ia_transition = bool + enable_current_object_expiration = bool + enable_noncurrent_version_expiration = bool + + abort_incomplete_multipart_upload_days = number + noncurrent_version_glacier_transition_days = number + noncurrent_version_deeparchive_transition_days = number + noncurrent_version_expiration_days = number + + standard_transition_days = number + glacier_transition_days = number + deeparchive_transition_days = number + expiration_days = number + })) + default = null + description = "A list of lifecycle rules" +} + variable "lifecycle_infrequent_storage_transition_enabled" { type = bool default = false @@ -199,34 +244,85 @@ variable "grants" { description = "ACL Policy grant.conflict with acl.set acl null to use this" } -variable "website" { - type = map(string) - default = {} - description = "Static website configuration" +variable "acl_grants" { + type = list(object({ + id = string + type = string + permission = string + uri = string + })) + default = null + + description = "A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this." +} +variable "owner_id" { + type = string + default = "" + description = "The canonical user ID associated with the AWS account." +} + +variable "website_config_enable" { + type = bool + default = false + description = "enable or disable aws_s3_bucket_website_configuration" +} + +variable "index_document" { + type = string + default = "index.html" + description = "The name of the index document for the website" +} +variable "error_document" { + type = string + default = "error.html" + description = "he name of the error document for the website " +} +variable "routing_rule" { + type = string + default = "docs/" + description = "ist of rules that define when a redirect is applied and the redirect behavior " +} +variable "redirect" { + type = string + default = "documents/" + description = "The redirect behavior for every request to this bucket's website endpoint " } variable "logging" { - type = map(string) - default = {} - description = "Logging Object Configuration details" + type = bool + default = false + description = "Logging Object to enable and disable logging" } -variable "acceleration_status" { +variable "target_bucket" { type = string - default = null + default = "" + description = "The bucket where you want Amazon S3 to store server access logs." +} + +variable "target_prefix" { + type = string + default = "" + description = "A prefix for all log object keys." +} + +variable "acceleration_status" { + type = bool + default = false description = "Sets the accelerate configuration of an existing bucket. Can be Enabled or Suspended" } variable "request_payer" { - type = string - default = null + type = bool + default = false description = "Specifies who should bear the cost of Amazon S3 data transfer. Can be either BucketOwner or Requester. By default, the owner of the S3 bucket would incur the costs of any data transfer" } + variable "object_lock_configuration" { type = object({ - mode = string + mode = string #Valid values are GOVERNANCE and COMPLIANCE. days = number years = number })