diff --git a/modules/meraki-site/main.tf b/modules/meraki-site/main.tf index 4ca7118..5f328d5 100755 --- a/modules/meraki-site/main.tf +++ b/modules/meraki-site/main.tf @@ -220,6 +220,10 @@ resource "meraki_device_management_interface" "mx_wan" { 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 diff --git a/modules/meraki-site/variables.tf b/modules/meraki-site/variables.tf index 04e172b..fe67caa 100755 --- a/modules/meraki-site/variables.tf +++ b/modules/meraki-site/variables.tf @@ -191,6 +191,10 @@ variable "mx_wan_uplinks" { wan1_static_subnet_mask = optional(string, null) wan1_static_gateway_ip = optional(string, null) wan1_static_dns = optional(list(string), null) + wan2_static_ip = optional(string, null) + wan2_static_subnet_mask = optional(string, null) + wan2_static_gateway_ip = optional(string, null) + wan2_static_dns = optional(list(string), null) })) default = [] description = "Configuración de interfaz WAN1 estática para cada MX. El serial se resuelve automáticamente por nombre de dispositivo." diff --git a/sites/BCN01-LAB/.terraform.lock.hcl b/sites/BCN01-LAB/.terraform.lock.hcl new file mode 100644 index 0000000..86bad8c --- /dev/null +++ b/sites/BCN01-LAB/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/ciscodevnet/meraki" { + version = "1.9.0" + constraints = "1.9.0" + hashes = [ + "h1:KmWz0JvCHdDd3AtuawxUwmW0VN3fooGw4CRaxiKHT5Y=", + "zh:0b9a7d32f331998a2a1531811667be44f799dfc03f6929f1414d2cab69f659f2", + "zh:179f791e2aa0ca6353541d90956548033b9ee0c880a096e48ce3ae3fe8a1862e", + "zh:2a1a32c6a8068c194e19859a7d88e0b95d0d9cbcf31444454b055ed62ace715a", + "zh:491812b74919d131f4ef3ba968d10b678275ed201428e2af7f53df40fd7e8cee", + "zh:4f5043f5165ee5199a61e4c15230d9f973ed0211a06600d75638f8369bac73fa", + "zh:5679d5a0d5dd370ff5d9321913f293f76be8f7ebc25e5cf1b45ceed9de803348", + "zh:58e1faba3d322bca68efb5cdac1ebe4e6d6f92834ebe5ccff8e491685620185c", + "zh:861b04ee4a498070cfb581488ddc3e90b25be895d35861c2a03a3b224d28e9b5", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:92ee52e0dd3372e6dc2ed21bf5b21124b646c4c1037477e66eae87614d814036", + "zh:a3a851a3ce0c32b17506da0b9370f9d31df8e9d5ed4b422dc09ff5337d4b4192", + "zh:cdac168b00fa658ec68862677cb0b00f356095654e6b1d0df823c330492fa753", + "zh:dc454d6b1051891c99051b92e61015d244eb320a5a491cbffb770a005e448898", + "zh:f14317688e068e40dc11f609c4dc4f81cfa50fbaa43dcbe0117725f1149e9d89", + "zh:fe0544ac117d0c643559f042996fa32f243988bc48d4d49875abad6b326f0e2e", + ] +} diff --git a/sites/BCN01-LAB/MANUAL_STEPS.md b/sites/BCN01-LAB/MANUAL_STEPS.md new file mode 100644 index 0000000..c53d8dc --- /dev/null +++ b/sites/BCN01-LAB/MANUAL_STEPS.md @@ -0,0 +1,32 @@ +# Pasos manuales — BCN01-LAB + +Configuraciones que no pueden gestionarse via Terraform (limitaciones del provider v1.9.0) +y deben aplicarse directamente en el Meraki Dashboard. + +--- + +## Client VPN (L2TP/IPSec) + +**Dashboard:** Security & SD-WAN → Client VPN + +| Parámetro | Valor | +|-----------|-------| +| Estado | Enabled | +| Subnet VPN | `10.2.58.0/23` | +| Authentication | RADIUS | +| RADIUS server | IP del Okta RADIUS Agent, puerto `1812` | +| RADIUS secret | Ver secret de Okta RADIUS Agent | + +> **Nota:** El provider `CiscoDevNet/meraki` v1.9.0 no incluye el resource +> `meraki_appliance_vpn_client_vpn`. Cuando el provider lo soporte, esta +> configuración deberá migrarse a `sites/BCN01-LAB/vpn.auto.tfvars`. + +--- + +## OWE (Opportunistic Wireless Encryption) — SSID EQT-CORPO + +> **Note:** This is not a provider limitation. The provider manages `auth_mode = "open-enhanced"` correctly. + +**Dashboard:** Wireless → SSIDs → EQT-CORPO → Edit settings → Security + +After the first `terraform apply`, verify that **"Opportunistic Wireless Encryption"** is active in the Dashboard. In some cases Meraki requires a one-time manual confirmation click to enable OWE on a new SSID, even though Terraform has already pushed the correct configuration. diff --git a/sites/BCN01-LAB/appliance.tf b/sites/BCN01-LAB/appliance.tf new file mode 100644 index 0000000..9c83cee --- /dev/null +++ b/sites/BCN01-LAB/appliance.tf @@ -0,0 +1,13 @@ +locals { + appliance_ports = [ + { + # Port 7: trunk toward the switch stack + # Native VLAN 109 (MANAGEMENT), allows all VLANs + port_id = "7" + enabled = true + type = "trunk" + vlan = 109 # MANAGEMENT — native (untagged) VLAN + allowed_vlans = "all" + }, + ] +} diff --git a/sites/BCN01-LAB/firewall.tf b/sites/BCN01-LAB/firewall.tf new file mode 100644 index 0000000..df2be01 --- /dev/null +++ b/sites/BCN01-LAB/firewall.tf @@ -0,0 +1,67 @@ +locals { + firewall_rules = [ + { + comment = "Allow ACCESS to internet" + policy = "allow" + protocol = "any" + src_cidr = "10.2.32.0/21" # VLAN 100 - ACCESS + dest_cidr = "any" + }, + { + comment = "Allow MANAGEMENT to internet (Meraki Dashboard access)" + policy = "allow" + protocol = "any" + src_cidr = "10.2.55.0/24" # VLAN 109 - MANAGEMENT + dest_cidr = "any" + }, + { + comment = "Allow APs to internet (Meraki Dashboard access)" + policy = "allow" + protocol = "any" + src_cidr = "10.2.54.0/24" # VLAN 108 - APs + dest_cidr = "any" + }, + { + comment = "Allow GUEST to internet" + policy = "allow" + protocol = "any" + src_cidr = "10.2.40.0/21" # VLAN 101 - GUEST + dest_cidr = "any" + }, + { + comment = "Allow SERVERS to internet" + policy = "allow" + protocol = "any" + src_cidr = "10.2.56.0/24" # VLAN 110 - SERVERS + dest_cidr = "any" + }, + { + comment = "Allow GUEST to SERVERS" + policy = "allow" + protocol = "any" + src_cidr = "10.2.40.0/21" # VLAN 101 - GUEST + dest_cidr = "10.2.56.0/24" # VLAN 110 - SERVERS + }, + { + comment = "Test" + policy = "allow" + protocol = "any" + src_cidr = "10.212.0.0/16" + dest_cidr = "10.212.225.51/32,10.2.56.5/32" + }, + { + comment = "Allow VPN outbound traffic" + policy = "allow" + protocol = "any" + src_cidr = "10.2.58.0/23" # Client VPN subnet + dest_cidr = "any" + }, + { + comment = "Deny all other outbound traffic" + policy = "deny" + protocol = "any" + src_cidr = "any" + dest_cidr = "any" + }, + ] +} diff --git a/sites/BCN01-LAB/main.tf b/sites/BCN01-LAB/main.tf new file mode 100755 index 0000000..d58dc0f --- /dev/null +++ b/sites/BCN01-LAB/main.tf @@ -0,0 +1,38 @@ +terraform { + backend "s3" {} # Config injected via -backend-config in CI (see backend.hcl at repo root) + required_version = ">= 1.5.0" + required_providers { + meraki = { + source = "CiscoDevNet/meraki" + version = "1.9.0" + } + } +} + +provider "meraki" {} + +locals { + organization_name = "Adevinta Information Services SLU" + network_name = "BCN01-LAB" +} + +module "bcn01_lab" { + source = "../../modules/meraki-site" + + organization_name = local.organization_name + network_name = local.network_name + switch_vlans = local.switch_vlans + firewall_rules = local.firewall_rules + wireless_ssids = local.wireless_ssids + radius_secret = var.radius_secret + wifi_password_psk = var.wifi_password_psk + switch_access_policies = local.switch_access_policies + switch_port_configs = local.switch_port_configs + switch_stack_port_configs = local.switch_stack_port_configs + switch_named_port_configs = local.switch_named_port_configs + switch_management_vlan = local.switch_management_vlan + stack_routing_interfaces = local.stack_routing_interfaces + appliance_ports = local.appliance_ports + mx_wan_uplinks = local.mx_wan_uplinks + mx_warm_spare = local.mx_warm_spare +} diff --git a/sites/BCN01-LAB/ssids.tf b/sites/BCN01-LAB/ssids.tf new file mode 100644 index 0000000..9d5a755 --- /dev/null +++ b/sites/BCN01-LAB/ssids.tf @@ -0,0 +1,31 @@ +locals { + wireless_ssids = [ + { + number = 0 + name = "EQT-CORPO" + enabled = true + auth_mode = "8021x" + encryption_mode = "wpa" + wpa_encryption_mode = "WPA3 Transition Mode" + splash_page = "None" + ip_assignment_mode = "Bridge mode" + use_vlan_tagging = true + default_vlan_id = 100 + radius_servers = [ + { host = "10.2.56.5", port = 1812 } + ] + }, + { + number = 1 + name = "EQT-GUEST" + enabled = true + auth_mode = "psk" + encryption_mode = "wpa" + wpa_encryption_mode = "WPA3 Transition Mode" + # Password injected via TF_VAR_wifi_password_psk (GitHub Secret: WIFI_PASSWORD_PSK) + ip_assignment_mode = "Bridge mode" + use_vlan_tagging = true + default_vlan_id = 101 + }, + ] +} diff --git a/sites/BCN01-LAB/switch.tf b/sites/BCN01-LAB/switch.tf new file mode 100644 index 0000000..a84f6fc --- /dev/null +++ b/sites/BCN01-LAB/switch.tf @@ -0,0 +1,106 @@ +locals { + # 802.1X access policies + # The RADIUS shared secret is injected from var.radius_secret — never put it here. + switch_access_policies = [ + { + name = "DOT1X-CORPO" + access_policy_type = "802.1x" + host_mode = "Multi-Auth" + radius_accounting_enabled = false + radius_re_authentication_interval = 0 + url_redirect_walled_garden_enabled = false + radius_failed_auth_vlan_id = 101 # Fallback to GUEST if RADIUS unreachable + radius_servers = [ + { host = "10.2.56.5", port = 1812 } + ] + }, + ] + + # Ports by explicit serial — use when targeting a switch directly by serial number + # Example: + # switch_port_configs = [ + # { + # serial = "XXXX-XXXX-XXXX" + # port_range = "1-20" + # type = "access" + # vlan = 100 # ACCESS — fallback if RADIUS doesn't assign a VLAN + # access_policy_type = "Custom access policy" + # access_policy_number = 1 # references DOT1X-CORPO above + # }, + # ] + switch_port_configs = [] + + # Ports by stack name — Terraform resolves serials for all stack members automatically. + # The same port_range is applied to EVERY switch in the stack. + switch_stack_port_configs = [ + { + stack_name = "bcn01-lab-stack01" + port_range = "47" + name = "UPLINK LAN BCN01-F04-MX01" + type = "trunk" + vlan = 109 # MANAGEMENT — native (untagged) VLAN + allowed_vlans = "all" + access_policy_type = "Open" + }, + { + stack_name = "bcn01-lab-stack01" + port_range = "48" + name = "UPLINK LAN BCN01-F04-MX02" + type = "trunk" + vlan = 109 # MANAGEMENT — native (untagged) VLAN + allowed_vlans = "all" + access_policy_type = "Open" + }, + ] + + # Ports by switch display name — targets a specific stack member without knowing its serial + switch_named_port_configs = [ + { + switch_name = "eqt-lab-st01-sw01" + port_range = "1" + name = "Servers" + type = "access" + vlan = 110 # SERVERS + access_policy_type = "Open" + }, + { + switch_name = "eqt-lab-st01-sw01" + port_range = "2,3" + name = "WAN" + type = "access" + vlan = 111 # WAN + access_policy_type = "Open" + }, + { + switch_name = "eqt-lab-st01-sw02" + port_range = "2,3" + name = "WAN" + type = "access" + vlan = 111 # WAN + access_policy_type = "Open" + }, + { + switch_name = "eqt-lab-st01-sw01" + port_range = "37" + name = "AP" + type = "trunk" + vlan = 108 # APs — native (untagged) VLAN + allowed_vlans = "100,101,108" # ACCESS + GUEST + APs + access_policy_type = "Open" + }, + ] + + switch_management_vlan = 109 + + # L3 routing interfaces on the stack (SVIs for management access to Meraki Dashboard) + 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" + }, + ] +} diff --git a/sites/BCN01-LAB/variables.tf b/sites/BCN01-LAB/variables.tf new file mode 100755 index 0000000..e6aaeac --- /dev/null +++ b/sites/BCN01-LAB/variables.tf @@ -0,0 +1,15 @@ +# Sensitive variables — injected via TF_VAR_* environment variables (GitHub Secrets). +# Never put values for these in any .tf file. + +variable "radius_secret" { + type = string + sensitive = true + default = "" + description = "RADIUS shared secret. Pass via TF_VAR_radius_secret (GitHub Secret: RADIUS_SECRET)." +} + +variable "wifi_password_psk" { + type = string + sensitive = true + description = "PSK Wi-Fi password. Pass via TF_VAR_wifi_password_psk (GitHub Secret: WIFI_PASSWORD_PSK)." +} diff --git a/sites/BCN01-LAB/vlans.tf b/sites/BCN01-LAB/vlans.tf new file mode 100644 index 0000000..8e53b65 --- /dev/null +++ b/sites/BCN01-LAB/vlans.tf @@ -0,0 +1,93 @@ +locals { + switch_vlans = { + "100" = { + name = "ACCESS" + subnet = "10.2.32.0/21" + appliance_ip = "10.2.32.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.32.1", end = "10.2.32.49" } + ] + } + "101" = { + name = "GUEST" + subnet = "10.2.40.0/21" + appliance_ip = "10.2.40.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.40.1", end = "10.2.40.49" } + ] + } + "102" = { + name = "VC" + subnet = "10.2.48.0/24" + appliance_ip = "10.2.48.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.48.1", end = "10.2.48.49" } + ] + } + "103" = { + name = "PRINTERS" + subnet = "10.2.49.0/24" + appliance_ip = "10.2.49.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.49.1", end = "10.2.49.49" } + ] + } + "104" = { + name = "DISPLAYS" + subnet = "10.2.50.0/24" + appliance_ip = "10.2.50.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.50.1", end = "10.2.50.49" } + ] + } + "105" = { + name = "BOOKING" + subnet = "10.2.51.0/24" + appliance_ip = "10.2.51.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.51.1", end = "10.2.51.49" } + ] + } + "106" = { + name = "BADGE_READERS" + subnet = "10.2.52.0/24" + appliance_ip = "10.2.52.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.52.1", end = "10.2.52.49" } + ] + } + "107" = { + name = "CCTV" + subnet = "10.2.53.0/24" + appliance_ip = "10.2.53.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.53.1", end = "10.2.53.49" } + ] + } + "108" = { + name = "APs" + subnet = "10.2.54.0/24" + appliance_ip = "10.2.54.1" + reserved_ip_ranges = [ + { comment = "Static reserved", id = "static", start = "10.2.54.1", end = "10.2.54.49" } + ] + } + "109" = { + name = "MANAGEMENT" + subnet = "10.2.55.0/24" + appliance_ip = "10.2.55.1" + dhcp_handling = "Do not respond to DHCP requests" + } + "110" = { + name = "SERVERS" + subnet = "10.2.56.0/24" + 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" + # No subnet or appliance_ip — L2-only switching VLAN toward the ISP + } + } +} diff --git a/sites/BCN01-LAB/wan.tf b/sites/BCN01-LAB/wan.tf new file mode 100644 index 0000000..aeec3a8 --- /dev/null +++ b/sites/BCN01-LAB/wan.tf @@ -0,0 +1,29 @@ +locals { + # Warm Spare (HA) — floating VIP between primary and spare MX + mx_warm_spare = { + enabled = true + spare_name = "BCN01-F04-MX02" + uplink_mode = "virtual" + virtual_ip1 = "57.133.120.183" # Floating VIP on WAN1 + virtual_ip2 = "57.133.120.182" # Floating VIP on WAN2 + } + + # Static WAN2 configuration for each MX (WAN1 disabled) + # Device serials are resolved automatically from the display name + mx_wan_uplinks = [ + { + name = "BCN01-F04-MX01" + wan2_static_ip = "57.133.120.181" + wan2_static_subnet_mask = "255.255.255.240" # /28 + wan2_static_gateway_ip = "57.133.120.177" + wan2_static_dns = ["8.8.8.8", "1.1.1.1"] + }, + { + name = "BCN01-F04-MX02" + wan2_static_ip = "57.133.120.180" + wan2_static_subnet_mask = "255.255.255.240" # /28 + wan2_static_gateway_ip = "57.133.120.177" + wan2_static_dns = ["8.8.8.8", "1.1.1.1"] + }, + ] +}