Merge pull request #8 from its-corp/feature/bcn01-sync-dashboard-changes

feat(BCN01-LAB): sync manual Dashboard changes to Terraform
This commit is contained in:
Jose Martinez
2026-03-26 06:28:41 +01:00
committed by GitHub Enterprise
8 changed files with 308 additions and 32 deletions
+97 -16
View File
@@ -21,6 +21,11 @@ data "meraki_switch_stacks" "stacks" {
network_id = local.network_id 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 # Local para extraer el network_id exacto de la lista de redes
locals { locals {
network_id = [for n in data.meraki_networks.net.items : n.id if n.name == var.network_name][0] 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 # Helper: expande un port_range en lista de port_ids
# "1-48" -> ["1","2",...,"48"] | "1" -> ["1"] # "1-48" -> ["1","2",...,"48"] | "1" -> ["1"]
_expand_range = { _expand_range = {
for config in concat(var.switch_port_configs, var.switch_stack_port_configs) : for config in concat(var.switch_port_configs, var.switch_stack_port_configs, var.switch_named_port_configs) :
"${try(config.serial, config.stack_name)}:${config.port_range}" => ( "${try(config.serial, try(config.stack_name, config.switch_name))}:${config.port_range}" => (
length(split("-", config.port_range)) == 2 length(split("-", config.port_range)) == 2
? [for i in range( ? [for i in range(
tonumber(split("-", config.port_range)[0]), tonumber(split("-", config.port_range)[0]),
@@ -45,6 +50,19 @@ locals {
stack.name => stack.serials 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 # Expande switch_port_configs (serial explícito) en entradas individuales
switch_ports_expanded = flatten([ switch_ports_expanded = flatten([
for config in var.switch_port_configs : [ for config in var.switch_port_configs : [
@@ -55,6 +73,7 @@ locals {
name = config.name name = config.name
type = config.type type = config.type
vlan = config.vlan vlan = config.vlan
allowed_vlans = config.allowed_vlans
access_policy_type = config.access_policy_type access_policy_type = config.access_policy_type
access_policy_number = config.access_policy_number access_policy_number = config.access_policy_number
} }
@@ -73,6 +92,7 @@ locals {
name = config.name name = config.name
type = config.type type = config.type
vlan = config.vlan vlan = config.vlan
allowed_vlans = config.allowed_vlans
access_policy_type = config.access_policy_type access_policy_type = config.access_policy_type
access_policy_number = config.access_policy_number 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( all_ports = merge(
{ for p in local.switch_ports_expanded : p.key => p }, { 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 network_id = local.network_id
vlan_id = each.key vlan_id = each.key
name = each.value.name name = each.value.name
subnet = each.value.subnet subnet = each.value.subnet # null para VLANs sin L3
appliance_ip = each.value.appliance_ip appliance_ip = each.value.appliance_ip # null para VLANs sin L3
reserved_ip_ranges = each.value.reserved_ip_ranges reserved_ip_ranges = each.value.reserved_ip_ranges
dhcp_handling = each.value.dhcp_handling 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 } for_each = { for s in var.wireless_ssids : tostring(s.number) => s }
depends_on = [meraki_appliance_vlans_settings.activate_vlans] depends_on = [meraki_appliance_vlans_settings.activate_vlans]
network_id = local.network_id network_id = local.network_id
number = each.value.number number = each.value.number
name = each.value.name name = each.value.name
enabled = each.value.enabled enabled = each.value.enabled
auth_mode = each.value.auth_mode auth_mode = each.value.auth_mode
splash_page = each.value.splash_page psk = each.value.psk
ip_assignment_mode = each.value.ip_assignment_mode wpa_encryption_mode = each.value.wpa_encryption_mode
use_vlan_tagging = each.value.use_vlan_tagging splash_page = each.value.splash_page
default_vlan_id = each.value.default_vlan_id 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 ? [ radius_servers = length(each.value.radius_servers) > 0 ? [
for server in each.value.radius_servers : { for server in each.value.radius_servers : {
host = server.host host = server.host
@@ -175,8 +215,49 @@ resource "meraki_switch_port" "ports" {
name = each.value.name name = each.value.name
type = each.value.type 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_type = each.value.access_policy_type
access_policy_number = each.value.access_policy_number 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
}
+64 -7
View File
@@ -30,11 +30,12 @@ variable "firewall_rules" {
# SSIDs wireless # SSIDs wireless
variable "wireless_ssids" { variable "wireless_ssids" {
type = list(object({ type = list(object({
number = number number = number
name = string name = string
enabled = optional(bool, true) enabled = optional(bool, true)
auth_mode = string auth_mode = string
splash_page = optional(string, "None") 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") wpa_encryption_mode = optional(string, "WPA3 only")
ip_assignment_mode = optional(string, "Bridge mode") ip_assignment_mode = optional(string, "Bridge mode")
use_vlan_tagging = optional(bool, false) use_vlan_tagging = optional(bool, false)
@@ -61,8 +62,8 @@ variable "radius_secret" {
variable "switch_vlans" { variable "switch_vlans" {
type = map(object({ type = map(object({
name = string name = string
subnet = string subnet = optional(string, null) # null para VLANs sin L3 (p.ej. WAN puro switching)
appliance_ip = string appliance_ip = optional(string, null)
dhcp_handling = optional(string, "Run a DHCP server") dhcp_handling = optional(string, "Run a DHCP server")
reserved_ip_ranges = optional(list(object({ reserved_ip_ranges = optional(list(object({
comment = string comment = string
@@ -112,6 +113,7 @@ variable "switch_port_configs" {
name = optional(string, "") name = optional(string, "")
type = optional(string, "access") type = optional(string, "access")
vlan = optional(number, null) vlan = optional(number, null)
allowed_vlans = optional(string, "all")
access_policy_type = optional(string, "Open") access_policy_type = optional(string, "Open")
access_policy_number = optional(number, null) access_policy_number = optional(number, null)
})) }))
@@ -127,6 +129,7 @@ variable "switch_stack_port_configs" {
name = optional(string, "") name = optional(string, "")
type = optional(string, "access") type = optional(string, "access")
vlan = optional(number, null) vlan = optional(number, null)
allowed_vlans = optional(string, "all")
access_policy_type = optional(string, "Open") access_policy_type = optional(string, "Open")
access_policy_number = optional(number, null) 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." 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)."
}
+15
View File
@@ -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"
},
]
+11
View File
@@ -1,5 +1,16 @@
# Reglas de firewall L3 # Reglas de firewall L3
firewall_rules = [ 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" comment = "Bloqueo temporal switch a 8.8.8.8"
policy = "deny" policy = "deny"
+4
View File
@@ -32,4 +32,8 @@ module "bcn01_lab" {
switch_access_policies = var.switch_access_policies switch_access_policies = var.switch_access_policies
switch_port_configs = var.switch_port_configs switch_port_configs = var.switch_port_configs
switch_stack_port_configs = var.switch_stack_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
} }
+57 -5
View File
@@ -113,10 +113,62 @@ switch_port_configs = []
# ] # ]
switch_stack_port_configs = [ switch_stack_port_configs = [
{ {
stack_name = "bcn01-lab-stack01" stack_name = "bcn01-lab-stack01"
port_range = "6" port_range = "6"
type = "access" type = "access"
vlan = 101 # GUEST vlan = 101 # GUEST
access_policy_type = "Open" 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"
}, },
] ]
+55 -4
View File
@@ -34,6 +34,7 @@ variable "wireless_ssids" {
name = string name = string
enabled = optional(bool, true) enabled = optional(bool, true)
auth_mode = string auth_mode = string
psk = optional(string, null)
splash_page = optional(string, "None") splash_page = optional(string, "None")
wpa_encryption_mode = optional(string, "WPA3 only") wpa_encryption_mode = optional(string, "WPA3 only")
ip_assignment_mode = optional(string, "Bridge mode") ip_assignment_mode = optional(string, "Bridge mode")
@@ -61,8 +62,8 @@ variable "radius_secret" {
variable "switch_vlans" { variable "switch_vlans" {
type = map(object({ type = map(object({
name = string name = string
subnet = string subnet = optional(string, null)
appliance_ip = string appliance_ip = optional(string, null)
dhcp_handling = optional(string, "Run a DHCP server") dhcp_handling = optional(string, "Run a DHCP server")
reserved_ip_ranges = optional(list(object({ reserved_ip_ranges = optional(list(object({
comment = string comment = string
@@ -100,10 +101,11 @@ variable "switch_access_policies" {
variable "switch_port_configs" { variable "switch_port_configs" {
type = list(object({ type = list(object({
serial = string serial = string
port_range = string # puerto único "1" o rango "1-24" port_range = string
name = optional(string, "") name = optional(string, "")
type = optional(string, "access") type = optional(string, "access")
vlan = optional(number, null) vlan = optional(number, null)
allowed_vlans = optional(string, "all")
access_policy_type = optional(string, "Open") access_policy_type = optional(string, "Open")
access_policy_number = optional(number, null) 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." 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" { variable "switch_stack_port_configs" {
type = list(object({ type = list(object({
stack_name = string stack_name = string
@@ -119,9 +120,59 @@ variable "switch_stack_port_configs" {
name = optional(string, "") name = optional(string, "")
type = optional(string, "access") type = optional(string, "access")
vlan = optional(number, null) vlan = optional(number, null)
allowed_vlans = optional(string, "all")
access_policy_type = optional(string, "Open") access_policy_type = optional(string, "Open")
access_policy_number = optional(number, null) access_policy_number = optional(number, null)
})) }))
default = [] default = []
description = "Puertos de stack a configurar por nombre. Terraform resuelve los seriales automáticamente." 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."
}
+5
View File
@@ -89,4 +89,9 @@ switch_vlans = {
appliance_ip = "10.2.56.1" appliance_ip = "10.2.56.1"
dhcp_handling = "Do not respond to DHCP requests" 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)
}
} }