terraform { required_version = ">= 1.5.0" required_providers { meraki = { source = "CiscoDevNet/meraki" version = "1.9.0" } } } # Búsqueda automática de IDs (Data Sources) data "meraki_organization" "org" { name = var.organization_name } data "meraki_networks" "net" { organization_id = data.meraki_organization.org.id } # Stacks de switches en la red (para resolución dinámica de seriales por nombre) 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] # Helper: expande un port_range en lista de port_ids # Soporta: "1" -> ["1"] | "1-4" -> ["1","2","3","4"] | "2,3" -> ["2","3"] | "1-3,5,47" -> ["1","2","3","5","47"] _expand_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}" => flatten([ for segment in split(",", config.port_range) : length(split("-", trimspace(segment))) == 2 ? [for i in range( tonumber(split("-", trimspace(segment))[0]), tonumber(split("-", trimspace(segment))[1]) + 1 ) : tostring(i)] : [trimspace(segment)] ]) } # Mapa de nombre de stack -> lista de seriales de sus miembros stack_serials = { for stack in data.meraki_switch_stacks.stacks.items : 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 : [ for port_id in local._expand_range["${config.serial}:${config.port_range}"] : { key = "${config.serial}:${port_id}" serial = config.serial 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 } ] ]) # Expande switch_stack_port_configs (por nombre de stack) en entradas individuales # Aplica el mismo port_range a TODOS los miembros del stack stack_ports_expanded = flatten([ for config in var.switch_stack_port_configs : [ for serial in local.stack_serials[config.stack_name] : [ for port_id in local._expand_range["${config.stack_name}:${config.port_range}"] : { key = "${serial}:${port_id}" serial = serial 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 } ] ] ]) # 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.named_ports_expanded : p.key => p } ) } # --- CONFIGURACIÓN GATEWAY (MX) --- # 1. Activar VLANs en el Appliance (MX) resource "meraki_appliance_vlans_settings" "activate_vlans" { network_id = local.network_id vlans_enabled = true } # 2. Crear las interfaces L3 y VLANs en el MX # Solo se crean VLANs con subnet definida (VLANs L2-only como WAN se excluyen # porque la API de Meraki exige que subnet sea una dirección IPv4 en formato CIDR) resource "meraki_appliance_vlan" "mx_gateways" { for_each = { for k, v in var.switch_vlans : k => v if v.subnet != null } depends_on = [meraki_appliance_vlans_settings.activate_vlans] network_id = local.network_id vlan_id = each.key name = each.value.name subnet = each.value.subnet appliance_ip = each.value.appliance_ip reserved_ip_ranges = each.value.reserved_ip_ranges dhcp_handling = each.value.dhcp_handling } # 3. SSIDs wireless # SSIDs con WPA (psk, open-enhanced): incluyen wpa_encryption_mode resource "meraki_wireless_ssid" "ssids" { for_each = { for s in var.wireless_ssids : tostring(s.number) => s if s.auth_mode != "open" } 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 visible = each.value.visible auth_mode = each.value.auth_mode psk = each.value.auth_mode == "psk" ? (each.value.psk != null ? each.value.psk : var.wifi_password_psk) : null wpa_encryption_mode = each.value.wpa_encryption_mode encryption_mode = each.value.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 port = server.port secret = var.radius_secret } ] : [] } # SSIDs open (sin WPA): wpa_encryption_mode se omite — la API Meraki lo rechaza para auth_mode=open resource "meraki_wireless_ssid" "ssids_open" { for_each = { for s in var.wireless_ssids : tostring(s.number) => s if s.auth_mode == "open" } 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 visible = each.value.visible 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 radius_servers = length(each.value.radius_servers) > 0 ? [ for server in each.value.radius_servers : { host = server.host port = server.port secret = var.radius_secret } ] : [] } # 4. Reglas de firewall L3 resource "meraki_appliance_l3_firewall_rules" "reglas_firewall" { network_id = local.network_id rules = var.firewall_rules } # 4b. Configuración WAN estática de los MX (primary y spare) # El serial se resuelve dinámicamente a partir del nombre del dispositivo # usando el mismo data source meraki_network_devices que ya se usa para los switches resource "meraki_device_management_interface" "mx_wan" { for_each = { for mx in var.mx_wan_uplinks : mx.name => mx } serial = local.device_serials[each.key] wan1_static_ip = each.value.wan1_static_ip wan1_static_subnet_mask = each.value.wan1_static_subnet_mask wan1_static_gateway_ip = each.value.wan1_static_gateway_ip wan1_static_dns = each.value.wan1_static_dns wan2_static_ip = each.value.wan2_static_ip wan2_static_subnet_mask = each.value.wan2_static_subnet_mask wan2_static_gateway_ip = each.value.wan2_static_gateway_ip wan2_static_dns = each.value.wan2_static_dns } # 4c. Warm Spare (HA) — VIP flotante entre primary y spare resource "meraki_appliance_warm_spare" "ha" { count = var.mx_warm_spare != null ? 1 : 0 network_id = local.network_id enabled = var.mx_warm_spare.enabled spare_serial = local.device_serials[var.mx_warm_spare.spare_name] uplink_mode = var.mx_warm_spare.uplink_mode virtual_ip1 = var.mx_warm_spare.virtual_ip1 virtual_ip2 = var.mx_warm_spare.virtual_ip2 } # --- CONFIGURACIÓN SWITCHES (MS) --- # 5. Políticas de acceso 802.1X resource "meraki_switch_access_policy" "dot1x" { for_each = { for p in var.switch_access_policies : p.name => p } network_id = local.network_id name = each.value.name access_policy_type = each.value.access_policy_type host_mode = each.value.host_mode radius_accounting_enabled = each.value.radius_accounting_enabled radius_testing_enabled = each.value.radius_testing_enabled radius_coa_support_enabled = each.value.radius_coa_support_enabled radius_failed_auth_vlan_id = each.value.radius_failed_auth_vlan_id radius_re_authentication_interval = each.value.radius_re_authentication_interval url_redirect_walled_garden_enabled = each.value.url_redirect_walled_garden_enabled radius_servers = [ for server in each.value.radius_servers : { host = server.host port = server.port secret = var.radius_secret } ] } # 6. Configuración de puertos de switch # switch_port_configs: serial explícito | switch_stack_port_configs: por nombre de stack resource "meraki_switch_port" "ports" { for_each = local.all_ports depends_on = [meraki_switch_access_policy.dot1x] serial = each.value.serial port_id = each.value.port_id name = each.value.name 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. Reglas NAT 1:1 del MX resource "meraki_appliance_firewall_one_to_one_nat_rules" "nat_1to1" { count = length(var.one_to_one_nat_rules) > 0 ? 1 : 0 network_id = local.network_id rules = [ for rule in var.one_to_one_nat_rules : { name = rule.name public_ip = rule.public_ip lan_ip = rule.lan_ip uplink = rule.uplink allowed_inbound = [ for ib in rule.allowed_inbound : { protocol = ib.protocol destination_ports = ib.destination_ports allowed_ips = ib.allowed_ips } ] } ] } # 10. 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 }