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) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-09 12:15:23 +02:00
parent 8a42f5482f
commit ebd21623ef
4 changed files with 55 additions and 24 deletions

View file

@ -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"

View file

@ -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)

View file

@ -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)

View file

@ -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)