diff --git a/modules/meraki-site/main.tf b/modules/meraki-site/main.tf index 85161b4..42cc74d 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_network_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_network_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, resolución dinámica de serial) + 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 } ) } @@ -103,8 +141,8 @@ resource "meraki_appliance_vlan" "mx_gateways" { network_id = local.network_id vlan_id = each.key name = each.value.name - subnet = each.value.subnet - appliance_ip = each.value.appliance_ip + subnet = each.value.subnet # null para VLANs sin L3 + appliance_ip = each.value.appliance_ip # null para VLANs sin L3 reserved_ip_ranges = each.value.reserved_ip_ranges dhcp_handling = each.value.dhcp_handling } @@ -114,15 +152,17 @@ resource "meraki_wireless_ssid" "ssids" { for_each = { for s in var.wireless_ssids : tostring(s.number) => s } depends_on = [meraki_appliance_vlans_settings.activate_vlans] - network_id = local.network_id - number = each.value.number - name = each.value.name - enabled = each.value.enabled - auth_mode = each.value.auth_mode - splash_page = each.value.splash_page - ip_assignment_mode = each.value.ip_assignment_mode - use_vlan_tagging = each.value.use_vlan_tagging - default_vlan_id = each.value.default_vlan_id + network_id = local.network_id + number = each.value.number + name = each.value.name + enabled = each.value.enabled + auth_mode = each.value.auth_mode + psk = each.value.psk + wpa_encryption_mode = each.value.wpa_encryption_mode + splash_page = each.value.splash_page + ip_assignment_mode = each.value.ip_assignment_mode + use_vlan_tagging = each.value.use_vlan_tagging + default_vlan_id = each.value.default_vlan_id radius_servers = length(each.value.radius_servers) > 0 ? [ for server in each.value.radius_servers : { host = server.host @@ -175,8 +215,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_switch_settings" "mgmt_vlan" { + count = var.switch_management_vlan != null ? 1 : 0 + network_id = local.network_id + + vlan = var.switch_management_vlan +} + +# 8. Interfaces de enrutamiento L3 en stacks (SVIs para acceso de gestión al Dashboard) +resource "meraki_switch_stack_routing_interface" "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 + interface_ip = each.value.ip_address + subnet = each.value.subnet + + default_gateway = 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..f4c55d9 100755 --- a/modules/meraki-site/variables.tf +++ b/modules/meraki-site/variables.tf @@ -30,11 +30,12 @@ variable "firewall_rules" { # SSIDs wireless variable "wireless_ssids" { type = list(object({ - number = number - name = string - enabled = optional(bool, true) - auth_mode = string - splash_page = optional(string, "None") + number = number + name = string + enabled = optional(bool, true) + auth_mode = string + psk = optional(string, null) # Contraseña WPA2/WPA3-PSK (sensible, usar TF_VAR o secret) + splash_page = optional(string, "None") wpa_encryption_mode = optional(string, "WPA3 only") ip_assignment_mode = optional(string, "Bridge mode") use_vlan_tagging = optional(bool, false) @@ -61,8 +62,8 @@ variable "radius_secret" { variable "switch_vlans" { type = map(object({ name = string - subnet = string - appliance_ip = string + subnet = optional(string, null) # null para VLANs sin L3 (p.ej. WAN puro switching) + appliance_ip = optional(string, null) dhcp_handling = optional(string, "Run a DHCP server") reserved_ip_ranges = optional(list(object({ comment = string @@ -112,6 +113,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 +129,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 +137,57 @@ 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) +# Útil para configurar un miembro específico de un stack sin conocer el serial +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..6d73fc5 100644 --- a/sites/BCN01-LAB/switch.auto.tfvars +++ b/sites/BCN01-LAB/switch.auto.tfvars @@ -113,10 +113,62 @@ 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 de un switch concreto (miembro individual del stack) +# El serial se encuentra en: Dashboard > Switches > eqt-lab-st01-sw01 > Overview +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" + }, + { + # Puerto 3 de eqt-lab-st01-sw01 → AP (trunk, nativa 108 APs, tageadas 100 ACCESS y 101 GUEST) + switch_name = "eqt-lab-st01-sw01" + port_range = "3" + name = "AP" + type = "trunk" + vlan = 108 # APs - VLAN nativa (untagged) + allowed_vlans = "100,101,108" # ACCESS + GUEST + APs + 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..4af645a 100755 --- a/sites/BCN01-LAB/variables.tf +++ b/sites/BCN01-LAB/variables.tf @@ -34,6 +34,7 @@ variable "wireless_ssids" { name = string enabled = optional(bool, true) auth_mode = string + psk = optional(string, null) splash_page = optional(string, "None") wpa_encryption_mode = optional(string, "WPA3 only") ip_assignment_mode = optional(string, "Bridge mode") @@ -61,8 +62,8 @@ variable "radius_secret" { variable "switch_vlans" { type = map(object({ name = string - subnet = string - appliance_ip = string + subnet = optional(string, null) + appliance_ip = optional(string, null) dhcp_handling = optional(string, "Run a DHCP server") reserved_ip_ranges = optional(list(object({ comment = string @@ -100,10 +101,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 +113,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 +120,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." +} diff --git a/sites/BCN01-LAB/vlans.auto.tfvars b/sites/BCN01-LAB/vlans.auto.tfvars index 189cfae..dbfa75c 100755 --- a/sites/BCN01-LAB/vlans.auto.tfvars +++ b/sites/BCN01-LAB/vlans.auto.tfvars @@ -89,4 +89,9 @@ switch_vlans = { appliance_ip = "10.2.56.1" dhcp_handling = "Do not respond to DHCP requests" } + "111" = { + name = "WAN" + dhcp_handling = "Do not respond to DHCP requests" + # Sin L3: sin subnet ni appliance_ip (VLAN de switching puro hacia el ISP) + } } \ No newline at end of file