variable "vpc_id" { description = "ID of the VPC in which to create security groups" type = string } variable "name_prefix" { description = "Prefix prepended to every security group name created by this module" type = string validation { condition = length(trimspace(var.name_prefix)) > 0 error_message = "name_prefix must not be empty." } } variable "security_groups" { description = "Map of security group definitions" type = map(object({ name_suffix = string description = string ingress_rules = optional(list(object({ description = optional(string, "") protocol = string from_port = number to_port = number ipv4_cidr_blocks = optional(list(string), []) ipv6_cidr_blocks = optional(list(string), []) source_sg_key = optional(string, null) source_sg_id = optional(string, null) self = optional(bool, false) })), []) egress_rules = optional(list(object({ description = optional(string, "") protocol = string from_port = number to_port = number ipv4_cidr_blocks = optional(list(string), []) ipv6_cidr_blocks = optional(list(string), []) source_sg_key = optional(string, null) source_sg_id = optional(string, null) self = optional(bool, false) })), []) })) default = {} validation { condition = alltrue([ for sg_key, sg in var.security_groups : length(sg.ingress_rules) > 0 || length(sg.egress_rules) > 0 ]) error_message = <<-EOT One or more security groups have no ingress or egress rules. Each security group must define at least one rule in either ingress_rules or egress_rules. A security group with neither does not allow any traffic movement. Fix by either removing the empty security group from security_groups, or adding at least one rule to it. EOT } validation { condition = alltrue(flatten([ for sg_key, sg in var.security_groups : [ for rule in concat(sg.ingress_rules, sg.egress_rules) : length(rule.ipv4_cidr_blocks) > 0 || length(rule.ipv6_cidr_blocks) > 0 || rule.source_sg_key != null || rule.source_sg_id != null || rule.self ] ])) error_message = <<-EOT One or more rules have no source configured. Every ingress or egress rule must set at least one of: ipv4_cidr_blocks, ipv6_cidr_blocks, source_sg_key, source_sg_id, or self = true. A rule with none of these produces no AWS resource at all, which is almost always a mistake. EOT } validation { condition = alltrue(flatten([ for sg_key, sg in var.security_groups : [ for rule in concat(sg.ingress_rules, sg.egress_rules) : rule.source_sg_id == null || can(regex("^sg-[a-z0-9]+$", rule.source_sg_id)) ] ])) error_message = <<-EOT One or more source_sg_id values are not a valid format. It must be a security group ID (e.g. "sg-0123abcd"). EOT } validation { condition = alltrue(flatten([ for sg_key, sg in var.security_groups : [ for rule in concat(sg.ingress_rules, sg.egress_rules) : rule.source_sg_key == null || contains(keys(var.security_groups), rule.source_sg_key) ] ])) error_message = <<-EOT One or more source_sg_key values reference a security group that is not defined in the security_groups map. It must match one of the keys in the security_groups map. EOT } } variable "tags" { description = "Resource tags to apply to all resources" type = map(string) default = {} }