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
+87 -6
View File
@@ -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
}
@@ -119,6 +157,8 @@ resource "meraki_wireless_ssid" "ssids" {
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
@@ -176,7 +216,48 @@ resource "meraki_switch_port" "ports" {
type = each.value.type
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
}
+59 -2
View File
@@ -34,6 +34,7 @@ variable "wireless_ssids" {
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")
@@ -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)."
}
+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
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"
+4
View File
@@ -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
}
+52
View File
@@ -119,4 +119,56 @@ switch_stack_port_configs = [
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"
},
]
+55 -4
View File
@@ -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."
}
+5
View File
@@ -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)
}
}