Files
bf-infra/docs/nas-vlan20/nautobot-image/nautobot_config.py
Christoph Emsenhuber 3666e1e7fe TP22: RouterOS-Fleet unter Nautobot Golden Config, Deploy scharf
Nimmt die 4 Live-Geräte (crs326-core/-branch, ap1, ap2) real unter Golden
Config: Backup -> Compliance -> intended -> kontrollierter Push. Damit greift
der Governance-Entscheid "Config exklusiv über Nautobot" operativ.

Secret-Leak geschlossen (Anlass, ungeplant gefunden)
  Es gab keine ConfigReplace-Regeln, deshalb hatte der Backup-Job den
  ap1-/export ungefiltert nach Gitea geschrieben: der live genutzte
  WG-Hub-private-key und 6 WLAN-PSKs lagen dort im Klartext.
  - nautobot/ingest-gc-scrubbing.py: 5 ConfigReplace-Regeln, Maske
    "<<REDACTED>>". RouterOS quotet nur bei Bedarf (4 von 6 PSKs standen
    unquotiert), daher Alternation mit der quotierten Variante zuerst --
    \S+ ist greedy und würde bei einem PSK mit Leerzeichen den Rest
    exponieren. Regex-Selbsttest läuft bei jedem Aufruf.
  - Gitea-Repo neu angelegt (History-Rewrite verworfen: Gitea hält
    unerreichbare Blobs bis zum server-seitigen gc). Verifiziert: 0
    unmaskierte Secrets über alle 4 Geräte. User-Entscheid: nicht rotieren.
  - push-gc-template.py: Pre-Push-Secret-Guard, --dry-run, -m, sha optional.

Template
  - AP-Zweig (role == "Wireless AP"), datengetrieben aus ConfigContext
    ap-common + local_config_context_data; ap1/ap2 differieren nur in 4
    Werten. RadioProfile verworfen (hängt an einem Controller, den es seit
    der CAPsMAN-Abschaffung nicht gibt).
  - crs326-core-Zweig von Slash- auf /export-Form (Space-Form, Attribute
    alphabetisch), stale disabled=yes aus der Cutover-Ära entfernt.
  - Switch-Zweig für crs326-branch.
  - Mgmt-Nummernplan korrigiert: ap2 und branch waren in den DNS-Statics
    vertauscht, ap1 zeigte auf ein nicht existierendes .99.3.
    Verbindlich: core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12.
  Ergebnis: ap1 intended == Geräte-Ist (0 Drift), core 68/69 Zeilen
  byte-identisch. 14 ComplianceRules, 51/56 compliant -- die 5 Restdrifts
  sind echte Altlasten (Rettungspfade, Aufräumen gehört zu TP19).

Deploy (nautobot_config.py, Image gc9)
  enable_plan/enable_deploy an. napalm-ros ist READ-ONLY: der Default-
  Dispatcher wählt NAPALM (Platform trägt napalm: ros), Backup geht,
  merge_config scheitert grundsätzlich mit leerer Fehlermeldung. Fix =
  custom_dispatcher auf NetmikoMikrotikRouteros, in den Golden-Config-
  Settings (nicht den Nornir-Settings). Pilot-Deploy: DNS-Statics auf dem
  Core korrigiert, per Purge-vor-Add, weil plan_type=missing nur add
  erzeugt und sonst Duplikate entstanden wären.
  Governance-Regeln in docs/teilprojekte.md festgeschrieben: Pläne nur
  feature-scoped (ein Voll-Push schriebe die Maske als PSK auf die APs),
  AP-Template nie zeilenweise auf ein laufendes Gerät (vlan-filtering=no
  reisst den Mgmt-Pfad ab).

CLAUDE.md: Power-Save-Fehldiagnose korrigiert
  Der P750W war nicht im Power-Save, sondern schlicht ausgeschaltet; das
  Ping-Symptom kam vom lokal mitlaufenden WARP-Client (cloudflared =
  QUIC-Proxy, kein ICMP). Die Merkregel "DHCP-geht-aber-Ping-nicht =
  ARP/Power-Save" ist damit widerlegt und entfernt; strukturell schliesst
  TP16.1 diese ICMP-Lücke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:16:21 +02:00

92 lines
4.7 KiB
Python

import os
import sys
from nautobot.core.settings import * # noqa F401,F403
from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
if DATABASES["default"]["ENGINE"].endswith("mysql"):
DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "05^)=zgn3ytglmp_iv4@era^e23u3#_wtop-@yeaeazempdy^=")
INSTALLATION_METRICS_ENABLED = is_truthy(os.getenv("NAUTOBOT_INSTALLATION_METRICS_ENABLED", "True"))
# --- BF-Netzwerk additions: Golden Config + Nornir (TP21) ---
#
# Extends the stock config shipped in the base ghcr.io/nautobot/nautobot image
# (unchanged above this point) instead of replacing it, so a rollback is just
# "point the compose file's image tag back at ghcr.io/nautobot/nautobot:3.1.7".
PLUGINS = [
"nautobot_plugin_nornir",
"nautobot_golden_config",
]
PLUGINS_CONFIG = {
"nautobot_plugin_nornir": {
"nornir_settings": {
# Per-device creds via each Device's SecretsGroup (ap1/ap2 -> "RouterOS
# Nornir" group, access_type=Generic to match this class's default --
# see get_device_creds()/_get_access_type_value() in nautobot_secrets.py).
# Switched 2026-07-22 from env_vars.CredentialsEnvVars (a single global
# NAPALM_USERNAME/PASSWORD pair for the whole fleet) so future devices
# (CRS326 etc.) can carry their own credentials.
"credentials": "nautobot_plugin_nornir.plugins.credentials.nautobot_secrets.CredentialsNautobotSecrets",
"runner": {"plugin": "threaded", "options": {"num_workers": 5}},
},
# nautobot-plugin-nornir 2.0+ dropped PLUGINS_CONFIG["dispatcher_mapping"]
# (it now errors on boot if present) -- per-platform dispatcher overrides
# moved onto the Platform model itself (network_driver_mappings). The
# ALLNET sg8316m custom driver will be wired in there once credentials
# are sorted, not here.
},
"nautobot_golden_config": {
# Backup + Intended-Config + Compliance enabled 2026-07-22 (TP21 follow-up
# session) once a Jinja template + Git repo target existed.
#
# Plan + Deploy enabled 2026-08-13 (TP22 Schritt 3), after backup scrubbing
# (ConfigReplace) and compliance were green on all four RouterOS devices.
# Two binding governance rules apply -- see docs/teilprojekte.md TP22:
# 1. Config Plans ONLY feature-/manual-scoped, never "full intended":
# a full push would write the literal `passphrase="<<REDACTED>>"` to the
# APs (kicking every WLAN client) and `private-key="<<REDACTED>>"` to the
# core (killing the WireGuard hub). Sections containing the mask are
# excluded from any push.
# 2. The AP template is never pushed line-by-line to a live device: it
# transiently sets vlan-filtering=no, which severs the vlan99 management
# path mid-session. Full re-provisioning goes via file upload + /import.
#
# enable_postprocessing stays OFF on purpose: render_secrets would not fix the
# compliance side (the stored intended would keep the un-rendered Jinja
# expression -> permanent false positive) and pushing PSKs has no use here.
"enable_backup": True,
"enable_compliance": True,
"enable_intended": True,
"enable_sotagg": True,
"enable_plan": True,
"enable_deploy": True,
"enable_postprocessing": False,
# REQUIRED for deploy on RouterOS (found the hard way, 2026-08-13):
# the default dispatcher resolves to NAPALM because Platform.network_driver_mappings
# carries `napalm: ros` -- but napalm-ros is a READ-ONLY driver. Backup (get_config)
# works, merge_config does not and fails with an empty error:
# NornirNautobotException: E1015: The method merge_config failed ... ``
# nornir-nautobot ships a RouterOS-specific Netmiko dispatcher whose merge_config
# pushes via netmiko_send_config -- pin it explicitly. Key = network_driver name.
"custom_dispatcher": {
"mikrotik_routeros": "nornir_nautobot.plugins.tasks.dispatcher.mikrotik_routeros.NetmikoMikrotikRouteros",
},
"postprocessing_callables": [],
"postprocessing_subscribed": [],
"per_feature_bar": True,
"per_feature_width": 0.13,
"per_feature_height": 4,
"default_deploy_status": "Not Approved",
"get_custom_compliance": None,
"sot_agg_transposer": None,
},
}
NAPALM_USERNAME = os.getenv("NAUTOBOT_NAPALM_USERNAME", "")
NAPALM_PASSWORD = os.getenv("NAUTOBOT_NAPALM_PASSWORD", "")
NAPALM_TIMEOUT = int(os.getenv("NAUTOBOT_NAPALM_TIMEOUT", "30"))