diff --git a/modules/meraki-site/main.tf b/modules/meraki-site/main.tf index 85161b4..2f131bd 100755 --- a/modules/meraki-site/main.tf +++ b/modules/meraki-site/main.tf @@ -21,6 +21,11 @@ data "meraki_switch_stacks" "stacks" { network_id = local.network_id } +# Dispositivos de la red (para resolución dinámica de serial por nombre de switch) +data "meraki_devices" "devices" { + network_id = local.network_id +} + # Local para extraer el network_id exacto de la lista de redes locals { network_id = [for n in data.meraki_networks.net.items : n.id if n.name == var.network_name][0] @@ -28,8 +33,8 @@ locals { # Helper: expande un port_range en lista de port_ids # "1-48" -> ["1","2",...,"48"] | "1" -> ["1"] _expand_range = { - for config in concat(var.switch_port_configs, var.switch_stack_port_configs) : - "${try(config.serial, config.stack_name)}:${config.port_range}" => ( + for config in concat(var.switch_port_configs, var.switch_stack_port_configs, var.switch_named_port_configs) : + "${try(config.serial, try(config.stack_name, config.switch_name))}:${config.port_range}" => ( length(split("-", config.port_range)) == 2 ? [for i in range( tonumber(split("-", config.port_range)[0]), @@ -45,6 +50,19 @@ locals { stack.name => stack.serials } + # Mapa de nombre de stack -> ID del stack + stack_ids = { + for stack in data.meraki_switch_stacks.stacks.items : + stack.name => stack.id + } + + # Mapa de nombre de dispositivo -> serial + device_serials = { + for d in data.meraki_devices.devices.items : + d.name => d.serial + if d.name != null && d.name != "" + } + # Expande switch_port_configs (serial explícito) en entradas individuales switch_ports_expanded = flatten([ for config in var.switch_port_configs : [ @@ -55,6 +73,7 @@ locals { name = config.name type = config.type vlan = config.vlan + allowed_vlans = config.allowed_vlans access_policy_type = config.access_policy_type access_policy_number = config.access_policy_number } @@ -73,6 +92,7 @@ locals { name = config.name type = config.type vlan = config.vlan + allowed_vlans = config.allowed_vlans access_policy_type = config.access_policy_type access_policy_number = config.access_policy_number } @@ -80,10 +100,28 @@ locals { ] ]) - # Unión de ambas listas para el resource meraki_switch_port + # Expande switch_named_port_configs (por nombre de switch) en entradas individuales + named_ports_expanded = flatten([ + for config in var.switch_named_port_configs : [ + for port_id in local._expand_range["${config.switch_name}:${config.port_range}"] : { + key = "${local.device_serials[config.switch_name]}:${port_id}" + serial = local.device_serials[config.switch_name] + port_id = port_id + name = config.name + type = config.type + vlan = config.vlan + allowed_vlans = config.allowed_vlans + access_policy_type = config.access_policy_type + access_policy_number = config.access_policy_number + } + ] + ]) + + # Unión de todas las listas para el resource meraki_switch_port all_ports = merge( { for p in local.switch_ports_expanded : p.key => p }, - { for p in local.stack_ports_expanded : p.key => p } + { for p in local.stack_ports_expanded : p.key => p }, + { for p in local.named_ports_expanded : p.key => p } ) } @@ -175,8 +213,49 @@ resource "meraki_switch_port" "ports" { name = each.value.name type = each.value.type - vlan = each.value.vlan + vlan = each.value.vlan + allowed_vlans = each.value.type == "trunk" ? each.value.allowed_vlans : null access_policy_type = each.value.access_policy_type access_policy_number = each.value.access_policy_number } + +# 7. VLAN de gestión de los switches del site +resource "meraki_networks_switch_settings" "mgmt_vlan" { + count = var.switch_management_vlan != null ? 1 : 0 + network_id = local.network_id + + management_vlan = var.switch_management_vlan +} + +# 8. Interfaces de enrutamiento L3 en stacks (SVIs para acceso de gestión al Dashboard) +resource "meraki_networks_switch_stacks_routing_interfaces" "stack_interfaces" { + for_each = { + for i in var.stack_routing_interfaces : + "${i.stack_name}:${i.vlan_id}" => i + } + + network_id = local.network_id + switch_stack_id = local.stack_ids[each.value.stack_name] + + name = each.value.name + vlan_id = each.value.vlan_id + ip_address = each.value.ip_address + subnet = each.value.subnet + + default_gateway_ip = each.value.default_gateway +} + +# 9. Puertos del firewall MX +resource "meraki_appliance_port" "ports" { + for_each = { for p in var.appliance_ports : p.port_id => p } + + network_id = local.network_id + port_id = each.value.port_id + + enabled = each.value.enabled + type = each.value.type + vlan = each.value.vlan + allowed_vlans = each.value.type == "trunk" ? each.value.allowed_vlans : null + drop_untagged_traffic = each.value.drop_untagged_traffic +} diff --git a/modules/meraki-site/variables.tf b/modules/meraki-site/variables.tf index e2f0500..03ccb9a 100755 --- a/modules/meraki-site/variables.tf +++ b/modules/meraki-site/variables.tf @@ -112,6 +112,7 @@ variable "switch_port_configs" { name = optional(string, "") type = optional(string, "access") vlan = optional(number, null) + allowed_vlans = optional(string, "all") access_policy_type = optional(string, "Open") access_policy_number = optional(number, null) })) @@ -127,6 +128,7 @@ variable "switch_stack_port_configs" { name = optional(string, "") type = optional(string, "access") vlan = optional(number, null) + allowed_vlans = optional(string, "all") access_policy_type = optional(string, "Open") access_policy_number = optional(number, null) })) @@ -134,3 +136,56 @@ variable "switch_stack_port_configs" { description = "Puertos de stack a configurar por nombre. Terraform resuelve los seriales de todos los miembros automáticamente." } +# Configuración de puertos por nombre de switch (resolución dinámica de serial por nombre) +variable "switch_named_port_configs" { + type = list(object({ + switch_name = string # nombre exacto del switch en Meraki Dashboard + port_range = string # puerto único "1" o rango "1-24" + name = optional(string, "") + type = optional(string, "access") + vlan = optional(number, null) + allowed_vlans = optional(string, "all") + access_policy_type = optional(string, "Open") + access_policy_number = optional(number, null) + })) + default = [] + description = "Puertos de switch a configurar por nombre de dispositivo. Terraform resuelve el serial automáticamente." +} + +# VLAN de gestión de los switches del site +variable "switch_management_vlan" { + type = number + default = null + description = "VLAN ID de gestión para los switches del site (Dashboard > Switch > Switch settings > Management VLAN)" +} + +# Interfaces de enrutamiento L3 en stacks de switches +variable "stack_routing_interfaces" { + type = list(object({ + stack_name = string # nombre exacto del stack en Meraki Dashboard + name = string # nombre descriptivo de la interfaz + vlan_id = number + ip_address = string # IP estática del stack en esta VLAN + subnet = string # subred en formato CIDR, ej: "10.2.55.0/24" + default_gateway = optional(string, null) # gateway para acceso a internet + dns1 = optional(string, null) # DNS primario + dns2 = optional(string, null) # DNS secundario + })) + default = [] + description = "Interfaces L3 (SVIs) en stacks de switches para acceso de gestión al Dashboard de Meraki." +} + +# Puertos del firewall MX +variable "appliance_ports" { + type = list(object({ + port_id = string + enabled = optional(bool, true) + type = optional(string, "access") # "access" o "trunk" + vlan = optional(number, null) # VLAN nativa en trunk, o VLAN de acceso + allowed_vlans = optional(string, "all") # solo para trunk + drop_untagged_traffic = optional(bool, false) + })) + default = [] + description = "Configuración de puertos del firewall MX (LAN ports)." +} + diff --git a/sites/BCN01-LAB/appliance.auto.tfvars b/sites/BCN01-LAB/appliance.auto.tfvars new file mode 100644 index 0000000..0602d59 --- /dev/null +++ b/sites/BCN01-LAB/appliance.auto.tfvars @@ -0,0 +1,15 @@ +# Configuración de puertos LAN del firewall MX +# port_id: número del puerto físico en el MX +# type: "trunk" o "access" +# vlan: VLAN nativa (untagged) en trunk, o VLAN de acceso en access +appliance_ports = [ + { + # Puerto 7: trunk hacia el stack de switches + # VLAN nativa 109 (MANAGEMENT), permite todas las VLANs + port_id = "7" + enabled = true + type = "trunk" + vlan = 109 # MANAGEMENT - VLAN nativa (untagged) + allowed_vlans = "all" + }, +] diff --git a/sites/BCN01-LAB/firewall.auto.tfvars b/sites/BCN01-LAB/firewall.auto.tfvars index 0a40d49..cf2fb0d 100755 --- a/sites/BCN01-LAB/firewall.auto.tfvars +++ b/sites/BCN01-LAB/firewall.auto.tfvars @@ -1,5 +1,16 @@ # Reglas de firewall L3 firewall_rules = [ + { + # Permite que los switches (VLAN MANAGEMENT) lleguen a internet para acceder al Dashboard de Meraki + comment = "Permitir MANAGEMENT a internet (acceso Dashboard Meraki)" + policy = "allow" + protocol = "any" + src_cidr = "10.2.55.0/24" # VLAN 109 - MANAGEMENT + src_port = "any" + dest_cidr = "any" + dest_port = "any" + syslog_enabled = false + }, { comment = "Bloqueo temporal switch a 8.8.8.8" policy = "deny" diff --git a/sites/BCN01-LAB/main.tf b/sites/BCN01-LAB/main.tf index 874047f..1b252d4 100755 --- a/sites/BCN01-LAB/main.tf +++ b/sites/BCN01-LAB/main.tf @@ -32,4 +32,8 @@ module "bcn01_lab" { switch_access_policies = var.switch_access_policies switch_port_configs = var.switch_port_configs switch_stack_port_configs = var.switch_stack_port_configs + switch_named_port_configs = var.switch_named_port_configs + switch_management_vlan = var.switch_management_vlan + stack_routing_interfaces = var.stack_routing_interfaces + appliance_ports = var.appliance_ports } diff --git a/sites/BCN01-LAB/switch.auto.tfvars b/sites/BCN01-LAB/switch.auto.tfvars index 9ea7377..f8aa529 100644 --- a/sites/BCN01-LAB/switch.auto.tfvars +++ b/sites/BCN01-LAB/switch.auto.tfvars @@ -113,10 +113,51 @@ switch_port_configs = [] # ] switch_stack_port_configs = [ { - stack_name = "bcn01-lab-stack01" - port_range = "6" - type = "access" - vlan = 101 # GUEST - access_policy_type = "Open" + stack_name = "bcn01-lab-stack01" + port_range = "6" + type = "access" + vlan = 101 # GUEST + access_policy_type = "Open" + }, + { + # Puerto 47: uplink trunk en ambos miembros del stack + # VLAN nativa 109 (MANAGEMENT), permite todas las VLANs + stack_name = "bcn01-lab-stack01" + port_range = "47" + name = "Uplink trunk" + type = "trunk" + vlan = 109 # MANAGEMENT - VLAN nativa (untagged) + allowed_vlans = "all" + access_policy_type = "Open" + }, +] + +# Puertos por nombre de switch (para configurar un miembro concreto del stack) +switch_named_port_configs = [ + { + # Puerto 1 de eqt-lab-st01-sw01 → VLAN SERVERS (estática, sin autenticación) + switch_name = "eqt-lab-st01-sw01" + port_range = "1" + name = "Servers" + type = "access" + vlan = 110 # SERVERS + access_policy_type = "Open" + }, +] + +# VLAN de gestión de los switches del site +switch_management_vlan = 109 + +# Interfaces L3 en el stack para acceso de gestión al Dashboard de Meraki +stack_routing_interfaces = [ + { + stack_name = "bcn01-lab-stack01" + name = "MANAGEMENT" + vlan_id = 109 + ip_address = "10.2.55.2" + subnet = "10.2.55.0/24" + default_gateway = "10.2.55.1" + dns1 = "8.8.8.8" + dns2 = "8.8.4.4" }, ] diff --git a/sites/BCN01-LAB/variables.tf b/sites/BCN01-LAB/variables.tf index c60d991..2562be7 100755 --- a/sites/BCN01-LAB/variables.tf +++ b/sites/BCN01-LAB/variables.tf @@ -100,10 +100,11 @@ variable "switch_access_policies" { variable "switch_port_configs" { type = list(object({ serial = string - port_range = string # puerto único "1" o rango "1-24" + port_range = string name = optional(string, "") type = optional(string, "access") vlan = optional(number, null) + allowed_vlans = optional(string, "all") access_policy_type = optional(string, "Open") access_policy_number = optional(number, null) })) @@ -111,7 +112,6 @@ variable "switch_port_configs" { description = "Puertos de switch a configurar con 802.1X. Requiere serial del switch." } -# Configuración de puertos por nombre de stack variable "switch_stack_port_configs" { type = list(object({ stack_name = string @@ -119,9 +119,59 @@ variable "switch_stack_port_configs" { name = optional(string, "") type = optional(string, "access") vlan = optional(number, null) + allowed_vlans = optional(string, "all") access_policy_type = optional(string, "Open") access_policy_number = optional(number, null) })) default = [] description = "Puertos de stack a configurar por nombre. Terraform resuelve los seriales automáticamente." } + +variable "switch_named_port_configs" { + type = list(object({ + switch_name = string + port_range = string + name = optional(string, "") + type = optional(string, "access") + vlan = optional(number, null) + allowed_vlans = optional(string, "all") + access_policy_type = optional(string, "Open") + access_policy_number = optional(number, null) + })) + default = [] + description = "Puertos de switch a configurar por nombre de dispositivo. Terraform resuelve el serial automáticamente." +} + +variable "switch_management_vlan" { + type = number + default = null + description = "VLAN ID de gestión para los switches del site." +} + +variable "stack_routing_interfaces" { + type = list(object({ + stack_name = string + name = string + vlan_id = number + ip_address = string + subnet = string + default_gateway = optional(string, null) + dns1 = optional(string, null) + dns2 = optional(string, null) + })) + default = [] + description = "Interfaces L3 (SVIs) en stacks de switches para acceso de gestión al Dashboard." +} + +variable "appliance_ports" { + type = list(object({ + port_id = string + enabled = optional(bool, true) + type = optional(string, "access") + vlan = optional(number, null) + allowed_vlans = optional(string, "all") + drop_untagged_traffic = optional(bool, false) + })) + default = [] + description = "Configuración de puertos LAN del firewall MX." +}