From 137868373437b4847bed5e2edeb23df81fa9933e Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 13:22:37 -0600 Subject: [PATCH 01/24] Set pin to v3 for now --- versions.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.tf b/versions.tf index 749b0eb1..b032ca02 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.68.0" + version = "~> 3.68.0" } time = { source = "hashicorp/time" From caee4420710fb1c00c11df213a25254a4d96b0a6 Mon Sep 17 00:00:00 2001 From: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> Date: Thu, 10 Feb 2022 19:24:05 +0000 Subject: [PATCH 02/24] Auto Format --- README.md | 4 ++-- docs/terraform.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c3f40345..a6c6c7ae 100644 --- a/README.md +++ b/README.md @@ -213,14 +213,14 @@ Available targets: | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 3.68.0 | +| [aws](#requirement\_aws) | ~> 3.68.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.68.0 | +| [aws](#provider\_aws) | ~> 3.68.0 | | [time](#provider\_time) | >= 0.7 | ## Modules diff --git a/docs/terraform.md b/docs/terraform.md index c688ee51..efbb52e2 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -4,14 +4,14 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 3.68.0 | +| [aws](#requirement\_aws) | ~> 3.68.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.68.0 | +| [aws](#provider\_aws) | ~> 3.68.0 | | [time](#provider\_time) | >= 0.7 | ## Modules From 9e9a6fabcd0a5c36870406a4266f3d900a866b26 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 13:30:38 -0600 Subject: [PATCH 03/24] Create versions.tf --- examples/complete/versions.tf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/complete/versions.tf diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf new file mode 100644 index 00000000..b032ca02 --- /dev/null +++ b/examples/complete/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.68.0" + } + time = { + source = "hashicorp/time" + version = ">= 0.7" + } + } +} From 664a13d187dbcb734f7c219bcb927e0eaa89f816 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 14:34:31 -0600 Subject: [PATCH 04/24] Attempt to upgrade to aws v4 --- examples/complete/versions.tf | 2 +- main.tf | 330 ++++++++++++++++++++-------------- versions.tf | 2 +- 3 files changed, 198 insertions(+), 136 deletions(-) diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index b032ca02..749b0eb1 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.68.0" + version = ">= 3.68.0" } time = { source = "hashicorp/time" diff --git a/main.tf b/main.tf index c0e6ce25..430f6859 100644 --- a/main.tf +++ b/main.tf @@ -2,6 +2,7 @@ locals { enabled = module.this.enabled bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id replication_enabled = local.enabled && var.s3_replication_enabled + versioning_enabled = local.enabled && var.versioning_enabled bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}" # Deprecate `replication_rules` in favor of `s3_replication_rules` to keep all the replication related @@ -19,19 +20,133 @@ resource "aws_s3_bucket" "default" { #bridgecrew:skip=BC_AWS_GENERAL_56:Skipping `Ensure that S3 buckets are encrypted with KMS by default` because we do not have good defaults count = local.enabled ? 1 : 0 bucket = local.bucket_name - acl = try(length(var.grants), 0) == 0 ? var.acl : null force_destroy = var.force_destroy tags = module.this.tags - acceleration_status = var.transfer_acceleration_enabled ? "Enabled" : null - dynamic "versioning" { - for_each = var.versioning_enabled ? [true] : [] + dynamic "object_lock_configuration" { + for_each = var.object_lock_configuration != null ? [1] : [] + 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 + } + } + } + } +} + +resource "aws_s3_bucket_logging" "default" { + count = local.enabled && var.logging != null ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + target_bucket = var.logging["bucket_name"] + target_prefix = var.logging["prefix"] +} + +resource "aws_s3_bucket_website_configuration" "default" { + count = local.enabled && var.website_inputs != null ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + index_document { + suffix = lookup(var.website_inputs, "index_document") + } + + error_document { + key = lookup(var.website_inputs, "error_document") + } + + redirect_all_requests_to { + host_name = lookup(var.website_inputs, "redirect_all_requests_to") + protocol = lookup(var.website_inputs, "protocol") + } + + dynamic "routing_rule" { + for_each = length(jsondecode(lookup(var.website_inputs, "routing_rules"))) > 0 ? jsondecode(lookup(var.website_inputs, "routing_rules")) : [] + content { + dynamic "condition" { + for_each = routing_rule.value["Condition"] + + content { + condition { + key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") + } + } + } + + dynamic "redirect" { + for_each = routing_rule.value["Redirect"] + content { + replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") + } + } + } + } +} + +resource "aws_s3_bucket_acl" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + acl = try(length(var.grants), 0) == 0 ? var.acl : null + + access_control_policy { + dynamic "grant" { + for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants + + content { + grantee { + id = grant.value.id + type = grant.value.type + uri = grant.value.uri + } + permissions = grant.value.permissions + } + } + } +} + +# https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html +# https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption +resource "aws_s3_bucket_server_side_encryption_configuration" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + rule { + bucket_key_enabled = var.bucket_key_enabled + + apply_server_side_encryption_by_default { + sse_algorithm = var.sse_algorithm + kms_master_key_id = var.kms_master_key_arn + } + } +} + +resource "aws_s3_bucket_cors_configuration" "default" { + count = local.enabled ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) + + dynamic "cors_rule" { + for_each = var.cors_rule_inputs == null ? [] : var.cors_rule_inputs + content { - enabled = true + allowed_headers = cors_rule.value.allowed_headers + allowed_methods = cors_rule.value.allowed_methods + allowed_origins = cors_rule.value.allowed_origins + expose_headers = cors_rule.value.expose_headers + max_age_seconds = cors_rule.value.max_age_seconds } } +} + +resource "aws_s3_bucket_lifecycle_configuration" "bucket-config" { + count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) - dynamic "lifecycle_rule" { + dynamic "rule" { for_each = var.lifecycle_rules content { enabled = lifecycle_rule.value.enabled @@ -103,155 +218,102 @@ resource "aws_s3_bucket" "default" { } } } +} - dynamic "logging" { - for_each = var.logging == null ? [] : [1] - content { - target_bucket = var.logging["bucket_name"] - target_prefix = var.logging["prefix"] - } - } - - # https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html - # https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption - server_side_encryption_configuration { - rule { - bucket_key_enabled = var.bucket_key_enabled +resource "aws_s3_bucket_accelerate_configuration" "default" { + count = local.transfer_acceleration_enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + status = "Enabled" +} - apply_server_side_encryption_by_default { - sse_algorithm = var.sse_algorithm - kms_master_key_id = var.kms_master_key_arn - } - } - } +resource "aws_s3_bucket_versioning" "default" { + count = local.versioning_enabled ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) - dynamic "website" { - for_each = var.website_inputs == null ? [] : var.website_inputs - content { - index_document = website.value.index_document - error_document = website.value.error_document - redirect_all_requests_to = website.value.redirect_all_requests_to - routing_rules = website.value.routing_rules - } - } - - dynamic "cors_rule" { - for_each = var.cors_rule_inputs == null ? [] : var.cors_rule_inputs - - content { - allowed_headers = cors_rule.value.allowed_headers - allowed_methods = cors_rule.value.allowed_methods - allowed_origins = cors_rule.value.allowed_origins - expose_headers = cors_rule.value.expose_headers - max_age_seconds = cors_rule.value.max_age_seconds - } + versioning_configuration { + status = "Enabled" } +} - dynamic "grant" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants +resource "aws_s3_bucket_replication_configuration" "default" { + count = local.replication_enabled ? 1 : 0 - content { - id = grant.value.id - type = grant.value.type - permissions = grant.value.permissions - uri = grant.value.uri - } - } + bucket = join("", aws_s3_bucket.default.*.id) + role = aws_iam_role.replication[0].arn - dynamic "replication_configuration" { - for_each = local.replication_enabled ? [1] : [] + dynamic "rule" { + for_each = local.s3_replication_rules == null ? [] : local.s3_replication_rules content { - role = aws_iam_role.replication[0].arn + id = rule.value.id + priority = try(rule.value.priority, 0) + # `prefix` at this level is a V1 feature, replaced in V2 with the filter block. + # `prefix` conflicts with `filter`, and for multiple destinations, a filter block + # is required even if it empty, so we always implement `prefix` as a filter. + # OBSOLETE: prefix = try(rule.value.prefix, null) + status = try(rule.value.status, null) + # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on + delete_marker_replication_status = try(rule.value.delete_marker_replication_status, null) + + destination { + # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with + # s3_replica_bucket_arn to specify single destination for all rules + bucket = try(length(rule.value.destination_bucket), 0) > 0 ? rule.value.destination_bucket : var.s3_replica_bucket_arn + storage_class = try(rule.value.destination.storage_class, "STANDARD") + replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) + account_id = try(rule.value.destination.account_id, null) + + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-walkthrough-5.html + dynamic "metrics" { + for_each = try(rule.value.destination.metrics.status, "") == "Enabled" ? [1] : [] + + content { + status = "Enabled" + # Minutes can only have 15 as a valid value. + minutes = 15 + } + } - dynamic "rules" { - for_each = local.s3_replication_rules == null ? [] : local.s3_replication_rules + # This block is required when replication metrics are enabled. + dynamic "replication_time" { + for_each = try(rule.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - content { - id = rules.value.id - priority = try(rules.value.priority, 0) - # `prefix` at this level is a V1 feature, replaced in V2 with the filter block. - # `prefix` conflicts with `filter`, and for multiple destinations, a filter block - # is required even if it empty, so we always implement `prefix` as a filter. - # OBSOLETE: prefix = try(rules.value.prefix, null) - status = try(rules.value.status, null) - # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on - delete_marker_replication_status = try(rules.value.delete_marker_replication_status, null) - - destination { - # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with - # s3_replica_bucket_arn to specify single destination for all rules - bucket = try(length(rules.value.destination_bucket), 0) > 0 ? rules.value.destination_bucket : var.s3_replica_bucket_arn - storage_class = try(rules.value.destination.storage_class, "STANDARD") - replica_kms_key_id = try(rules.value.destination.replica_kms_key_id, null) - account_id = try(rules.value.destination.account_id, null) - - # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-walkthrough-5.html - dynamic "metrics" { - for_each = try(rules.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - - content { - status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 - } - } - - # This block is required when replication metrics are enabled. - dynamic "replication_time" { - for_each = try(rules.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - - content { - status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 - } - } - - dynamic "access_control_translation" { - for_each = try(rules.value.destination.access_control_translation.owner, null) == null ? [] : [rules.value.destination.access_control_translation.owner] - - content { - owner = access_control_translation.value - } - } + content { + status = "Enabled" + # Minutes can only have 15 as a valid value. + minutes = 15 } + } - dynamic "source_selection_criteria" { - for_each = try(rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled, null) == null ? [] : [rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled] + dynamic "access_control_translation" { + for_each = try(rule.value.destination.access_control_translation.owner, null) == null ? [] : [rule.value.destination.access_control_translation.owner] - content { - sse_kms_encrypted_objects { - enabled = source_selection_criteria.value - } - } + content { + owner = access_control_translation.value } + } + } - # Replication to multiple destination buckets requires that priority is specified in the rules object. - # If the corresponding rule requires no filter, an empty configuration block filter {} must be specified. - # See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket - dynamic "filter" { - for_each = try(rules.value.filter, null) == null ? [{ prefix = null, tags = {} }] : [rules.value.filter] + dynamic "source_selection_criteria" { + for_each = try(rule.value.source_selection_criteria.sse_kms_encrypted_objects.enabled, null) == null ? [] : [rule.value.source_selection_criteria.sse_kms_encrypted_objects.enabled] - content { - prefix = try(filter.value.prefix, try(rules.value.prefix, null)) - tags = try(filter.value.tags, {}) - } + content { + sse_kms_encrypted_objects { + enabled = source_selection_criteria.value } } } - } - } - dynamic "object_lock_configuration" { - for_each = var.object_lock_configuration != null ? [1] : [] - 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 + # Replication to multiple destination buckets requires that priority is specified in the rules object. + # If the corresponding rule requires no filter, an empty configuration block filter {} must be specified. + # See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket + dynamic "filter" { + for_each = try(rule.value.filter, null) == null ? [{ prefix = null, tags = {} }] : [rule.value.filter] + + content { + prefix = try(filter.value.prefix, try(rule.value.prefix, null)) + tags = try(filter.value.tags, {}) } } } diff --git a/versions.tf b/versions.tf index b032ca02..749b0eb1 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.68.0" + version = ">= 3.68.0" } time = { source = "hashicorp/time" From 1c615f0b6e26912377a873bcbc88709e3d50e5c1 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 14:34:49 -0600 Subject: [PATCH 05/24] Attempt to upgrade to aws v4 --- examples/complete/versions.tf | 2 +- versions.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 749b0eb1..a5c83197 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.68.0" + version = ">= 4.0.0" } time = { source = "hashicorp/time" diff --git a/versions.tf b/versions.tf index 749b0eb1..a5c83197 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.68.0" + version = ">= 4.0.0" } time = { source = "hashicorp/time" From 59b69f2e3b028fa4b30ee58f620ea82c51bf69c7 Mon Sep 17 00:00:00 2001 From: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> Date: Thu, 10 Feb 2022 20:35:50 +0000 Subject: [PATCH 06/24] Auto Format --- README.md | 13 +++++++++++-- docs/terraform.md | 13 +++++++++++-- main.tf | 30 +++++++++++++++--------------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a6c6c7ae..4173144d 100644 --- a/README.md +++ b/README.md @@ -213,14 +213,14 @@ Available targets: | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | ~> 3.68.0 | +| [aws](#requirement\_aws) | >= 4.0.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.68.0 | +| [aws](#provider\_aws) | >= 4.0.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -238,9 +238,18 @@ Available targets: | [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.bucket-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/docs/terraform.md b/docs/terraform.md index efbb52e2..d490c5e5 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -4,14 +4,14 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | ~> 3.68.0 | +| [aws](#requirement\_aws) | >= 4.0.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | ~> 3.68.0 | +| [aws](#provider\_aws) | >= 4.0.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -29,9 +29,18 @@ | [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.bucket-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/main.tf b/main.tf index 430f6859..bdb44787 100644 --- a/main.tf +++ b/main.tf @@ -18,10 +18,10 @@ resource "aws_s3_bucket" "default" { #bridgecrew:skip=BC_AWS_S3_16:Skipping `Ensure S3 bucket versioning is enabled` because dynamic blocks are not supported by checkov #bridgecrew:skip=BC_AWS_S3_14:Skipping `Ensure all data stored in the S3 bucket is securely encrypted at rest` because variables are not understood #bridgecrew:skip=BC_AWS_GENERAL_56:Skipping `Ensure that S3 buckets are encrypted with KMS by default` because we do not have good defaults - count = local.enabled ? 1 : 0 - bucket = local.bucket_name - force_destroy = var.force_destroy - tags = module.this.tags + count = local.enabled ? 1 : 0 + bucket = local.bucket_name + force_destroy = var.force_destroy + tags = module.this.tags dynamic "object_lock_configuration" { for_each = var.object_lock_configuration != null ? [1] : [] @@ -39,7 +39,7 @@ resource "aws_s3_bucket" "default" { } resource "aws_s3_bucket_logging" "default" { - count = local.enabled && var.logging != null ? 1 : 0 + count = local.enabled && var.logging != null ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) target_bucket = var.logging["bucket_name"] @@ -47,7 +47,7 @@ resource "aws_s3_bucket_logging" "default" { } resource "aws_s3_bucket_website_configuration" "default" { - count = local.enabled && var.website_inputs != null ? 1 : 0 + count = local.enabled && var.website_inputs != null ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) index_document { @@ -87,7 +87,7 @@ resource "aws_s3_bucket_website_configuration" "default" { } resource "aws_s3_bucket_acl" "default" { - count = local.enabled ? 1 : 0 + count = local.enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) acl = try(length(var.grants), 0) == 0 ? var.acl : null @@ -98,9 +98,9 @@ resource "aws_s3_bucket_acl" "default" { content { grantee { - id = grant.value.id - type = grant.value.type - uri = grant.value.uri + id = grant.value.id + type = grant.value.type + uri = grant.value.uri } permissions = grant.value.permissions } @@ -111,7 +111,7 @@ resource "aws_s3_bucket_acl" "default" { # https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html # https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption resource "aws_s3_bucket_server_side_encryption_configuration" "default" { - count = local.enabled ? 1 : 0 + count = local.enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) rule { @@ -143,7 +143,7 @@ resource "aws_s3_bucket_cors_configuration" "default" { } resource "aws_s3_bucket_lifecycle_configuration" "bucket-config" { - count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 + count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) dynamic "rule" { @@ -221,14 +221,14 @@ resource "aws_s3_bucket_lifecycle_configuration" "bucket-config" { } resource "aws_s3_bucket_accelerate_configuration" "default" { - count = local.transfer_acceleration_enabled ? 1 : 0 + count = local.transfer_acceleration_enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) status = "Enabled" } resource "aws_s3_bucket_versioning" "default" { count = local.versioning_enabled ? 1 : 0 - + bucket = join("", aws_s3_bucket.default.*.id) versioning_configuration { @@ -240,7 +240,7 @@ resource "aws_s3_bucket_replication_configuration" "default" { count = local.replication_enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) - role = aws_iam_role.replication[0].arn + role = aws_iam_role.replication[0].arn dynamic "rule" { for_each = local.s3_replication_rules == null ? [] : local.s3_replication_rules From 480233ba14c7d35e5b4095cbb12128af1d7eee1d Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 15:15:08 -0600 Subject: [PATCH 07/24] Plan works but website and lifecycle is a wip --- main.tf | 314 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 173 insertions(+), 141 deletions(-) diff --git a/main.tf b/main.tf index bdb44787..8b0fb6b2 100644 --- a/main.tf +++ b/main.tf @@ -1,9 +1,12 @@ locals { - enabled = module.this.enabled - bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id - replication_enabled = local.enabled && var.s3_replication_enabled - versioning_enabled = local.enabled && var.versioning_enabled - bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}" + enabled = module.this.enabled + + replication_enabled = local.enabled && var.s3_replication_enabled + versioning_enabled = local.enabled && var.versioning_enabled + transfer_acceleration_enabled = local.enabled && var.transfer_acceleration_enabled + + bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id + bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}" # Deprecate `replication_rules` in favor of `s3_replication_rules` to keep all the replication related # inputs grouped under s3_replica[tion] @@ -46,63 +49,36 @@ resource "aws_s3_bucket_logging" "default" { target_prefix = var.logging["prefix"] } -resource "aws_s3_bucket_website_configuration" "default" { - count = local.enabled && var.website_inputs != null ? 1 : 0 - bucket = join("", aws_s3_bucket.default.*.id) +data "aws_canonical_user_id" "default" { + count = local.enabled ? 1 : 0 +} - index_document { - suffix = lookup(var.website_inputs, "index_document") - } +resource "aws_s3_bucket_acl" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) - error_document { - key = lookup(var.website_inputs, "error_document") - } + # Conflicts with access_control_policy so this is enabled if no grants + acl = try(length(var.grants), 0) == 0 ? var.acl : null - redirect_all_requests_to { - host_name = lookup(var.website_inputs, "redirect_all_requests_to") - protocol = lookup(var.website_inputs, "protocol") - } + dynamic "access_control_policy" { + for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] - dynamic "routing_rule" { - for_each = length(jsondecode(lookup(var.website_inputs, "routing_rules"))) > 0 ? jsondecode(lookup(var.website_inputs, "routing_rules")) : [] content { - dynamic "condition" { - for_each = routing_rule.value["Condition"] + dynamic "grant" { + for_each = var.grants content { - condition { - key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") + grantee { + id = grant.value.id + type = grant.value.type + uri = grant.value.uri } + permission = grant.value.permission } } - dynamic "redirect" { - for_each = routing_rule.value["Redirect"] - content { - replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") - } - } - } - } -} - -resource "aws_s3_bucket_acl" "default" { - count = local.enabled ? 1 : 0 - bucket = join("", aws_s3_bucket.default.*.id) - - acl = try(length(var.grants), 0) == 0 ? var.acl : null - - access_control_policy { - dynamic "grant" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants - - content { - grantee { - id = grant.value.id - type = grant.value.type - uri = grant.value.uri - } - permissions = grant.value.permissions + owner { + id = join("", data.aws_canonical_user_id.default.*.id) } } } @@ -125,12 +101,12 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "default" { } resource "aws_s3_bucket_cors_configuration" "default" { - count = local.enabled ? 1 : 0 + count = local.enabled && var.cors_rule_inputs != null ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) dynamic "cors_rule" { - for_each = var.cors_rule_inputs == null ? [] : var.cors_rule_inputs + for_each = var.cors_rule_inputs content { allowed_headers = cors_rule.value.allowed_headers @@ -142,83 +118,122 @@ resource "aws_s3_bucket_cors_configuration" "default" { } } -resource "aws_s3_bucket_lifecycle_configuration" "bucket-config" { - count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 - bucket = join("", aws_s3_bucket.default.*.id) - - dynamic "rule" { - for_each = var.lifecycle_rules - content { - enabled = lifecycle_rule.value.enabled - prefix = lifecycle_rule.value.prefix - tags = lifecycle_rule.value.tags - abort_incomplete_multipart_upload_days = lifecycle_rule.value.abort_incomplete_multipart_upload_days - - dynamic "noncurrent_version_expiration" { - for_each = lifecycle_rule.value.enable_noncurrent_version_expiration ? [1] : [] - - content { - days = lifecycle_rule.value.noncurrent_version_expiration_days - } - } - - dynamic "noncurrent_version_transition" { - for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] - - content { - days = lifecycle_rule.value.noncurrent_version_glacier_transition_days - storage_class = "GLACIER" - } - } - - dynamic "noncurrent_version_transition" { - for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] - - content { - days = lifecycle_rule.value.noncurrent_version_deeparchive_transition_days - storage_class = "DEEP_ARCHIVE" - } - } - - dynamic "transition" { - for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] - - content { - days = lifecycle_rule.value.glacier_transition_days - storage_class = "GLACIER" - } - } - - dynamic "transition" { - for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] - - content { - days = lifecycle_rule.value.deeparchive_transition_days - storage_class = "DEEP_ARCHIVE" - } - } - - - - dynamic "transition" { - for_each = lifecycle_rule.value.enable_standard_ia_transition ? [1] : [] - - content { - days = lifecycle_rule.value.standard_transition_days - storage_class = "STANDARD_IA" - } - } - - dynamic "expiration" { - for_each = lifecycle_rule.value.enable_current_object_expiration ? [1] : [] - - content { - days = lifecycle_rule.value.expiration_days - } - } - } - } -} +# resource "aws_s3_bucket_website_configuration" "default" { +# for_each = local.enabled && var.website_inputs != null ? var.website_inputs : [] +# bucket = join("", aws_s3_bucket.default.*.id) + +# index_document { +# suffix = each.value.index_document +# } + +# error_document { +# key = each.value.error_document +# } + +# redirect_all_requests_to { +# host_name = each.value.redirect_all_requests_to +# protocol = each.value.protocol +# } + +# dynamic "routing_rule" { +# for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] +# content { +# dynamic "condition" { +# for_each = routing_rule.value["Condition"] + +# content { +# key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") +# } +# } + +# dynamic "redirect" { +# for_each = routing_rule.value["Redirect"] +# content { +# replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") +# } +# } +# } +# } +# } + +# resource "aws_s3_bucket_lifecycle_configuration" "default" { +# count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 +# bucket = join("", aws_s3_bucket.default.*.id) + +# dynamic "rule" { +# for_each = var.lifecycle_rules + +# content { +# enabled = lifecycle_rule.value.enabled +# prefix = lifecycle_rule.value.prefix +# tags = lifecycle_rule.value.tags +# abort_incomplete_multipart_upload_days = lifecycle_rule.value.abort_incomplete_multipart_upload_days + +# dynamic "noncurrent_version_expiration" { +# for_each = lifecycle_rule.value.enable_noncurrent_version_expiration ? [1] : [] + +# content { +# days = lifecycle_rule.value.noncurrent_version_expiration_days +# } +# } + +# dynamic "noncurrent_version_transition" { +# for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + +# content { +# days = lifecycle_rule.value.noncurrent_version_glacier_transition_days +# storage_class = "GLACIER" +# } +# } + +# dynamic "noncurrent_version_transition" { +# for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + +# content { +# days = lifecycle_rule.value.noncurrent_version_deeparchive_transition_days +# storage_class = "DEEP_ARCHIVE" +# } +# } + +# dynamic "transition" { +# for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + +# content { +# days = lifecycle_rule.value.glacier_transition_days +# storage_class = "GLACIER" +# } +# } + +# dynamic "transition" { +# for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + +# content { +# days = lifecycle_rule.value.deeparchive_transition_days +# storage_class = "DEEP_ARCHIVE" +# } +# } + + + +# dynamic "transition" { +# for_each = lifecycle_rule.value.enable_standard_ia_transition ? [1] : [] + +# content { +# days = lifecycle_rule.value.standard_transition_days +# storage_class = "STANDARD_IA" +# } +# } + +# dynamic "expiration" { +# for_each = lifecycle_rule.value.enable_current_object_expiration ? [1] : [] + +# content { +# days = lifecycle_rule.value.expiration_days +# } +# } +# } +# } +# } resource "aws_s3_bucket_accelerate_configuration" "default" { count = local.transfer_acceleration_enabled ? 1 : 0 @@ -254,15 +269,21 @@ resource "aws_s3_bucket_replication_configuration" "default" { # OBSOLETE: prefix = try(rule.value.prefix, null) status = try(rule.value.status, null) # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on - delete_marker_replication_status = try(rule.value.delete_marker_replication_status, null) + delete_marker_replication { + status = try(rule.value.delete_marker_replication_status, null) + } destination { # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with # s3_replica_bucket_arn to specify single destination for all rules - bucket = try(length(rule.value.destination_bucket), 0) > 0 ? rule.value.destination_bucket : var.s3_replica_bucket_arn - storage_class = try(rule.value.destination.storage_class, "STANDARD") - replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) - account_id = try(rule.value.destination.account_id, null) + bucket = try(length(rule.value.destination_bucket), 0) > 0 ? rule.value.destination_bucket : var.s3_replica_bucket_arn + storage_class = try(rule.value.destination.storage_class, "STANDARD") + + encryption_configuration { + replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) + } + + account = try(rule.value.destination.account_id, null) # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-walkthrough-5.html dynamic "metrics" { @@ -270,8 +291,10 @@ resource "aws_s3_bucket_replication_configuration" "default" { content { status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 + event_threshold { + # Minutes can only have 15 as a valid value. + minutes = 15 + } } } @@ -281,8 +304,10 @@ resource "aws_s3_bucket_replication_configuration" "default" { content { status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 + time { + # Minutes can only have 15 as a valid value. + minutes = 15 + } } } @@ -300,7 +325,7 @@ resource "aws_s3_bucket_replication_configuration" "default" { content { sse_kms_encrypted_objects { - enabled = source_selection_criteria.value + status = source_selection_criteria.value } } } @@ -313,7 +338,14 @@ resource "aws_s3_bucket_replication_configuration" "default" { content { prefix = try(filter.value.prefix, try(rule.value.prefix, null)) - tags = try(filter.value.tags, {}) + dynamic "tag" { + for_each = try(filter.value.tags, {}) + + content { + key = tag.key + value = tag.value + } + } } } } From fe98403772f5e803123f5018129920e260f0b590 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 15:18:09 -0600 Subject: [PATCH 08/24] Added website configuration --- main.tf | 62 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/main.tf b/main.tf index 8b0fb6b2..54e55cb5 100644 --- a/main.tf +++ b/main.tf @@ -118,43 +118,43 @@ resource "aws_s3_bucket_cors_configuration" "default" { } } -# resource "aws_s3_bucket_website_configuration" "default" { -# for_each = local.enabled && var.website_inputs != null ? var.website_inputs : [] -# bucket = join("", aws_s3_bucket.default.*.id) +resource "aws_s3_bucket_website_configuration" "default" { + for_each = local.enabled && var.website_inputs != null ? toset(var.website_inputs) : toset([]) + bucket = join("", aws_s3_bucket.default.*.id) -# index_document { -# suffix = each.value.index_document -# } + index_document { + suffix = each.value.index_document + } -# error_document { -# key = each.value.error_document -# } + error_document { + key = each.value.error_document + } -# redirect_all_requests_to { -# host_name = each.value.redirect_all_requests_to -# protocol = each.value.protocol -# } + redirect_all_requests_to { + host_name = each.value.redirect_all_requests_to + protocol = each.value.protocol + } -# dynamic "routing_rule" { -# for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] -# content { -# dynamic "condition" { -# for_each = routing_rule.value["Condition"] + dynamic "routing_rule" { + for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] + content { + dynamic "condition" { + for_each = routing_rule.value["Condition"] -# content { -# key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") -# } -# } + content { + key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") + } + } -# dynamic "redirect" { -# for_each = routing_rule.value["Redirect"] -# content { -# replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") -# } -# } -# } -# } -# } + dynamic "redirect" { + for_each = routing_rule.value["Redirect"] + content { + replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") + } + } + } + } +} # resource "aws_s3_bucket_lifecycle_configuration" "default" { # count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 From 4d911c3f03f1a8953155815825769365647d3200 Mon Sep 17 00:00:00 2001 From: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> Date: Thu, 10 Feb 2022 21:19:15 +0000 Subject: [PATCH 09/24] Auto Format --- README.md | 2 +- docs/terraform.md | 2 +- main.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4173144d..8d35c252 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,6 @@ Available targets: | [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | | [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | -| [aws_s3_bucket_lifecycle_configuration.bucket-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | @@ -251,6 +250,7 @@ Available targets: | [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_canonical_user_id.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/docs/terraform.md b/docs/terraform.md index d490c5e5..1f0a6072 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -32,7 +32,6 @@ | [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | | [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | -| [aws_s3_bucket_lifecycle_configuration.bucket-config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | @@ -42,6 +41,7 @@ | [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_canonical_user_id.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | diff --git a/main.tf b/main.tf index 54e55cb5..bc8a53ad 100644 --- a/main.tf +++ b/main.tf @@ -120,7 +120,7 @@ resource "aws_s3_bucket_cors_configuration" "default" { resource "aws_s3_bucket_website_configuration" "default" { for_each = local.enabled && var.website_inputs != null ? toset(var.website_inputs) : toset([]) - bucket = join("", aws_s3_bucket.default.*.id) + bucket = join("", aws_s3_bucket.default.*.id) index_document { suffix = each.value.index_document From 9267ad2919f46559abf73408476f540190f53a54 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 15:34:53 -0600 Subject: [PATCH 10/24] Added lifecycle --- examples/complete/lifecycle.us-east-2.tfvars | 2 + examples/complete/variables.tf | 4 +- main.tf | 134 ++++++++++--------- variables.tf | 2 + 4 files changed, 80 insertions(+), 62 deletions(-) diff --git a/examples/complete/lifecycle.us-east-2.tfvars b/examples/complete/lifecycle.us-east-2.tfvars index 00deda0f..4ede23d4 100644 --- a/examples/complete/lifecycle.us-east-2.tfvars +++ b/examples/complete/lifecycle.us-east-2.tfvars @@ -10,6 +10,7 @@ acl = "private" lifecycle_rules = [ { + id = "temp1" prefix = null enabled = true tags = { "temp" : "true" } @@ -31,6 +32,7 @@ lifecycle_rules = [ expiration_days = 1 }, { + id = "temp2" prefix = null enabled = true tags = {} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index f080d322..d8b11327 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -18,6 +18,7 @@ variable "grants" { variable "lifecycle_rules" { type = list(object({ + id = string enabled = bool prefix = string tags = map(string) @@ -39,6 +40,7 @@ variable "lifecycle_rules" { expiration_days = number })) default = [{ + id = "noop" enabled = false prefix = "" tags = {} @@ -256,4 +258,4 @@ variable "bucket_key_enabled" { For more information, see: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html EOT -} \ No newline at end of file +} diff --git a/main.tf b/main.tf index bc8a53ad..8198d2b6 100644 --- a/main.tf +++ b/main.tf @@ -156,84 +156,93 @@ resource "aws_s3_bucket_website_configuration" "default" { } } -# resource "aws_s3_bucket_lifecycle_configuration" "default" { -# count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 -# bucket = join("", aws_s3_bucket.default.*.id) +resource "aws_s3_bucket_lifecycle_configuration" "default" { + count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) -# dynamic "rule" { -# for_each = var.lifecycle_rules + dynamic "rule" { + for_each = var.lifecycle_rules -# content { -# enabled = lifecycle_rule.value.enabled -# prefix = lifecycle_rule.value.prefix -# tags = lifecycle_rule.value.tags -# abort_incomplete_multipart_upload_days = lifecycle_rule.value.abort_incomplete_multipart_upload_days + content { + id = rule.value.id + status = try(rule.value.enabled == true ? "Enabled" : "Disabled", rule.value.status) + prefix = rule.value.prefix -# dynamic "noncurrent_version_expiration" { -# for_each = lifecycle_rule.value.enable_noncurrent_version_expiration ? [1] : [] + filter { + and { + tags = rule.value.tags + } + } -# content { -# days = lifecycle_rule.value.noncurrent_version_expiration_days -# } -# } + abort_incomplete_multipart_upload { + days_after_initiation = rule.value.abort_incomplete_multipart_upload_days + } -# dynamic "noncurrent_version_transition" { -# for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + dynamic "noncurrent_version_expiration" { + for_each = rule.value.enable_noncurrent_version_expiration ? [1] : [] -# content { -# days = lifecycle_rule.value.noncurrent_version_glacier_transition_days -# storage_class = "GLACIER" -# } -# } + content { + noncurrent_days = rule.value.noncurrent_version_expiration_days + } + } -# dynamic "noncurrent_version_transition" { -# for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + dynamic "noncurrent_version_transition" { + for_each = rule.value.enable_glacier_transition ? [1] : [] -# content { -# days = lifecycle_rule.value.noncurrent_version_deeparchive_transition_days -# storage_class = "DEEP_ARCHIVE" -# } -# } + content { + noncurrent_days = rule.value.noncurrent_version_glacier_transition_days + storage_class = "GLACIER" + } + } -# dynamic "transition" { -# for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + dynamic "noncurrent_version_transition" { + for_each = rule.value.enable_deeparchive_transition ? [1] : [] -# content { -# days = lifecycle_rule.value.glacier_transition_days -# storage_class = "GLACIER" -# } -# } + content { + noncurrent_days = rule.value.noncurrent_version_deeparchive_transition_days + storage_class = "DEEP_ARCHIVE" + } + } -# dynamic "transition" { -# for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + dynamic "transition" { + for_each = rule.value.enable_glacier_transition ? [1] : [] -# content { -# days = lifecycle_rule.value.deeparchive_transition_days -# storage_class = "DEEP_ARCHIVE" -# } -# } + 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 = lifecycle_rule.value.enable_standard_ia_transition ? [1] : [] -# content { -# days = lifecycle_rule.value.standard_transition_days -# storage_class = "STANDARD_IA" -# } -# } -# dynamic "expiration" { -# for_each = lifecycle_rule.value.enable_current_object_expiration ? [1] : [] + dynamic "transition" { + for_each = rule.value.enable_standard_ia_transition ? [1] : [] -# content { -# days = lifecycle_rule.value.expiration_days -# } -# } -# } -# } -# } + 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 + } + } + } + } +} resource "aws_s3_bucket_accelerate_configuration" "default" { count = local.transfer_acceleration_enabled ? 1 : 0 @@ -487,7 +496,10 @@ data "aws_iam_policy_document" "bucket_policy" { } data "aws_iam_policy_document" "aggregated_policy" { - count = local.enabled ? 1 : 0 + count = local.enabled ? 1 : 0 + + # TODO: use source_policy_documents over var.policy + # source_policy_documents = var.source_policy_documents source_json = var.policy override_json = join("", data.aws_iam_policy_document.bucket_policy.*.json) } diff --git a/variables.tf b/variables.tf index 29b2e87d..eaa94ff2 100644 --- a/variables.tf +++ b/variables.tf @@ -81,6 +81,7 @@ variable "allow_ssl_requests_only" { variable "lifecycle_rules" { type = list(object({ + id = string prefix = string enabled = bool tags = map(string) @@ -102,6 +103,7 @@ variable "lifecycle_rules" { expiration_days = number })) default = [{ + id = "noop" enabled = false prefix = "" tags = {} From 8e2c4572bdc31b01843f5b87de7683ddc5ddd126 Mon Sep 17 00:00:00 2001 From: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> Date: Thu, 10 Feb 2022 21:35:46 +0000 Subject: [PATCH 11/24] Auto Format --- README.md | 3 ++- docs/terraform.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d35c252..4eaa1296 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,7 @@ Available targets: | [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | | [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | @@ -286,7 +287,7 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
list(object({
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 1f0a6072..dbb6c298 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -32,6 +32,7 @@ | [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | | [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | @@ -77,7 +78,7 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
list(object({
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | From 737b6a51f9b11a64549a2e48761c46cf9a7e3af1 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 15:41:37 -0600 Subject: [PATCH 12/24] Use the original order --- main.tf | 227 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 114 insertions(+), 113 deletions(-) diff --git a/main.tf b/main.tf index 8198d2b6..c1a47f6f 100644 --- a/main.tf +++ b/main.tf @@ -24,7 +24,6 @@ resource "aws_s3_bucket" "default" { count = local.enabled ? 1 : 0 bucket = local.bucket_name force_destroy = var.force_destroy - tags = module.this.tags dynamic "object_lock_configuration" { for_each = var.object_lock_configuration != null ? [1] : [] @@ -39,120 +38,23 @@ resource "aws_s3_bucket" "default" { } } } -} - -resource "aws_s3_bucket_logging" "default" { - count = local.enabled && var.logging != null ? 1 : 0 - bucket = join("", aws_s3_bucket.default.*.id) - target_bucket = var.logging["bucket_name"] - target_prefix = var.logging["prefix"] + tags = module.this.tags } -data "aws_canonical_user_id" "default" { - count = local.enabled ? 1 : 0 -} - -resource "aws_s3_bucket_acl" "default" { - count = local.enabled ? 1 : 0 - bucket = join("", aws_s3_bucket.default.*.id) - - # Conflicts with access_control_policy so this is enabled if no grants - acl = try(length(var.grants), 0) == 0 ? var.acl : null - - dynamic "access_control_policy" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] - - content { - dynamic "grant" { - for_each = var.grants - - content { - grantee { - id = grant.value.id - type = grant.value.type - uri = grant.value.uri - } - permission = grant.value.permission - } - } - - owner { - id = join("", data.aws_canonical_user_id.default.*.id) - } - } - } -} - -# https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html -# https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption -resource "aws_s3_bucket_server_side_encryption_configuration" "default" { - count = local.enabled ? 1 : 0 +resource "aws_s3_bucket_accelerate_configuration" "default" { + count = local.transfer_acceleration_enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) - - rule { - bucket_key_enabled = var.bucket_key_enabled - - apply_server_side_encryption_by_default { - sse_algorithm = var.sse_algorithm - kms_master_key_id = var.kms_master_key_arn - } - } + status = "Enabled" } -resource "aws_s3_bucket_cors_configuration" "default" { - count = local.enabled && var.cors_rule_inputs != null ? 1 : 0 +resource "aws_s3_bucket_versioning" "default" { + count = local.versioning_enabled ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) - dynamic "cors_rule" { - for_each = var.cors_rule_inputs - - content { - allowed_headers = cors_rule.value.allowed_headers - allowed_methods = cors_rule.value.allowed_methods - allowed_origins = cors_rule.value.allowed_origins - expose_headers = cors_rule.value.expose_headers - max_age_seconds = cors_rule.value.max_age_seconds - } - } -} - -resource "aws_s3_bucket_website_configuration" "default" { - for_each = local.enabled && var.website_inputs != null ? toset(var.website_inputs) : toset([]) - bucket = join("", aws_s3_bucket.default.*.id) - - index_document { - suffix = each.value.index_document - } - - error_document { - key = each.value.error_document - } - - redirect_all_requests_to { - host_name = each.value.redirect_all_requests_to - protocol = each.value.protocol - } - - dynamic "routing_rule" { - for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] - content { - dynamic "condition" { - for_each = routing_rule.value["Condition"] - - content { - key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") - } - } - - dynamic "redirect" { - for_each = routing_rule.value["Redirect"] - content { - replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") - } - } - } + versioning_configuration { + status = "Enabled" } } @@ -244,19 +146,118 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" { } } -resource "aws_s3_bucket_accelerate_configuration" "default" { - count = local.transfer_acceleration_enabled ? 1 : 0 +resource "aws_s3_bucket_logging" "default" { + count = local.enabled && var.logging != null ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) - status = "Enabled" + + target_bucket = var.logging["bucket_name"] + target_prefix = var.logging["prefix"] } -resource "aws_s3_bucket_versioning" "default" { - count = local.versioning_enabled ? 1 : 0 +# https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html +# https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption +resource "aws_s3_bucket_server_side_encryption_configuration" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + rule { + bucket_key_enabled = var.bucket_key_enabled + + apply_server_side_encryption_by_default { + sse_algorithm = var.sse_algorithm + kms_master_key_id = var.kms_master_key_arn + } + } +} + +resource "aws_s3_bucket_website_configuration" "default" { + for_each = local.enabled && var.website_inputs != null ? toset(var.website_inputs) : toset([]) + bucket = join("", aws_s3_bucket.default.*.id) + + index_document { + suffix = each.value.index_document + } + + error_document { + key = each.value.error_document + } + + redirect_all_requests_to { + host_name = each.value.redirect_all_requests_to + protocol = each.value.protocol + } + + dynamic "routing_rule" { + for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] + content { + dynamic "condition" { + for_each = routing_rule.value["Condition"] + + content { + key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") + } + } + + dynamic "redirect" { + for_each = routing_rule.value["Redirect"] + content { + replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") + } + } + } + } +} + +resource "aws_s3_bucket_cors_configuration" "default" { + count = local.enabled && var.cors_rule_inputs != null ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) - versioning_configuration { - status = "Enabled" + dynamic "cors_rule" { + for_each = var.cors_rule_inputs + + content { + allowed_headers = cors_rule.value.allowed_headers + allowed_methods = cors_rule.value.allowed_methods + allowed_origins = cors_rule.value.allowed_origins + expose_headers = cors_rule.value.expose_headers + max_age_seconds = cors_rule.value.max_age_seconds + } + } +} + +data "aws_canonical_user_id" "default" { + count = local.enabled ? 1 : 0 +} + +resource "aws_s3_bucket_acl" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + # Conflicts with access_control_policy so this is enabled if no grants + acl = try(length(var.grants), 0) == 0 ? var.acl : null + + dynamic "access_control_policy" { + for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] + + content { + dynamic "grant" { + for_each = var.grants + + content { + grantee { + id = grant.value.id + type = grant.value.type + uri = grant.value.uri + } + permission = grant.value.permission + } + } + + owner { + id = join("", data.aws_canonical_user_id.default.*.id) + } + } } } From ea261388f4f24699385203b1120b963a4665de41 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Thu, 10 Feb 2022 16:04:05 -0600 Subject: [PATCH 13/24] Add source_policy_documents input --- examples/complete/grants.us-east-2.tfvars | 14 ++++++--- examples/complete/variables.tf | 18 ++++++++---- main.tf | 35 ++++++++++++----------- variables.tf | 12 ++++++-- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/examples/complete/grants.us-east-2.tfvars b/examples/complete/grants.us-east-2.tfvars index ae61c275..d5a74002 100644 --- a/examples/complete/grants.us-east-2.tfvars +++ b/examples/complete/grants.us-east-2.tfvars @@ -10,10 +10,16 @@ acl = "" grants = [ { - id = null - type = "Group" - permissions = ["READ", "WRITE"] - uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + id = null + type = "Group" + permission = "READ" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + }, + { + id = null + type = "Group" + permission = "WRITE" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" }, ] diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index d8b11327..31e1662a 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -6,10 +6,10 @@ variable "acl" { variable "grants" { type = list(object({ - id = string - type = string - permissions = list(string) - uri = string + id = string + type = string + permission = string + uri = string })) default = null @@ -73,8 +73,14 @@ variable "s3_replication_enabled" { variable "policy" { type = string - default = "" - description = "A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" + default = null + description = "DEPRECATED: Use source_policy_documents instead. A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" +} + +variable "source_policy_documents" { + type = list(string) + default = null + description = "List of IAM policy documents that are merged together into the exported document. Statements defined in source_policy_documents or source_json must have unique sids. Statements with the same sid from documents assigned to the override_json and override_policy_documents arguments will override source statements." } variable "region" { diff --git a/main.tf b/main.tf index c1a47f6f..f8279f1d 100644 --- a/main.tf +++ b/main.tf @@ -25,20 +25,6 @@ resource "aws_s3_bucket" "default" { bucket = local.bucket_name force_destroy = var.force_destroy - dynamic "object_lock_configuration" { - for_each = var.object_lock_configuration != null ? [1] : [] - 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 - } - } - } - } - tags = module.this.tags } @@ -362,6 +348,22 @@ resource "aws_s3_bucket_replication_configuration" "default" { } } +resource "aws_s3_bucket_object_lock_configuration" "default" { + count = local.enabled && var.object_lock_configuration != null ? 1 : 0 + + bucket = join("", aws_s3_bucket.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 + } + } +} + module "s3_user" { source = "cloudposse/iam-s3-user/aws" version = "0.15.7" @@ -499,8 +501,9 @@ data "aws_iam_policy_document" "bucket_policy" { data "aws_iam_policy_document" "aggregated_policy" { count = local.enabled ? 1 : 0 - # TODO: use source_policy_documents over var.policy - # source_policy_documents = var.source_policy_documents + source_policy_documents = var.source_policy_documents + + # TODO: use source_policy_documents and deprecate var.policy source_json = var.policy override_json = join("", data.aws_iam_policy_document.bucket_policy.*.json) } diff --git a/variables.tf b/variables.tf index eaa94ff2..c3ae2501 100644 --- a/variables.tf +++ b/variables.tf @@ -8,7 +8,7 @@ variable "grants" { type = list(object({ id = string type = string - permissions = list(string) + permission = string uri = string })) default = null @@ -18,8 +18,14 @@ variable "grants" { variable "policy" { type = string - default = "" - description = "A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" + default = null + description = "DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" +} + +variable "source_policy_documents" { + type = list(string) + default = null + description = "List of IAM policy documents that are merged together into the exported document. Statements defined in source_policy_documents or source_json must have unique sids. Statements with the same sid from documents assigned to the override_json and override_policy_documents arguments will override source statements." } variable "force_destroy" { From e3946e652e3b6eefe429b6bb5c8eec8a0d56b642 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Fri, 11 Feb 2022 09:04:27 -0600 Subject: [PATCH 14/24] Add dynamics to replication rules --- main.tf | 19 +++++++++++++++---- variables.tf | 8 ++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/main.tf b/main.tf index f8279f1d..f456cb5f 100644 --- a/main.tf +++ b/main.tf @@ -259,24 +259,35 @@ resource "aws_s3_bucket_replication_configuration" "default" { content { id = rule.value.id priority = try(rule.value.priority, 0) + # `prefix` at this level is a V1 feature, replaced in V2 with the filter block. # `prefix` conflicts with `filter`, and for multiple destinations, a filter block # is required even if it empty, so we always implement `prefix` as a filter. # OBSOLETE: prefix = try(rule.value.prefix, null) status = try(rule.value.status, null) + # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on - delete_marker_replication { - status = try(rule.value.delete_marker_replication_status, null) + dynamic "delete_marker_replication" { + for_each = try(rule.value.delete_marker_replication_status, null) != null ? [1] : [] + + content { + status = try(rule.value.delete_marker_replication_status, null) + } } + # "rule.0.destination.0.encryption_configuration.0.replica_kms_key_id" is destination { # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with # s3_replica_bucket_arn to specify single destination for all rules bucket = try(length(rule.value.destination_bucket), 0) > 0 ? rule.value.destination_bucket : var.s3_replica_bucket_arn storage_class = try(rule.value.destination.storage_class, "STANDARD") - encryption_configuration { - replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) + dynamic "encryption_configuration" { + for_each = try(rule.value.destination.replica_kms_key_id, null) != null ? [1] : [] + + content { + replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) + } } account = try(rule.value.destination.account_id, null) diff --git a/variables.tf b/variables.tf index c3ae2501..11aad932 100644 --- a/variables.tf +++ b/variables.tf @@ -6,10 +6,10 @@ variable "acl" { variable "grants" { type = list(object({ - id = string - type = string - permission = string - uri = string + id = string + type = string + permission = string + uri = string })) default = null From b84ec5ec2afa169985f91ef32a8e7173d1b83d0d Mon Sep 17 00:00:00 2001 From: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com> Date: Fri, 11 Feb 2022 15:05:38 +0000 Subject: [PATCH 15/24] Auto Format --- README.md | 6 ++++-- docs/terraform.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4eaa1296..2466bf58 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,7 @@ Available targets: | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | @@ -279,7 +280,7 @@ Available targets: | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN 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` | `string` | `""` | no | @@ -292,7 +293,7 @@ Available targets: | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | +| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `null` | no | | [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no | | [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | @@ -303,6 +304,7 @@ Available targets: | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `null` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | diff --git a/docs/terraform.md b/docs/terraform.md index dbb6c298..889e7c53 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -34,6 +34,7 @@ | [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | @@ -70,7 +71,7 @@ | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN 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` | `string` | `""` | no | @@ -83,7 +84,7 @@ | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | +| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `null` | no | | [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no | | [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | @@ -94,6 +95,7 @@ | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `null` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | From ee7492e35e48f1e183c2cb3900f7b2caed61431b Mon Sep 17 00:00:00 2001 From: nitrocode Date: Fri, 11 Feb 2022 09:19:00 -0600 Subject: [PATCH 16/24] Add object lock enablement to s3 bucket --- main.tf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.tf b/main.tf index f456cb5f..08c1b5b5 100644 --- a/main.tf +++ b/main.tf @@ -25,6 +25,14 @@ resource "aws_s3_bucket" "default" { bucket = local.bucket_name force_destroy = var.force_destroy + dynamic "object_lock_configuration" { + for_each = var.object_lock_configuration != null ? [1] : [] + + content { + object_lock_enabled = true + } + } + tags = module.this.tags } From 98aa2665a6c0970a94da0b6cfecafb598f5c69f6 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Fri, 11 Feb 2022 09:36:10 -0600 Subject: [PATCH 17/24] Set delete market replication always --- main.tf | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/main.tf b/main.tf index 08c1b5b5..27ee3fba 100644 --- a/main.tf +++ b/main.tf @@ -274,16 +274,11 @@ resource "aws_s3_bucket_replication_configuration" "default" { # OBSOLETE: prefix = try(rule.value.prefix, null) status = try(rule.value.status, null) - # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on - dynamic "delete_marker_replication" { - for_each = try(rule.value.delete_marker_replication_status, null) != null ? [1] : [] - - content { - status = try(rule.value.delete_marker_replication_status, null) - } + # This is only relevant when "filter" is used + delete_marker_replication { + status = try(rule.value.delete_marker_replication_status, "Disabled") } - # "rule.0.destination.0.encryption_configuration.0.replica_kms_key_id" is destination { # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with # s3_replica_bucket_arn to specify single destination for all rules From 53324ecad0de9c7365bfa7850bdd61e44d67b0a3 Mon Sep 17 00:00:00 2001 From: nitrocode Date: Fri, 11 Feb 2022 09:47:45 -0600 Subject: [PATCH 18/24] Set object lock, added depends_on --- main.tf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 27ee3fba..26f941f9 100644 --- a/main.tf +++ b/main.tf @@ -29,7 +29,7 @@ resource "aws_s3_bucket" "default" { for_each = var.object_lock_configuration != null ? [1] : [] content { - object_lock_enabled = true + object_lock_enabled = "Enabled" } } @@ -360,6 +360,11 @@ resource "aws_s3_bucket_replication_configuration" "default" { } } } + + depends_on = [ + # replication must be set before versioning + aws_s3_bucket_versioning.default + ] } resource "aws_s3_bucket_object_lock_configuration" "default" { From f4d675faafa1a17611d7d965b1a4ecac195171df Mon Sep 17 00:00:00 2001 From: nitrocode Date: Fri, 11 Feb 2022 10:02:43 -0600 Subject: [PATCH 19/24] Set policy to empty string --- README.md | 2 +- docs/terraform.md | 2 +- examples/complete/variables.tf | 2 +- variables.tf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2466bf58..face9ca4 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ Available targets: | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `null` | no | +| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | | [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no | | [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 889e7c53..ce72e8d1 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -84,7 +84,7 @@ | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `null` | no | +| [policy](#input\_policy) | DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no | | [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no | | [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 31e1662a..034298cc 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -73,7 +73,7 @@ variable "s3_replication_enabled" { variable "policy" { type = string - default = null + default = "" description = "DEPRECATED: Use source_policy_documents instead. A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" } diff --git a/variables.tf b/variables.tf index 11aad932..f0f4777b 100644 --- a/variables.tf +++ b/variables.tf @@ -18,7 +18,7 @@ variable "grants" { variable "policy" { type = string - default = null + default = "" description = "DEPRECATED: A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy" } From efb994629a49b9a56058d1058f167ed7740fa927 Mon Sep 17 00:00:00 2001 From: Nuru Date: Tue, 15 Feb 2022 16:40:37 -0800 Subject: [PATCH 20/24] Update validate-codeowners.yml --- .github/workflows/validate-codeowners.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml index c5193b62..c99204e5 100644 --- a/.github/workflows/validate-codeowners.yml +++ b/.github/workflows/validate-codeowners.yml @@ -10,7 +10,7 @@ jobs: steps: - name: "Checkout source code at current commit" uses: actions/checkout@v2 - - uses: mszostok/codeowners-validator@v0.5.0 + - uses: mszostok/codeowners-validator@v0.7.0 if: github.event.pull_request.head.repo.full_name == github.repository name: "Full check of CODEOWNERS" with: @@ -18,10 +18,12 @@ jobs: # files so we can use the same CODEOWNERS file for Terraform and non-Terraform repos # checks: "files,syntax,owners,duppatterns" checks: "syntax,owners,duppatterns" + owner_checker_allow_unowned_patterns: "false" # GitHub access token is required only if the `owners` check is enabled github_access_token: "${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}" - - uses: mszostok/codeowners-validator@v0.5.0 + - uses: mszostok/codeowners-validator@v0.7.0 if: github.event.pull_request.head.repo.full_name != github.repository name: "Syntax check of CODEOWNERS" with: checks: "syntax,duppatterns" + owner_checker_allow_unowned_patterns: "false" From 492c82b4718c00a004d1593a4f842e1593c9bd00 Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 21 Feb 2022 03:07:25 -0800 Subject: [PATCH 21/24] Make backwards compatible --- .github/workflows/auto-readme.yml | 55 ++++++++++++++ .github/workflows/validate-codeowners.yml | 4 +- Makefile | 2 +- README.md | 14 ++-- docs/terraform.md | 14 ++-- examples/complete/grants.us-east-2.tfvars | 14 +--- examples/complete/lifecycle.us-east-2.tfvars | 2 - examples/complete/variables.tf | 14 ++-- main.tf | 32 ++++---- test/Makefile | 4 +- test/src/Makefile | 11 +-- test/src/examples_complete_test.go | 37 +++++----- variables-deprecated.tf | 78 ++++++++++++++++++++ variables.tf | 8 +- versions.tf | 4 +- 15 files changed, 208 insertions(+), 85 deletions(-) create mode 100644 .github/workflows/auto-readme.yml create mode 100644 variables-deprecated.tf diff --git a/.github/workflows/auto-readme.yml b/.github/workflows/auto-readme.yml new file mode 100644 index 00000000..03232cd6 --- /dev/null +++ b/.github/workflows/auto-readme.yml @@ -0,0 +1,55 @@ +name: "auto-readme" +on: + schedule: + # Example of job definition: + # .---------------- minute (0 - 59) + # | .------------- hour (0 - 23) + # | | .---------- day of month (1 - 31) + # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... + # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat + # | | | | | + # * * * * * user-name command to be executed + + # Update README.md nightly at 4am UTC + - cron: '0 4 * * *' + +jobs: + update: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Update readme + shell: bash + id: update + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + make init + make readme/build + # Ignore changes if they are only whitespace + git diff --ignore-all-space --ignore-blank-lines --quiet README.md && { git restore README.md; echo Ignoring whitespace-only changes in README; } + + - name: Create Pull Request + # This action will not create or change a pull request if there are no changes to make. + # If a PR of the auto-update/readme branch is open, this action will just update it, not create a new PR. + uses: cloudposse/actions/github/create-pull-request@0.30.0 + with: + token: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} + commit-message: Update README.md and docs + title: Update README.md and docs + body: |- + ## what + This is an auto-generated PR that updates the README.md and docs + + ## why + To have most recent changes of README.md and doc from origin templates + + branch: auto-update/readme + base: main + delete-branch: true + labels: | + auto-update + no-release + readme diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml index c99204e5..70f829e3 100644 --- a/.github/workflows/validate-codeowners.yml +++ b/.github/workflows/validate-codeowners.yml @@ -10,7 +10,7 @@ jobs: steps: - name: "Checkout source code at current commit" uses: actions/checkout@v2 - - uses: mszostok/codeowners-validator@v0.7.0 + - uses: mszostok/codeowners-validator@v0.7.1 if: github.event.pull_request.head.repo.full_name == github.repository name: "Full check of CODEOWNERS" with: @@ -21,7 +21,7 @@ jobs: owner_checker_allow_unowned_patterns: "false" # GitHub access token is required only if the `owners` check is enabled github_access_token: "${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}" - - uses: mszostok/codeowners-validator@v0.7.0 + - uses: mszostok/codeowners-validator@v0.7.1 if: github.event.pull_request.head.repo.full_name != github.repository name: "Syntax check of CODEOWNERS" with: diff --git a/Makefile b/Makefile index 6002cb92..b9920609 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SHELL := /bin/bash -export TERRAFORM_VERSION = 0.12.3 +export TERRAFORM_VERSION = 1.1.6 # List of targets the `readme` target should call before generating the readme export README_DEPS ?= docs/targets.md docs/terraform.md diff --git a/README.md b/README.md index face9ca4..80bb3d2c 100644 --- a/README.md +++ b/README.md @@ -212,15 +212,15 @@ Available targets: | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 4.0.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.2.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.0.0 | +| [aws](#provider\_aws) | >= 4.2.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -264,6 +264,7 @@ Available targets: | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [acl](#input\_acl) | The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`. | `string` | `"private"` | no | +| [acl\_grants](#input\_acl\_grants) | A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [allow\_encrypted\_uploads\_only](#input\_allow\_encrypted\_uploads\_only) | Set to `true` to prevent uploads of unencrypted objects to S3 bucket | `bool` | `false` | no | | [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `false` | no | @@ -280,7 +281,7 @@ Available targets: | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN 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` | `string` | `""` | no | @@ -288,7 +289,8 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_configuration\_rules](#input\_lifecycle\_configuration\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | DEPRECATED: A list of lifecycle rules |
list(object({
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
}))
| `null` | no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | @@ -304,7 +306,7 @@ Available targets: | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | -| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `null` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | diff --git a/docs/terraform.md b/docs/terraform.md index ce72e8d1..63d2ea47 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -3,15 +3,15 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 4.0.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.2.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.0.0 | +| [aws](#provider\_aws) | >= 4.2.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -55,6 +55,7 @@ | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [acl](#input\_acl) | The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`. | `string` | `"private"` | no | +| [acl\_grants](#input\_acl\_grants) | A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [allow\_encrypted\_uploads\_only](#input\_allow\_encrypted\_uploads\_only) | Set to `true` to prevent uploads of unencrypted objects to S3 bucket | `bool` | `false` | no | | [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `false` | no | @@ -71,7 +72,7 @@ | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN 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` | `string` | `""` | no | @@ -79,7 +80,8 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_configuration\_rules](#input\_lifecycle\_configuration\_rules) | A list of lifecycle rules |
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
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | DEPRECATED: A list of lifecycle rules |
list(object({
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
}))
| `null` | no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | @@ -95,7 +97,7 @@ | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | -| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `null` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | diff --git a/examples/complete/grants.us-east-2.tfvars b/examples/complete/grants.us-east-2.tfvars index d5a74002..ae61c275 100644 --- a/examples/complete/grants.us-east-2.tfvars +++ b/examples/complete/grants.us-east-2.tfvars @@ -10,16 +10,10 @@ acl = "" grants = [ { - id = null - type = "Group" - permission = "READ" - uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" - }, - { - id = null - type = "Group" - permission = "WRITE" - uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + id = null + type = "Group" + permissions = ["READ", "WRITE"] + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" }, ] diff --git a/examples/complete/lifecycle.us-east-2.tfvars b/examples/complete/lifecycle.us-east-2.tfvars index 4ede23d4..00deda0f 100644 --- a/examples/complete/lifecycle.us-east-2.tfvars +++ b/examples/complete/lifecycle.us-east-2.tfvars @@ -10,7 +10,6 @@ acl = "private" lifecycle_rules = [ { - id = "temp1" prefix = null enabled = true tags = { "temp" : "true" } @@ -32,7 +31,6 @@ lifecycle_rules = [ expiration_days = 1 }, { - id = "temp2" prefix = null enabled = true tags = {} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index 034298cc..3520e45f 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -6,10 +6,10 @@ variable "acl" { variable "grants" { type = list(object({ - id = string - type = string - permission = string - uri = string + id = string + type = string + permissions = list(string) + uri = string })) default = null @@ -18,9 +18,8 @@ variable "grants" { variable "lifecycle_rules" { type = list(object({ - id = string - enabled = bool prefix = string + enabled = bool tags = map(string) enable_glacier_transition = bool @@ -40,9 +39,8 @@ variable "lifecycle_rules" { expiration_days = number })) default = [{ - id = "noop" - enabled = false prefix = "" + enabled = false tags = {} enable_glacier_transition = true diff --git a/main.tf b/main.tf index 26f941f9..28e35a11 100644 --- a/main.tf +++ b/main.tf @@ -53,23 +53,28 @@ resource "aws_s3_bucket_versioning" "default" { } resource "aws_s3_bucket_lifecycle_configuration" "default" { - count = local.enabled && length(var.lifecycle_rules) > 0 ? 1 : 0 + count = local.enabled && length(local.lifecycle_configuration_rules) > 0 ? 1 : 0 bucket = join("", aws_s3_bucket.default.*.id) dynamic "rule" { - for_each = var.lifecycle_rules + for_each = local.lifecycle_configuration_rules content { id = rule.value.id status = try(rule.value.enabled == true ? "Enabled" : "Disabled", rule.value.status) - prefix = rule.value.prefix - filter { - and { - tags = rule.value.tags + dynamic "filter" { + for_each = (try(length(rule.value.prefix), 0) + try(length(rule.value.tags), 0)) > 0 ? [1] : [] + content { + prefix = rule.value.prefix + dynamic "and" { + for_each = try(length(rule.value.tags), 0) > 0 ? [1] : [] + content { + tags = rule.value.tags + } + } } } - abort_incomplete_multipart_upload { days_after_initiation = rule.value.abort_incomplete_multipart_upload_days } @@ -229,14 +234,14 @@ resource "aws_s3_bucket_acl" "default" { bucket = join("", aws_s3_bucket.default.*.id) # Conflicts with access_control_policy so this is enabled if no grants - acl = try(length(var.grants), 0) == 0 ? var.acl : null + acl = try(length(local.acl_grants), 0) == 0 ? var.acl : null dynamic "access_control_policy" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] + for_each = try(length(local.acl_grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] content { dynamic "grant" { - for_each = var.grants + for_each = local.acl_grants content { grantee { @@ -520,11 +525,8 @@ data "aws_iam_policy_document" "bucket_policy" { data "aws_iam_policy_document" "aggregated_policy" { count = local.enabled ? 1 : 0 - source_policy_documents = var.source_policy_documents - - # TODO: use source_policy_documents and deprecate var.policy - source_json = var.policy - override_json = join("", data.aws_iam_policy_document.bucket_policy.*.json) + source_policy_documents = compact(concat([var.policy], var.source_policy_documents)) + override_policy_documents = data.aws_iam_policy_document.bucket_policy.*.json } resource "aws_s3_bucket_policy" "default" { diff --git a/test/Makefile b/test/Makefile index 17b2fe74..095a5b7c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -33,11 +33,11 @@ clean: all: module examples/complete ## Run basic sanity checks against the module itself -module: export TESTS ?= installed lint get-modules module-pinning get-plugins provider-pinning validate terraform-docs input-descriptions output-descriptions +module: export TESTS ?= installed lint module-pinning provider-pinning validate terraform-docs input-descriptions output-descriptions module: deps $(call RUN_TESTS, ../) ## Run tests against example -examples/complete: export TESTS ?= installed lint get-modules get-plugins validate +examples/complete: export TESTS ?= installed lint validate examples/complete: deps $(call RUN_TESTS, ../$@) diff --git a/test/src/Makefile b/test/src/Makefile index dd458292..d7ae5975 100644 --- a/test/src/Makefile +++ b/test/src/Makefile @@ -1,4 +1,4 @@ -export TF_CLI_ARGS_init ?= -get-plugins=true +export TERRAFORM_VERSION ?= $(shell curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r -M '.current_version' | cut -d. -f1) .DEFAULT_GOAL : all @@ -14,16 +14,13 @@ init: .PHONY : test ## Run tests test: init - # Verify we have valid AWS credential - if command -v aws >/dev/null; then \ - (unset AWS_PROFILE; aws sts get-caller-identity >/dev/null); \ - fi - go test -v -timeout 10m -run TestExamplesComplete + go mod download + go test -v -timeout 15m -run TestExamplesComplete ## Run tests in docker container docker/test: docker run --name terratest --rm -it -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e GITHUB_TOKEN \ - -e PATH="/usr/local/terraform/0.13/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ + -e PATH="/usr/local/terraform/$(TERRAFORM_VERSION)/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ -v $(CURDIR)/../../:/module/ cloudposse/test-harness:latest -C /module/test/src test .PHONY : clean diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index fc8d354f..0d843d90 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -3,24 +3,21 @@ package test import ( "bytes" "encoding/json" - "math/rand" - "strconv" - "strings" - "testing" - "time" - "github.com/gruntwork-io/terratest/modules/aws" + "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" test_structure "github.com/gruntwork-io/terratest/modules/test-structure" "github.com/stretchr/testify/assert" + "strings" + "testing" ) // Test the Terraform module in examples/complete using Terratest. func TestExamplesComplete(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano()) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"fixtures.us-east-2.tfvars"} @@ -62,9 +59,9 @@ func TestExamplesComplete(t *testing.T) { // Test the Terraform module in examples/complete using Terratest for grants. func TestExamplesCompleteWithGrants(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 1) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"grants.us-east-2.tfvars"} @@ -99,9 +96,9 @@ func TestExamplesCompleteWithGrants(t *testing.T) { // Test the Terraform module in examples/complete using Terratest for grants. func TestExamplesCompleteWithObjectLock(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 2) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"object-lock.us-east-2.tfvars"} @@ -134,9 +131,9 @@ func TestExamplesCompleteWithObjectLock(t *testing.T) { func TestExamplesCompleteWithLifecycleRules(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 3) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"lifecycle.us-east-2.tfvars"} @@ -170,9 +167,9 @@ func TestExamplesCompleteWithLifecycleRules(t *testing.T) { func TestExamplesCompleteWithReplication(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 4) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"replication.us-east-2.tfvars"} @@ -220,10 +217,10 @@ func TestExamplesCompleteWithReplication(t *testing.T) { func TestExamplesCompleteWithPrivilegedPrincipals(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 5) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} awsRegion := "us-east-2" - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"privileged-principals.us-east-2.tfvars"} @@ -328,9 +325,9 @@ func TestExamplesCompleteWithPrivilegedPrincipals(t *testing.T) { func TestExamplesCompleteDisabled(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 6) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"replication.us-east-2.tfvars"} diff --git a/variables-deprecated.tf b/variables-deprecated.tf new file mode 100644 index 00000000..71a0772a --- /dev/null +++ b/variables-deprecated.tf @@ -0,0 +1,78 @@ +variable "grants" { + type = list(object({ + id = string + type = string + permissions = list(string) + uri = string + })) + default = null + + description = "DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this." +} + +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 + } + ] + ]) +} + +variable "lifecycle_rules" { + type = list(object({ + 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 = "DEPRECATED: A list of lifecycle rules" +} + +locals { + lifecycle_configuration_rules = var.lifecycle_rules == null ? var.lifecycle_configuration_rules : ( + [for i, v in var.lifecycle_rules : { + id = "rule-${i + 1}" + prefix = v.prefix + enabled = v.enabled + tags = v.tags + + enable_glacier_transition = v.enable_glacier_transition + enable_deeparchive_transition = v.enable_deeparchive_transition + enable_standard_ia_transition = v.enable_standard_ia_transition + enable_current_object_expiration = v.enable_current_object_expiration + enable_noncurrent_version_expiration = v.enable_noncurrent_version_expiration + + abort_incomplete_multipart_upload_days = v.abort_incomplete_multipart_upload_days + noncurrent_version_glacier_transition_days = v.noncurrent_version_glacier_transition_days + noncurrent_version_deeparchive_transition_days = v.noncurrent_version_deeparchive_transition_days + noncurrent_version_expiration_days = v.noncurrent_version_expiration_days + + standard_transition_days = v.standard_transition_days + glacier_transition_days = v.glacier_transition_days + deeparchive_transition_days = v.deeparchive_transition_days + expiration_days = v.expiration_days + }] + ) +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index f0f4777b..4a6fde88 100644 --- a/variables.tf +++ b/variables.tf @@ -4,7 +4,7 @@ variable "acl" { description = "The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`." } -variable "grants" { +variable "acl_grants" { type = list(object({ id = string type = string @@ -13,7 +13,7 @@ variable "grants" { })) default = null - description = "An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this." + description = "A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this." } variable "policy" { @@ -24,7 +24,7 @@ variable "policy" { variable "source_policy_documents" { type = list(string) - default = null + default = [] description = "List of IAM policy documents that are merged together into the exported document. Statements defined in source_policy_documents or source_json must have unique sids. Statements with the same sid from documents assigned to the override_json and override_policy_documents arguments will override source statements." } @@ -85,7 +85,7 @@ variable "allow_ssl_requests_only" { description = "Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests" } -variable "lifecycle_rules" { +variable "lifecycle_configuration_rules" { type = list(object({ id = string prefix = string diff --git a/versions.tf b/versions.tf index a5c83197..87da5770 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.0" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.0.0" + version = ">= 4.2.0" } time = { source = "hashicorp/time" From c7592fc3ea741ce4d39f6a9fd4a33e87f33c313c Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 21 Feb 2022 16:45:17 -0800 Subject: [PATCH 22/24] Ignore backup files --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ce573e9d..41a3239b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ # Build harness files .build-harness build-harness + +# Editor and merge files +*.orig +*.draft +*~ From 550e49b4d6befe0691fc8be7fd05e34eade700f4 Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 21 Feb 2022 16:46:01 -0800 Subject: [PATCH 23/24] lifecycle filter must be static, abort... must by dynamic. --- main.tf | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/main.tf b/main.tf index 28e35a11..c0d55aaf 100644 --- a/main.tf +++ b/main.tf @@ -63,20 +63,22 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" { id = rule.value.id status = try(rule.value.enabled == true ? "Enabled" : "Disabled", rule.value.status) - dynamic "filter" { - for_each = (try(length(rule.value.prefix), 0) + try(length(rule.value.tags), 0)) > 0 ? [1] : [] - content { - prefix = rule.value.prefix - dynamic "and" { - for_each = try(length(rule.value.tags), 0) > 0 ? [1] : [] - content { - tags = rule.value.tags - } + # 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 : {} } } } - abort_incomplete_multipart_upload { - days_after_initiation = rule.value.abort_incomplete_multipart_upload_days + + 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" { @@ -123,8 +125,6 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" { } } - - dynamic "transition" { for_each = rule.value.enable_standard_ia_transition ? [1] : [] @@ -143,6 +143,11 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" { } } } + + depends_on = [ + # versioning must be set before lifecycle configuration + aws_s3_bucket_versioning.default + ] } resource "aws_s3_bucket_logging" "default" { @@ -367,7 +372,7 @@ resource "aws_s3_bucket_replication_configuration" "default" { } depends_on = [ - # replication must be set before versioning + # versioning must be set before replication aws_s3_bucket_versioning.default ] } From b3babb48a559795ffd598bfdc723f3b3cb4eee5b Mon Sep 17 00:00:00 2001 From: Nuru Date: Mon, 21 Feb 2022 16:55:14 -0800 Subject: [PATCH 24/24] Do not reference non-existent object member --- main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.tf b/main.tf index c0d55aaf..7c890fa6 100644 --- a/main.tf +++ b/main.tf @@ -21,6 +21,7 @@ resource "aws_s3_bucket" "default" { #bridgecrew:skip=BC_AWS_S3_16:Skipping `Ensure S3 bucket versioning is enabled` because dynamic blocks are not supported by checkov #bridgecrew:skip=BC_AWS_S3_14:Skipping `Ensure all data stored in the S3 bucket is securely encrypted at rest` because variables are not understood #bridgecrew:skip=BC_AWS_GENERAL_56:Skipping `Ensure that S3 buckets are encrypted with KMS by default` because we do not have good defaults + #bridgecrew:skip=BC_AWS_GENERAL_72:We do not agree that cross-region replication must be enabled count = local.enabled ? 1 : 0 bucket = local.bucket_name force_destroy = var.force_destroy @@ -61,7 +62,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" { content { id = rule.value.id - status = try(rule.value.enabled == true ? "Enabled" : "Disabled", rule.value.status) + status = rule.value.enabled == true ? "Enabled" : "Disabled" # Filter is always required due to https://github.com/hashicorp/terraform-provider-aws/issues/23299 filter {