# Variables ==================================================================== variable "bucket_name" { description = "Globally unique S3 bucket name" type = string validation { condition = can(regex("^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$", var.bucket_name)) error_message = <<-EOT bucket_name must be 3-63 characters, lowercase letters, numbers, dots, and hyphens only, and must start and end with a letter or number. EOT } validation { condition = !can(regex("\\.\\.", var.bucket_name)) error_message = "bucket_name must not contain consecutive periods." } validation { condition = !can(regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", var.bucket_name)) error_message = "bucket_name must not be formatted as an IP address." } } variable "force_destroy" { description = "Allow destruction of non-empty bucket" type = bool default = false } variable "versioning_enabled" { description = "Enable object versioning" type = bool default = true } variable "kms_key_id" { description = "KMS key ARN for server-side encryption (null uses aws:kms with managed key)" type = string default = null validation { condition = var.kms_key_id == null || can(regex("^arn:aws[a-zA-Z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$", var.kms_key_id)) error_message = "kms_key_id must be a valid KMS key ARN (arn:aws:kms:::key/)." } } variable "sse_algorithm" { description = "SSE algorithm (aws:kms or AES256)" type = string default = "aws:kms" validation { condition = contains(["aws:kms", "AES256"], var.sse_algorithm) error_message = "sse_algorithm must be 'aws:kms' or 'AES256'." } validation { condition = var.sse_algorithm == "aws:kms" || var.kms_key_id == null error_message = <<-EOT kms_key_id must not be set when sse_algorithm is "AES256". AES256 does not use a KMS key, and the value would be silently ignored. EOT } } variable "bucket_key_enabled" { description = "Enable S3 bucket key to reduce KMS API calls" type = bool default = true } variable "block_public_acls" { description = "Block public ACLs" type = bool default = true } variable "block_public_policy" { description = "Block public bucket policies" type = bool default = true } variable "ignore_public_acls" { description = "Ignore public ACLs" type = bool default = true } variable "restrict_public_buckets" { description = "Restrict public bucket access" type = bool default = true } variable "lifecycle_rules" { description = "Map of lifecycle rules to apply" type = map(object({ enabled = optional(bool, true) prefix = optional(string, null) abort_incomplete_multipart_upload_days = optional(number, 7) expiration_days = optional(number, null) noncurrent_version_expiration_days = optional(number, 90) transitions = optional(list(object({ days = number storage_class = string })), []) })) default = {} validation { condition = alltrue(flatten([ for rule_key, rule in var.lifecycle_rules : [ for t in rule.transitions : contains(local.allowed_storage_classes, t.storage_class) ] ])) error_message = "One or more lifecycle transition storage_class values are not valid. Must be one of the following: ${join(", ", local.allowed_storage_classes)}." } validation { condition = alltrue([ for rule_key, rule in var.lifecycle_rules : rule.abort_incomplete_multipart_upload_days > 0 ]) error_message = "abort_incomplete_multipart_upload_days must be greater than 0 for every lifecycle rule." } validation { condition = alltrue([ for rule_key, rule in var.lifecycle_rules : rule.expiration_days == null || alltrue([ for t in rule.transitions : t.days < rule.expiration_days ]) ]) error_message = "Every transition's days must be less than expiration_days for rules where expiration_days is set." } } variable "logging_target_bucket" { description = "S3 bucket name to receive access logs (optional)" type = string default = null } variable "logging_target_prefix" { description = "Prefix for access log objects" type = string default = "logs/" } variable "replication_role_arn" { description = "IAM role ARN for S3 replication (required when replication_rules is set)" type = string default = null validation { condition = var.replication_role_arn == null || can(regex("^arn:aws[a-zA-Z-]*:iam::[0-9]{12}:role/.+$", var.replication_role_arn)) error_message = "replication_role_arn must be a valid IAM role ARN." } validation { condition = length(var.replication_rules) == 0 || var.replication_role_arn != null error_message = "replication_role_arn is required when replication_rules is set." } } variable "replication_rules" { description = "Map of replication rules" type = map(object({ destination_bucket_arn = string destination_kms_key_id = optional(string, null) destination_account_id = optional(string, null) prefix = optional(string, "") priority = optional(number, 1) delete_marker_replication = optional(bool, true) })) default = {} validation { condition = length(var.replication_rules) == 0 || var.versioning_enabled error_message = "versioning_enabled must be true when replication_rules is set." } validation { condition = alltrue([ for rule_key, rule in var.replication_rules : can(regex("^arn:aws[a-zA-Z-]*:s3:::[a-z0-9.-]+$", rule.destination_bucket_arn)) ]) error_message = "Every replication rule's destination_bucket_arn must be a valid S3 bucket ARN." } validation { condition = alltrue([ for rule_key, rule in var.replication_rules : rule.destination_account_id == null || can(regex("^[0-9]{12}$", rule.destination_account_id)) ]) error_message = "destination_account_id, when set, must be a 12-digit AWS account ID." } validation { condition = alltrue([ for rule_key, rule in var.replication_rules : rule.priority >= 0 ]) error_message = "priority must be 0 or greater for every replication rule." } validation { condition = alltrue([ for rule_key, rule in var.replication_rules : rule.destination_kms_key_id == null || can(regex("^arn:aws[a-zA-Z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$", rule.destination_kms_key_id)) ]) error_message = "destination_kms_key_id, when set, must be a valid KMS key ARN for every replication rule." } validation { condition = length(distinct([for rule_key, rule in var.replication_rules : rule.priority])) == length(var.replication_rules) error_message = "Every replication rule must have a unique priority." } } variable "policy" { description = "JSON bucket policy document (optional)" type = string default = null validation { condition = var.policy == null || can(jsondecode(var.policy)) error_message = "policy must be valid JSON." } } variable "object_ownership" { description = "Object ownership" type = string default = "BucketOwnerEnforced" validation { condition = contains(local.allowed_object_ownership_values, var.object_ownership) error_message = "object_ownership must be one of the following: ${join(", ", local.allowed_object_ownership_values)}." } } variable "object_lock_enabled" { description = <<-EOT Enable S3 Object Lock on the bucket. Can only be set at bucket creation, cannot be enabled on an existing bucket. Requires versioning_enabled = true. EOT type = bool default = false validation { condition = !var.object_lock_enabled || var.versioning_enabled error_message = <<-EOT object_lock_enabled requires versioning_enabled to also be true. Object Lock cannot function without versioning. EOT } } variable "object_lock_default_retention" { description = <<-EOT Default retention rule applied to objects when Object Lock is enabled. mode must be 'GOVERNANCE' or 'COMPLIANCE'. Only used when object_lock_enabled is true. EOT type = object({ mode = string days = number }) default = null validation { condition = ( var.object_lock_default_retention == null || contains(["GOVERNANCE", "COMPLIANCE"], var.object_lock_default_retention.mode) ) error_message = <<-EOT object_lock_default_retention.mode must be either 'GOVERNANCE' or 'COMPLIANCE'. EOT } validation { condition = ( var.object_lock_default_retention == null || var.object_lock_default_retention.days > 0 ) error_message = <<-EOT object_lock_default_retention.days must be greater than 0. EOT } } variable "tags" { description = "Resource tags to apply to all resources" type = map(string) default = {} }