From ebd21623efb9bf25b325ae05c79a87d1a487d151 Mon Sep 17 00:00:00 2001 From: sjat Date: Tue, 9 Jun 2026 12:15:23 +0200 Subject: [PATCH] feat: real flat+mgmt-VLAN topology in host_vars; role tweaks host_vars: DATA VLAN 30 (ether1 uplink + ether2-7 + sfp1/2), isolated MGMT VLAN 99 on ether8, mgmt 192.168.88.1/24, no gateway, NTP disabled. Role: switch_ntp_enabled flag (enable/disable NTP), conditional default route (skip when no gateway), and a guarded removal of the legacy defconf bridge IP so the mgmt IP lives only on vlan-mgmt. Membership Jinja re-validated; lint+syntax clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- host_vars/crs310-maker.yml | 50 +++++++++++-------- .../defaults/main.yml | 1 + .../tasks/identity.yml | 10 +++- .../tasks/vlans.yml | 18 ++++++- 4 files changed, 55 insertions(+), 24 deletions(-) diff --git a/host_vars/crs310-maker.yml b/host_vars/crs310-maker.yml index ef3b848..8667cf9 100644 --- a/host_vars/crs310-maker.yml +++ b/host_vars/crs310-maker.yml @@ -5,40 +5,46 @@ # base MAC (ether1): D0:EA:11:24:F4:AA # RouterOS: 7.19.6 stable (bootloader already current) -> pinned target below # -# Bootstrap status (2026-06-08): identity set; user `sjat` (full) created with the -# operator ed25519 key imported + a vaulted password (vault_switch_admin_password in -# group_vars/mikrotik.vault.yml). Key login verified. Default `admin` still enabled -# (not yet hardened). Switch currently on the bench at 192.168.88.1 (defconf, not yet -# reset/VLAN-configured). Real mgmt addressing below is the FUTURE production plan. +# Topology (decided 2026-06-09, see docs/superpowers/specs/ +# 2026-06-09-crs310-flat-mgmtvlan-design.md): the switch is a FLAT L2 switch on the +# makerspace 10.2.30.0/24 network with its management isolated on a dedicated VLAN. +# - ether1 is the copper UPLINK (SFP+ deferred until connectors arrive). +# - DATA VLAN 30: flat 10.2.30.0/24 bridged through; the switch does NO routing/DHCP +# and the CPU is not a member (no switch presence on the user network). +# - MGMT VLAN 99: isolated; switch mgmt IP 192.168.88.1/24 on vlan-mgmt, reachable +# only from the dedicated mgmt port ether8. No gateway, no NTP/DNS (no internet). + # Day-2 connection: key auth as the named admin user (overrides the bootstrap # default ansible_user=admin in group_vars/mikrotik.yml). ansible_user: sjat switch_identity_name: "crs310-maker" + +# ----- Management (isolated VLAN 99) ----- switch_mgmt_vlan_id: 99 -switch_mgmt_address: "10.0.99.2/24" # EDIT: real mgmt IP -switch_mgmt_gateway: "10.0.99.1" # EDIT: real gateway -switch_dns_servers: "10.0.99.1" -switch_ntp_servers: "10.0.99.1" +switch_mgmt_address: "192.168.88.1/24" +switch_mgmt_gateway: "" # isolated mgmt -> no default route +switch_dns_servers: "" # no DNS on an isolated mgmt plane +switch_ntp_enabled: false # no internet on mgmt -> NTP would only error switch_admin_user: "sjat" -# PLACEHOLDER VLAN/port topology — vlans.yml is correct mechanism, but these IDs -# and the per-port map are NOT the real makerspace plan. Replace with the real -# VLAN ids + full ether1-8/sfp map before any on-site VLAN run. Notes: -# - mode: access -> untagged member of `pvid`; mode: trunk -> tagged member of -# each id in `tagged_vlans`, with `pvid` as the native (untagged) VLAN. -# - trunk pvid: 1 means untagged frames on the uplink land in VLAN 1 (unused in a -# hardened design). Decide deliberately whether the uplink should carry any -# untagged traffic; set pvid to an intended native VLAN or leave 1 as a dead end. -# - the bridge (CPU) is tagged ONLY on switch_mgmt_vlan_id (see vlans.yml). +# ----- VLANs + per-port map (all untagged access; no trunks) ----- +# DATA = flat 10.2.30.0/24 (uplink + device ports); MGMT = isolated admin VLAN. switch_vlans: + - {id: 30, name: "data"} - {id: 99, name: "mgmt"} - - {id: 10, name: "members"} switch_bridge_ports: - - {interface: "ether1", pvid: 10, mode: access} - - {interface: "ether2", pvid: 10, mode: access} - - {interface: "sfp-sfpplus1", pvid: 1, mode: trunk, tagged_vlans: [99, 10]} + - {interface: "ether1", pvid: 30, mode: access} # copper uplink + - {interface: "ether2", pvid: 30, mode: access} + - {interface: "ether3", pvid: 30, mode: access} + - {interface: "ether4", pvid: 30, mode: access} + - {interface: "ether5", pvid: 30, mode: access} + - {interface: "ether6", pvid: 30, mode: access} + - {interface: "ether7", pvid: 30, mode: access} + - {interface: "sfp-sfpplus1", pvid: 30, mode: access} + - {interface: "sfp-sfpplus2", pvid: 30, mode: access} + - {interface: "ether8", pvid: 99, mode: access} # dedicated mgmt port # Firmware: pinned at the version already installed (no upgrade planned now). switch_firmware_target: "7.19.6" diff --git a/roles/makerfloss.mikrotik_switch/defaults/main.yml b/roles/makerfloss.mikrotik_switch/defaults/main.yml index fba749b..32590ec 100644 --- a/roles/makerfloss.mikrotik_switch/defaults/main.yml +++ b/roles/makerfloss.mikrotik_switch/defaults/main.yml @@ -5,6 +5,7 @@ switch_mgmt_vlan_id: 99 switch_mgmt_address: "192.168.88.1/24" # PLACEHOLDER — override in host_vars switch_mgmt_gateway: "192.168.88.254" # PLACEHOLDER — override in host_vars switch_dns_servers: "192.168.88.254" +switch_ntp_enabled: true # set false for an isolated mgmt plane switch_ntp_servers: "192.168.88.254" # Services to disable for hardening (winbox kept on by default for recovery) diff --git a/roles/makerfloss.mikrotik_switch/tasks/identity.yml b/roles/makerfloss.mikrotik_switch/tasks/identity.yml index 1095690..eda0337 100644 --- a/roles/makerfloss.mikrotik_switch/tasks/identity.yml +++ b/roles/makerfloss.mikrotik_switch/tasks/identity.yml @@ -15,10 +15,18 @@ - /ip/dns/set servers="{{ switch_dns_servers }}" allow-remote-requests=no changed_when: false -- name: Configure NTP client +- name: Enable NTP client community.routeros.command: commands: - /system/ntp/client/set enabled=yes servers="{{ switch_ntp_servers }}" + when: switch_ntp_enabled | bool + changed_when: false + +- name: Disable NTP client (isolated mgmt plane has no upstream time source) + community.routeros.command: + commands: + - /system/ntp/client/set enabled=no + when: not (switch_ntp_enabled | bool) changed_when: false - name: Disable unused IP services (hardening; winbox kept for recovery) diff --git a/roles/makerfloss.mikrotik_switch/tasks/vlans.yml b/roles/makerfloss.mikrotik_switch/tasks/vlans.yml index a95dfd9..4d54da8 100644 --- a/roles/makerfloss.mikrotik_switch/tasks/vlans.yml +++ b/roles/makerfloss.mikrotik_switch/tasks/vlans.yml @@ -76,7 +76,22 @@ interface="{{ switch_bridge_name }}" vlan-id={{ switch_mgmt_vlan_id }} } changed_when: false -- name: Assign the management IP address +# On a defconf switch the mgmt IP lives directly on the bare `bridge`; it must move to +# vlan-mgmt (same address can't be on both). Removing it drops a session reaching the +# switch THROUGH the bridge, so during the first cutover this is done out-of-band as one +# detached flip (see the design doc's runbook), not by a straight play run. In steady +# state both tasks below are no-ops. +- name: Remove the legacy management IP from the bare bridge interface + community.routeros.command: + commands: + - >- + :if ([:len [/ip/address/find interface="{{ switch_bridge_name }}" + address="{{ switch_mgmt_address }}"]] > 0) + do={ /ip/address/remove [find interface="{{ switch_bridge_name }}" + address="{{ switch_mgmt_address }}"] } + changed_when: false + +- name: Assign the management IP address to vlan-mgmt community.routeros.command: commands: - >- @@ -92,6 +107,7 @@ :if ([:len [/ip/route/find dst-address="0.0.0.0/0"]] = 0) do={ /ip/route/add dst-address=0.0.0.0/0 gateway="{{ switch_mgmt_gateway }}" } + when: switch_mgmt_gateway | length > 0 changed_when: false - name: Enable VLAN filtering (LAST — prove mgmt reachability first)