diff --git a/.secrets.baseline b/.secrets.baseline index fb7b246d..9cf5924f 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-03-25T14:13:22Z", + "generated_at": "2024-04-18T15:36:29Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -90,7 +90,7 @@ "hashed_secret": "a7c93faaa770c377154ea9d4d0d17a9056dbfa95", "is_secret": false, "is_verified": false, - "line_number": 191, + "line_number": 195, "type": "Secret Keyword", "verified_result": null } diff --git a/README.md b/README.md index e0cfa24b..19669114 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,7 @@ You need the following permissions to run this module. |------|------| | [ibm_cos_bucket.cos_bucket](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/cos_bucket) | resource | | [ibm_cos_bucket.cos_bucket1](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/cos_bucket) | resource | +| [ibm_cos_bucket_object_lock_configuration.lock_configuration](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/cos_bucket_object_lock_configuration) | resource | | [ibm_iam_authorization_policy.policy](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_resource_instance.cos_instance](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/resource_instance) | resource | | [ibm_resource_key.resource_keys](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/resource_key) | resource | @@ -185,6 +186,9 @@ You need the following permissions to run this module. | [kms\_encryption\_enabled](#input\_kms\_encryption\_enabled) | Set as true to use KMS key encryption to encrypt data in COS bucket (only applicable when var.create\_cos\_bucket is true). | `bool` | `true` | no | | [kms\_key\_crn](#input\_kms\_key\_crn) | CRN of the KMS key to use to encrypt the data in the COS bucket. Required if var.encryption\_enabled and var.create\_cos\_bucket are true. | `string` | `null` | no | | [management\_endpoint\_type\_for\_bucket](#input\_management\_endpoint\_type\_for\_bucket) | The type of endpoint for the IBM terraform provider to use to manage the bucket. (public, private or direct) | `string` | `"public"` | no | +| [object\_lock\_duration\_days](#input\_object\_lock\_duration\_days) | Specifies the default number of days for the retention lock duration. When setting 'object\_lock\_duration\_days' do not set 'object\_lock\_duration\_years'. Only used if 'create\_cos\_bucket' is true. | `number` | `0` | no | +| [object\_lock\_duration\_years](#input\_object\_lock\_duration\_years) | Specifies the default number of years for the retention lock duration. When setting 'object\_lock\_duration\_years' do not set 'object\_lock\_duration\_days'. Only used if 'create\_cos\_bucket' is true. | `number` | `0` | no | +| [object\_locking\_enabled](#input\_object\_locking\_enabled) | Specifies if an object lock configuration should be created. Requires 'object\_versoning\_enabled' to be true. Only used if 'create\_cos\_bucket' is true. | `bool` | `false` | no | | [object\_versioning\_enabled](#input\_object\_versioning\_enabled) | Enable object versioning to keep multiple versions of an object in a bucket. Cannot be used with retention rule. Only used if 'create\_cos\_bucket' is true. | `bool` | `false` | no | | [region](#input\_region) | The region to provision the bucket. If you pass a value for this, do not pass one for var.cross\_region\_location or var.single\_site\_location. | `string` | `"us-south"` | no | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where The COS instance will be provisioned. It is required if setting input variable create\_cos\_instance to true. | `string` | `null` | no | diff --git a/main.tf b/main.tf index 07a3ee17..ee1de473 100644 --- a/main.tf +++ b/main.tf @@ -5,12 +5,13 @@ ############################################################################## locals { - at_enabled = var.activity_tracker_crn == null ? [] : [1] - metrics_enabled = var.sysdig_crn == null ? [] : [1] - archive_enabled = var.archive_days == null ? [] : [1] - expire_enabled = var.expire_days == null ? [] : [1] - retention_enabled = var.retention_enabled ? [1] : [] - object_versioning_enabled = var.object_versioning_enabled ? [1] : [] + at_enabled = var.activity_tracker_crn == null ? [] : [1] + metrics_enabled = var.sysdig_crn == null ? [] : [1] + archive_enabled = var.archive_days == null ? [] : [1] + expire_enabled = var.expire_days == null ? [] : [1] + retention_enabled = var.retention_enabled ? [1] : [] + object_lock_duration_days = var.object_lock_duration_days > 0 ? [1] : [] + object_lock_duration_years = var.object_lock_duration_years > 0 ? [1] : [] # input variable validation # tflint-ignore: terraform_unused_declarations @@ -39,6 +40,12 @@ locals { validate_cross_region_retention = var.cross_region_location != "us" && var.retention_enabled ? tobool("Retention is currently only supported in the `US` location for cross region buckets.") : true # tflint-ignore: terraform_unused_declarations validate_cross_region_kms = var.cross_region_location != "us" && var.cross_region_location != null ? can(regex(".*hs-crypto.*", var.kms_key_crn)) ? tobool("Support for using HPCS instance for KMS encryption in cross-regional bucket is only available in US region.") : true : true + # tflint-ignore: terraform_unused_declarations + validate_locking = var.object_locking_enabled && !var.object_versioning_enabled ? tobool("Object locking requires object versioning to be enabled.") : true + # tflint-ignore: terraform_unused_declarations + validate_lock_duration_both = var.object_locking_enabled && var.object_lock_duration_days != 0 && var.object_lock_duration_years != 0 ? tobool("Object lock duration days and years can not both be set.") : true + # tflint-ignore: terraform_unused_declarations + validate_lock_duration_none = var.object_locking_enabled && var.object_lock_duration_days == 0 && var.object_lock_duration_years == 0 ? tobool("Object lock duration days or years must be set.") : true } # workaround for https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4478 @@ -129,6 +136,7 @@ resource "ibm_cos_bucket" "cos_bucket" { key_protect = var.kms_key_crn hard_quota = var.hard_quota force_delete = var.force_delete + object_lock = var.object_locking_enabled ## This for_each block is NOT a loop to attach to multiple retention blocks. ## This block is only used to conditionally add retention block depending on retention is enabled. dynamic "retention_rule" { @@ -179,13 +187,8 @@ resource "ibm_cos_bucket" "cos_bucket" { metrics_monitoring_crn = var.sysdig_crn } } - ## This for_each block is NOT a loop to attach to multiple versioning blocks. - ## This block is only used to conditionally attach a single versioning block. - dynamic "object_versioning" { - for_each = local.object_versioning_enabled - content { - enable = var.object_versioning_enabled - } + object_versioning { + enable = var.object_versioning_enabled } } @@ -208,6 +211,7 @@ resource "ibm_cos_bucket" "cos_bucket1" { storage_class = var.bucket_storage_class hard_quota = var.hard_quota force_delete = var.force_delete + object_lock = var.object_locking_enabled ## This for_each block is NOT a loop to attach to multiple retention blocks. ## This block is only used to conditionally add retention block depending on retention is enabled. dynamic "retention_rule" { @@ -258,13 +262,8 @@ resource "ibm_cos_bucket" "cos_bucket1" { metrics_monitoring_crn = var.sysdig_crn } } - ## This for_each block is NOT a loop to attach to multiple versioning blocks. - ## This block is only used to conditionally attach a single versioning block. - dynamic "object_versioning" { - for_each = local.object_versioning_enabled - content { - enable = var.object_versioning_enabled - } + object_versioning { + enable = var.object_versioning_enabled } } @@ -279,6 +278,44 @@ locals { s3_endpoint_direct = var.create_cos_bucket ? (var.kms_encryption_enabled ? ibm_cos_bucket.cos_bucket[0].s3_endpoint_direct : ibm_cos_bucket.cos_bucket1[0].s3_endpoint_direct) : null } +############################################################################## +# Bucket retention lock +############################################################################## + +resource "ibm_cos_bucket_object_lock_configuration" "lock_configuration" { + count = var.object_locking_enabled ? 1 : 0 + bucket_crn = local.bucket_crn + bucket_location = local.bucket_region + endpoint_type = var.management_endpoint_type_for_bucket + + # This is not a loop. Include either the `days` or `years`. + dynamic "object_lock_configuration" { + for_each = local.object_lock_duration_days + content { + object_lock_enabled = "Enabled" # only accepts "Enabled" + object_lock_rule { + default_retention { + mode = "COMPLIANCE" # only accepts "COMPLIANCE" + days = var.object_lock_duration_days + } + } + } + } + # This is not a loop. Include either the `days` or `years`. + dynamic "object_lock_configuration" { + for_each = local.object_lock_duration_years + content { + object_lock_enabled = "Enabled" # only accepts "Enabled" + object_lock_rule { + default_retention { + mode = "COMPLIANCE" # only accepts "COMPLIANCE" + years = var.object_lock_duration_years + } + } + } + } +} + ############################################################################## # Context Based Restrictions ############################################################################## diff --git a/modules/buckets/README.md b/modules/buckets/README.md index 834e9e7e..000a8bab 100644 --- a/modules/buckets/README.md +++ b/modules/buckets/README.md @@ -78,7 +78,7 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [bucket\_configs](#input\_bucket\_configs) | Cloud Object Storage bucket configurations |
list(object({
access_tags = optional(list(string), [])
add_bucket_name_suffix = optional(bool, false)
bucket_name = string
kms_encryption_enabled = optional(bool, true)
kms_guid = optional(string, null)
kms_key_crn = optional(string, null)
skip_iam_authorization_policy = optional(bool, false)
management_endpoint_type = optional(string, "public")
cross_region_location = optional(string, null)
storage_class = optional(string, "smart")
region_location = optional(string, null)
resource_instance_id = string
force_delete = optional(bool, true)
single_site_location = optional(string, null)
hard_quota = optional(number, null)

activity_tracking = optional(object({
read_data_events = optional(bool, true)
write_data_events = optional(bool, true)
activity_tracker_crn = optional(string, null)
}))
archive_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 20)
type = optional(string, "Glacier")
}))
expire_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 365)
}))
metrics_monitoring = optional(object({
usage_metrics_enabled = optional(bool, true)
request_metrics_enabled = optional(bool, true)
metrics_monitoring_crn = optional(string, null)
}))
object_versioning = optional(object({
enable = optional(bool, false)
}))
retention_rule = optional(object({
default = optional(number, 90)
maximum = optional(number, 350)
minimum = optional(number, 90)
permanent = optional(bool, false)
}))
cbr_rules = optional(list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})), [])
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
})), [])

}))
| n/a | yes | +| [bucket\_configs](#input\_bucket\_configs) | Cloud Object Storage bucket configurations |
list(object({
access_tags = optional(list(string), [])
add_bucket_name_suffix = optional(bool, false)
bucket_name = string
kms_encryption_enabled = optional(bool, true)
kms_guid = optional(string, null)
kms_key_crn = optional(string, null)
skip_iam_authorization_policy = optional(bool, false)
management_endpoint_type = optional(string, "public")
cross_region_location = optional(string, null)
storage_class = optional(string, "smart")
region_location = optional(string, null)
resource_instance_id = string
force_delete = optional(bool, true)
single_site_location = optional(string, null)
hard_quota = optional(number, null)
object_locking_enabled = optional(bool, false)
object_lock_duration_days = optional(number, 0)
object_lock_duration_years = optional(number, 0)

activity_tracking = optional(object({
read_data_events = optional(bool, true)
write_data_events = optional(bool, true)
activity_tracker_crn = optional(string, null)
}))
archive_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 20)
type = optional(string, "Glacier")
}))
expire_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 365)
}))
metrics_monitoring = optional(object({
usage_metrics_enabled = optional(bool, true)
request_metrics_enabled = optional(bool, true)
metrics_monitoring_crn = optional(string, null)
}))
object_versioning = optional(object({
enable = optional(bool, false)
}))
retention_rule = optional(object({
default = optional(number, 90)
maximum = optional(number, 350)
minimum = optional(number, 90)
permanent = optional(bool, false)
}))
cbr_rules = optional(list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})), [])
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
})), [])

}))
| n/a | yes | ### Outputs diff --git a/modules/buckets/main.tf b/modules/buckets/main.tf index 0e925f8e..4e7dfc9b 100644 --- a/modules/buckets/main.tf +++ b/modules/buckets/main.tf @@ -26,6 +26,9 @@ module "buckets" { management_endpoint_type_for_bucket = each.value.management_endpoint_type force_delete = each.value.force_delete hard_quota = each.value.hard_quota + object_locking_enabled = each.value.object_locking_enabled + object_lock_duration_days = each.value.object_lock_duration_days + object_lock_duration_years = each.value.object_lock_duration_years access_tags = can(each.value.access_tags) ? each.value.access_tags : [] diff --git a/modules/buckets/variables.tf b/modules/buckets/variables.tf index ac4e4767..c7c4b2f4 100644 --- a/modules/buckets/variables.tf +++ b/modules/buckets/variables.tf @@ -22,6 +22,9 @@ variable "bucket_configs" { force_delete = optional(bool, true) single_site_location = optional(string, null) hard_quota = optional(number, null) + object_locking_enabled = optional(bool, false) + object_lock_duration_days = optional(number, 0) + object_lock_duration_years = optional(number, 0) activity_tracking = optional(object({ read_data_events = optional(bool, true) diff --git a/modules/fscloud/README.md b/modules/fscloud/README.md index 531170db..ef5b50c6 100644 --- a/modules/fscloud/README.md +++ b/modules/fscloud/README.md @@ -108,7 +108,7 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [access\_tags](#input\_access\_tags) | A list of access tags to apply to the cos instance created by the module, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial for more details | `list(string)` | `[]` | no | -| [bucket\_configs](#input\_bucket\_configs) | Cloud Object Storage bucket configurations |
list(object({
access_tags = optional(list(string), [])
add_bucket_name_suffix = optional(bool, false)
bucket_name = string
kms_encryption_enabled = optional(bool, true)
kms_guid = optional(string, null)
kms_key_crn = string
skip_iam_authorization_policy = optional(bool, false)
management_endpoint_type = string
cross_region_location = optional(string, null)
storage_class = optional(string, "smart")
region_location = optional(string, null)
resource_instance_id = optional(string, null)
force_delete = optional(bool, true)
single_site_location = optional(string, null)
hard_quota = optional(number, null)

activity_tracking = optional(object({
read_data_events = optional(bool, true)
write_data_events = optional(bool, true)
activity_tracker_crn = optional(string, null)
}))
archive_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 20)
type = optional(string, "Glacier")
}))
expire_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 365)
}))
metrics_monitoring = optional(object({
usage_metrics_enabled = optional(bool, true)
request_metrics_enabled = optional(bool, true)
metrics_monitoring_crn = optional(string, null)
}))
object_versioning = optional(object({
enable = optional(bool, false)
}))
retention_rule = optional(object({
default = optional(number, 90)
maximum = optional(number, 350)
minimum = optional(number, 90)
permanent = optional(bool, false)
}))
cbr_rules = optional(list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})), [])
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
})), [])

}))
| `[]` | no | +| [bucket\_configs](#input\_bucket\_configs) | Cloud Object Storage bucket configurations |
list(object({
access_tags = optional(list(string), [])
add_bucket_name_suffix = optional(bool, false)
bucket_name = string
kms_encryption_enabled = optional(bool, true)
kms_guid = optional(string, null)
kms_key_crn = string
skip_iam_authorization_policy = optional(bool, false)
management_endpoint_type = string
cross_region_location = optional(string, null)
storage_class = optional(string, "smart")
region_location = optional(string, null)
resource_instance_id = optional(string, null)
force_delete = optional(bool, true)
single_site_location = optional(string, null)
hard_quota = optional(number, null)
object_locking_enabled = optional(bool, false)
object_lock_duration_days = optional(number, 0)
object_lock_duration_years = optional(number, 0)

activity_tracking = optional(object({
read_data_events = optional(bool, true)
write_data_events = optional(bool, true)
activity_tracker_crn = optional(string, null)
}))
archive_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 20)
type = optional(string, "Glacier")
}))
expire_rule = optional(object({
enable = optional(bool, false)
days = optional(number, 365)
}))
metrics_monitoring = optional(object({
usage_metrics_enabled = optional(bool, true)
request_metrics_enabled = optional(bool, true)
metrics_monitoring_crn = optional(string, null)
}))
object_versioning = optional(object({
enable = optional(bool, false)
}))
retention_rule = optional(object({
default = optional(number, 90)
maximum = optional(number, 350)
minimum = optional(number, 90)
permanent = optional(bool, false)
}))
cbr_rules = optional(list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
tags = optional(list(object({
name = string
value = string
})), [])
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
})), [])

}))
| `[]` | no | | [cos\_instance\_name](#input\_cos\_instance\_name) | The name to give the cloud object storage instance that will be provisioned by this module. Only required if 'create\_cos\_instance' is true. | `string` | `null` | no | | [cos\_plan](#input\_cos\_plan) | Plan to be used for creating cloud object storage instance. Only used if 'create\_cos\_instance' it true. | `string` | `"standard"` | no | | [cos\_tags](#input\_cos\_tags) | Optional list of tags to be added to cloud object storage instance. Only used if 'create\_cos\_instance' it true. | `list(string)` | `[]` | no | diff --git a/modules/fscloud/variables.tf b/modules/fscloud/variables.tf index 38c848e4..520fc2f1 100644 --- a/modules/fscloud/variables.tf +++ b/modules/fscloud/variables.tf @@ -76,6 +76,9 @@ variable "bucket_configs" { force_delete = optional(bool, true) single_site_location = optional(string, null) hard_quota = optional(number, null) + object_locking_enabled = optional(bool, false) + object_lock_duration_days = optional(number, 0) + object_lock_duration_years = optional(number, 0) activity_tracking = optional(object({ read_data_events = optional(bool, true) diff --git a/solutions/secure-cross-regional-bucket/main.tf b/solutions/secure-cross-regional-bucket/main.tf index 8476341e..ab593e74 100644 --- a/solutions/secure-cross-regional-bucket/main.tf +++ b/solutions/secure-cross-regional-bucket/main.tf @@ -19,6 +19,9 @@ locals { storage_class = var.bucket_storage_class force_delete = var.force_delete hard_quota = var.hard_quota + object_locking_enabled = var.object_locking_enabled + object_lock_duration_days = var.object_lock_duration_days + object_lock_duration_years = var.object_lock_duration_years activity_tracking = var.activity_tracker_crn != null ? { read_data_events = true diff --git a/solutions/secure-cross-regional-bucket/variables.tf b/solutions/secure-cross-regional-bucket/variables.tf index 7f43baef..84779ebd 100644 --- a/solutions/secure-cross-regional-bucket/variables.tf +++ b/solutions/secure-cross-regional-bucket/variables.tf @@ -162,3 +162,21 @@ variable "retention_permanent" { type = bool default = false } + +variable "object_locking_enabled" { + description = "Specifies if an object lock configuration should be created. Requires 'object_versoning_enabled' to be true. Only used if 'create_cos_bucket' is true." + type = bool + default = false +} + +variable "object_lock_duration_days" { + description = "Specifies the default number of days for the retention lock duration. When setting 'object_lock_duration_days' do not set 'object_lock_duration_years'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} + +variable "object_lock_duration_years" { + description = "Specifies the default number of years for the retention lock duration. When setting 'object_lock_duration_years' do not set 'object_lock_duration_days'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} diff --git a/solutions/secure-regional-bucket/main.tf b/solutions/secure-regional-bucket/main.tf index 52e6633e..aa6ba15f 100644 --- a/solutions/secure-regional-bucket/main.tf +++ b/solutions/secure-regional-bucket/main.tf @@ -19,6 +19,9 @@ locals { storage_class = var.bucket_storage_class force_delete = var.force_delete hard_quota = var.hard_quota + object_locking_enabled = var.object_locking_enabled + object_lock_duration_days = var.object_lock_duration_days + object_lock_duration_years = var.object_lock_duration_years activity_tracking = var.activity_tracker_crn != null ? { read_data_events = true diff --git a/solutions/secure-regional-bucket/variables.tf b/solutions/secure-regional-bucket/variables.tf index e1cc9796..fd7e7c4c 100644 --- a/solutions/secure-regional-bucket/variables.tf +++ b/solutions/secure-regional-bucket/variables.tf @@ -175,3 +175,21 @@ variable "retention_permanent" { type = bool default = false } + +variable "object_locking_enabled" { + description = "Specifies if an object lock configuration should be created. Requires 'object_versoning_enabled' to be true. Only used if 'create_cos_bucket' is true." + type = bool + default = false +} + +variable "object_lock_duration_days" { + description = "Specifies the default number of days for the retention lock duration. When setting 'object_lock_duration_days' do not set 'object_lock_duration_years'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} + +variable "object_lock_duration_years" { + description = "Specifies the default number of years for the retention lock duration. When setting 'object_lock_duration_years' do not set 'object_lock_duration_days'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} diff --git a/variables.tf b/variables.tf index e7dabbf5..fccf0fad 100644 --- a/variables.tf +++ b/variables.tf @@ -187,6 +187,24 @@ variable "retention_permanent" { default = false } +variable "object_locking_enabled" { + description = "Specifies if an object lock configuration should be created. Requires 'object_versoning_enabled' to be true. Only used if 'create_cos_bucket' is true." + type = bool + default = false +} + +variable "object_lock_duration_days" { + description = "Specifies the default number of days for the retention lock duration. When setting 'object_lock_duration_days' do not set 'object_lock_duration_years'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} + +variable "object_lock_duration_years" { + description = "Specifies the default number of years for the retention lock duration. When setting 'object_lock_duration_years' do not set 'object_lock_duration_days'. Only used if 'create_cos_bucket' is true." + type = number + default = 0 +} + variable "object_versioning_enabled" { description = "Enable object versioning to keep multiple versions of an object in a bucket. Cannot be used with retention rule. Only used if 'create_cos_bucket' is true." type = bool