# Nautobot Golden Config -- intended RouterOS config for {{ obj.name }} # # FORMAT-REGEL (TP22, 2026-08-13): Zeilen sind im RouterOS-**/export-Format** geschrieben # (Space-Form `/ip address add ...`, Attribute ALPHABETISCH), nicht in der Slash-Form der # Provisionierungs-Skripte. Golden Config vergleicht Backup (= echter /export) und Intended # textuell -- jede Abweichung in Reihenfolge, Quoting oder Pfadform ist ein Falsch-Drift. # Referenz-Exporte: routeros/state-backups/*-gc-backup-scrubbed-*.cfg # # Secrets tragen die Maske "<>" der ConfigReplace-Backup-Regeln. # 🔴 Zeilen mit <> duerfen NIE per Deploy gepusht werden. /system identity set name={{ obj.name }} {% if obj.name == "crs326-core" %} {#- VLAN-Tabelle: Site-Konstante. name = RouterOS-comment, lease/pool je VLAN. -#} {%- set vlans = [ {"id": 10, "name": "MAIN", "pool": "10.40.10.100-10.40.10.200", "lease": "1d"}, {"id": 20, "name": "INFRA", "pool": "10.40.20.100-10.40.20.200", "lease": "1d"}, {"id": 30, "name": "IOT", "pool": "10.40.30.100-10.40.30.250", "lease": "1d"}, {"id": 40, "name": "MEDIA", "pool": "10.40.40.100-10.40.40.200", "lease": "1d"}, {"id": 50, "name": "UNTRUSTED", "pool": "10.40.50.100-10.40.50.200", "lease": "1d"}, {"id": 90, "name": "GUEST", "pool": "10.40.90.100-10.40.90.200", "lease": "12h"}, {"id": 99, "name": "MGMT", "pool": "10.40.99.100-10.40.99.200", "lease": "1d"} ] %} # ===================================================================================== # crs326-core — PRODUKTIVER L3-Router (seit Cutover 2026-08-08, TP21.1). # Die frueheren `disabled=yes`-Staging-Flags sind ENTFERNT: sie stammten aus der # Vorbereitung des Cutovers und beschreiben nicht mehr den Sollzustand. # ===================================================================================== # [1] INTERFACE-LISTS /interface list add comment=TP15 name=WAN /interface list add comment=TP15 name=LAN /interface list member add interface=vlan100 list=WAN {% for v in vlans %} /interface list member add interface=vlan{{ v.id }} list=LAN {% endfor %} # [2] VLAN-SVIs auf der Bridge (vlan99 traegt bewusst keinen comment) {% for v in vlans %} /interface vlan add {% if v.id != 99 %}comment={{ v.name }} {% endif %}interface=bridge name=vlan{{ v.id }} vlan-id={{ v.id }} {% endfor %} /interface vlan add comment=WAN interface=bridge name=vlan100 vlan-id=100 # [3] GW-IPs je VLAN — datengetrieben aus Nautobot (IP am SVI-Interface gebunden). # comment = Interface-Description; network wird aus der Host-IP abgeleitet. {% for iface in obj.interfaces.all() %} {% if iface.name.startswith("vlan") %} {% for ip in iface.ip_addresses.all() %} /ip address add address={{ ip.address }} comment="{{ iface.description }}" interface={{ iface.name }} network={{ ip.host.split(".")[:3] | join(".") }}.0 {% endfor %} {% endif %} {% endfor %} # [4] WAN — DHCP-Client auf vlan100 (Liwest/Arris) /ip dhcp-client add comment="WAN Arris DHCP (Liwest) via vlan100" interface=vlan100 use-peer-dns=no use-peer-ntp=no # [5] DHCP-Server je VLAN {% for v in vlans %} /ip pool add name=pool-v{{ v.id }} ranges={{ v.pool }} {% endfor %} {% for v in vlans %} /ip dhcp-server add address-pool=pool-v{{ v.id }} comment={{ v.name }} interface=vlan{{ v.id }} lease-time={{ v.lease }} name=dhcp-v{{ v.id }} {% endfor %} {% for v in vlans %} /ip dhcp-server network add address=10.40.{{ v.id }}.0/24 dns-server=10.40.{{ v.id }}.1 domain=bf.lan gateway=10.40.{{ v.id }}.1 {% endfor %} /ip dhcp-server option add code=6 name="dns 1.1.1.1" value=0x01010101 # [6] DNS — Upstream aktuell PLAIN 1.1.1.1. DoH-Cloudflare-Gateway ist offener TODO # (core hat kein CA-Bundle -> verify-doh-cert nicht moeglich), s. TP21.1. /ip dns set allow-remote-requests=yes mdns-repeat-ifaces=vlan10,vlan20,vlan40,vlan30,vlan90 servers=1.1.1.1 # Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich): # core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12 /ip dns static add address=10.40.99.1 comment="core = GW/Router" name=router.lan type=A /ip dns static add address=10.40.99.1 comment="crs326-core (L3)" name=core.bf.lan type=A /ip dns static add address=10.40.99.2 comment=crs326-branch name=branch.bf.lan type=A /ip dns static add address=10.40.99.11 comment="ap1 = reiner AP" name=ap1.bf.lan type=A /ip dns static add address=10.40.99.12 comment="ap2 = reiner AP" name=ap2.bf.lan type=A /ip dns static add address=10.40.20.167 name=macmini.bf.lan type=A /ip dns static add address=10.40.20.100 name=pve.bf.lan type=A /ip dns static add address=10.40.20.173 name=pve-mcp.bf.lan type=A /ip dns static add address=10.40.20.10 name=nas.bf.lan type=A /ip dns static add address=10.40.20.126 name=docker.bf.lan type=A /ip dns static add address=10.40.20.20 name=platform-pg.bf.lan type=A # [7] NAT /ip firewall nat add action=accept chain=srcnat comment="TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" dst-address=10.40.0.0/16 /ip firewall nat add action=masquerade chain=srcnat comment="defconf: masquerade" ipsec-policy=out,none out-interface-list=WAN # [8] ROUTEN zu den WG-Spokes (Default-Route kommt dynamisch vom WAN-DHCP-Client) /ip route add comment="TP10: FM spoke -> wg-hub" dst-address=10.101.0.0/24 gateway=wg-hub /ip route add comment="TP10: DW spoke -> wg-hub" dst-address=10.95.10.0/24 gateway=wg-hub # [9] WG-HUB (TP10 Multi-Site). Der echte Privkey liegt in routeros/ap1-wg-hub.privkey. /interface wireguard add comment="TP10 Multi-Site VPN Hub (FM/DW spokes)" listen-port=51821 mtu=1412 name=wg-hub private-key="<>" /interface wireguard peers add allowed-address=10.101.0.0/24 comment=fm endpoint-address=abxhutfgsid9snmy.myfritz.net endpoint-port=52476 interface=wg-hub name=fm public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE=" /interface wireguard peers add allowed-address=10.95.10.0/24 comment=dw interface=wg-hub name=dw public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw=" {% elif obj.role.name == "Switch" %} # ===================================================================================== # L2-Switch (crs326-branch) — Sollzustand des Mgmt-Layers. # Bewusst NICHT emittiert: die TEMP-Bring-up-Objekte (vlan10 "TEMP Mgmt VLAN10" + # 10.40.10.2). Die tauchen dadurch als Compliance-`extra` auf — genau richtig, denn sie # sind Altlast aus dem Bring-up und gehoeren entfernt (VLAN1/VLAN10-Interim, TP19). # ===================================================================================== /interface vlan add comment=MGMT interface=bridge name=vlan99 vlan-id=99 /ip address add address={{ obj.primary_ip4.address }} comment=TP21-mgmt-target interface=vlan99 network={{ obj.primary_ip4.host.split(".")[:3] | join(".") }}.0 /ip dns set servers=10.40.99.1 /ip route add comment=TP21-default dst-address=0.0.0.0/0 gateway=10.40.99.1 {% elif obj.role.name == "Wireless AP" %} {% set ap = obj.get_config_context()["ap"] %} {% set mgmt_host = obj.primary_ip4.host %} {% set uplink = ap.uplink %} # ===================================================================================== # TP22 — Standalone-AP (kein CAPsMAN). Gerendert aus ConfigContext "ap-common" # + Device.local_config_context_data (Kanaele) + DCIM (Name, primary_ip4). # # SCOPE: nur die COMPLIANCE-VERWALTETEN Sektionen, bewusst NICHT die ganze Config. # Golden Config vergleicht per ComplianceRule.match_config sektionsweise -> defconf- # Boilerplate (ipv4/ipv6-Firewall, /disk, /tool mac-server) und die VAP-Zeilen # (/interface wifi add ... mac-address=...) bleiben draussen: deren MACs sind # geraetevergeben und in Nautobot nicht modelliert. # # 🔴 NIE als Ganzes auf ein laufendes Geraet pushen: die Provisionierung setzt # zwischenzeitlich vlan-filtering=no, was den vlan99-Mgmt-Pfad abreisst -> der Rest # der Zeilen kaeme ueber dieselbe SSH-Session nie an. Voll-Reprovisionierung # ausschliesslich per Datei-Upload + /import (siehe routeros/.secrets/ap*-prov-base.rsc). # # Attributreihenfolge = alphabetisch, exakt wie RouterOS /export sie schreibt. # Referenz: routeros/state-backups/ap1-gc-backup-scrubbed-20260813.cfg # ===================================================================================== # [1] CHANNELS — Frequenz aus der Kanalnummer gerechnet (eine Quelle der Wahrheit) {% for r in ap.radios %} {% set ch = ap.channels[r.key] %} {% set freq = (5000 + 5 * ch) if r.key == "5g" else (2407 + 5 * ch) %} /interface wifi channel add band={{ r.band }} frequency={{ freq }} name=ch-{{ r.key }} width={{ r.width }} {% endfor %} # [2] DATAPATH — je Zone ein VLAN-Tag. OHNE datapath egressiert die SSID ungetaggt # in VLAN1 (das war das TP3.1-Leck) -> jede Zone MUSS hier auftauchen. {% for z in ap.zones %} /interface wifi datapath add bridge={{ ap.bridge }} name=dp-{{ z.name }} vlan-id={{ z.vlan }} {% endfor %} # [3] SECURITY — Passphrasen tragen die Backup-Maske aus TP22 Schritt 0. # ft-over-ds schreibt RouterOS nur, wenn ft=yes. {% for z in ap.zones %} /interface wifi security add authentication-types={{ z.auth }} ft={{ z.ft }}{% if z.ft == "yes" %} ft-over-ds={{ z.ft_over_ds }}{% endif %} name=sec-{{ z.name }} passphrase="<>" {% endfor %} # [4] CONFIGURATION — main sitzt auf den Master-Radios und bringt den Channel mit # (je Band eine configuration); alle anderen Zonen sind Slave-VAPs ohne channel=. {% for z in ap.zones %} {% if z.master %} {% for r in ap.radios %} {% if r.key in z.bands %} /interface wifi configuration add channel=ch-{{ r.key }} country={{ ap.country }} datapath=dp-{{ z.name }} mode=ap name=cfg-{{ z.name }}-{{ r.key }} security=sec-{{ z.name }} ssid={{ z.ssid }} {% endif %} {% endfor %} {% else %} /interface wifi configuration add country={{ ap.country }} datapath=dp-{{ z.name }} mode=ap name=cfg-{{ z.name }} security=sec-{{ z.name }} ssid={{ z.ssid }} {% endif %} {% endfor %} # [5] BRIDGE-VLAN — Nur Master-Radios sind Bridge-Ports (Slave-VAPs forwarden darueber), # deshalb muessen wifi1/wifi2 tagged-Member je Client-VLAN sein. Die frueheren # "Ausnahmen" fallen hier aus den Daten heraus: IOT hat bands=["2g"] -> nur wifi2. /interface bridge vlan add bridge={{ ap.bridge }} untagged={{ ap.bridge }},{{ uplink }} vlan-ids={{ ap.native_vlan }} {% for z in ap.zones %} {% set members = [uplink] + (ap.radios | selectattr("key", "in", z.bands) | map(attribute="iface") | list) %} /interface bridge vlan add bridge={{ ap.bridge }} tagged={{ members | join(",") }} vlan-ids={{ z.vlan }} {% endfor %} # MGMT liegt auf der Bridge, NICHT auf den Radios - sonst waere Management over-the-air. /interface bridge vlan add bridge={{ ap.bridge }} tagged={{ uplink }},{{ ap.bridge }} vlan-ids={{ ap.mgmt.vlan }} # [6] MGMT-LAYER # interface-list + dns hier, damit die platform-weiten ComplianceFeatures # (gelten pro Platform, nicht pro Rolle) auf APs nicht ins Leere laufen. /interface list add comment=defconf name=WAN /interface list add comment=defconf name=LAN /interface list member add comment=defconf interface={{ ap.bridge }} list=LAN /interface list member add interface=vlan{{ ap.mgmt.vlan }} list=LAN /ip dns set allow-remote-requests=yes servers={{ ap.mgmt.gateway }} /interface vlan add interface={{ ap.bridge }} name=vlan{{ ap.mgmt.vlan }} vlan-id={{ ap.mgmt.vlan }} /ip address add address={{ obj.primary_ip4.address }} interface=vlan{{ ap.mgmt.vlan }} network={{ mgmt_host.split(".")[:3] | join(".") }}.0 /ip route add comment="mgmt default via core" dst-address=0.0.0.0/0 gateway={{ ap.mgmt.gateway }} {% endif %}