Wie wir Prefix-Policies mit IRRd, Ansible und MikroTik RouterOS als kontrollierten Change gebaut haben

„Das läuft doch seit Jahren. Warum sollten wir daran etwas ändern?“

Diesen Gedanken kann man bei BGP-Prefix-Filtern gut nachvollziehen. Eine einmal sauber aufgebaute Address-List funktioniert im laufenden Betrieb oft über sehr lange Zeiträume – bis sich auf der Gegenseite etwas ändert.

Ein Kunde registriert ein zusätzliches Prefix. Ein Peer ergänzt ein neues Route-Objekt. Ein AS-SET wächst. Oder ein Prefix wird aus dem IRR entfernt.

Die technische Änderung ist meistens klein. Der operative Aufwand dahinter ist es nicht.

Man muss die Quelle prüfen, Prefixe validieren, mehrere Router konsistent aktualisieren, mögliche Auswirkungen auf bestehende BGP-Sessions berücksichtigen und den Vorgang sauber dokumentieren.

Bei einem Router ist das überschaubar. Mit jedem weiteren Kunden, Peering und Edge-Router wird aus einer kleinen Konfigurationsänderung ein Prozessproblem.

Genau diesen Ablauf haben wir deshalb automatisiert.

Das Ergebnis ist eine Ansible-basierte Lösung, die:

  • IRR-Daten über die IRRd-GraphQL-API abfragt
  • Prefixe technisch und organisatorisch validiert
  • RouterOS-Address-Lists auf mehreren MikroTik-Routern abgleicht
  • Änderungen kontrolliert ausführt
  • bei Bedarf betroffene BGP-Verbindungen neu initialisiert
  • pro Lauf genau ein Ticket erzeugt
  • und auch bei „keine Änderungen“ einen vollständigen Betriebsnachweis liefert

Der interessante Teil war dabei nicht, ein Prefix auf einen Router zu schreiben.

Der interessante Teil war, den gesamten Prozess so zu bauen, dass er auch bei fehlerhaften Daten, teilweise ausgeführten Änderungen oder einer ausgefallenen BGP-Session nachvollziehbar und sicher bleibt.

Die gezeigten Beispiele stammen aus unserer produktiven RouterOS-7-Infrastruktur. AS-Nummern, Prefixe, Listennamen und weitere Details wurden abstrahiert oder durch Dokumentationsbereiche ersetzt.

Ausgangslage

Wir betreiben mehrere MikroTik-RouterOS-7-Systeme als BGP-Edges.

Auf den Routern werden Address-Lists unter anderem für folgende Aufgaben genutzt:

  • Kunden-Prefixe
  • bilaterale Peerings
  • Prefix-Filter
  • input.accept-nlri
  • Routing-Policies
  • RTBH-Freigaben
  • weitere sicherheitsrelevante Filter

Für einen Kunden oder Peer existiert dabei typischerweise eine eigene Address-List, zum Beispiel:

Copy to Clipboard

Diese Listen wurden bisher manuell gepflegt.

Das funktioniert, solange Änderungen selten sind. Mit zunehmender Anzahl an Kunden, Peerings und Routern entstehen aber typische Probleme:

  • Die Liste auf Router A ist aktuell, auf Router B nicht.
  • Ein Prefix ist im IRR registriert, im Filter aber noch nicht enthalten.
  • Ein veraltetes Prefix bleibt auf dem Router bestehen.
  • Eine Änderung wird durchgeführt, aber nicht dokumentiert.
  • Ein Fehler tritt nach dem Schreiben auf, obwohl der eigentliche Eintrag bereits angelegt wurde.
  • Ein automatischer Job läuft nicht, aber niemand bemerkt es.
  • Eine Address-List ändert sich, wird von input.accept-nlri aber erst nach einem BGP-Neustart berücksichtigt.

Aus einer kleinen Konfigurationsänderung wird so schnell ein Prozessproblem. Und Prozessprobleme fallen im Netzbetrieb meist nicht sofort auf, sondern erst dann, wenn ein Kunde etwas nicht erreicht.

Das Zielbild: ein kontrollierter Change, kein blinder Write

Die Automatisierung sollte nicht einfach IRR-Daten auf Router schreiben.

Sie sollte sich wie ein kontrollierter Change verhalten.

Der gewünschte Ablauf war:

Copy to Clipboard

Zusätzlich sollte ein Lauf ohne Änderungen ebenfalls ein Ticket erzeugen.

Das war eine bewusste Entscheidung.

Ein fehlendes Ticket bedeutet dadurch nicht „es gab nichts zu tun“, sondern „der Lauf hat möglicherweise gar nicht stattgefunden“.

Ein Ticket mit:

Copy to Clipboard

ist deshalb kein Rauschen, sondern ein technischer Betriebsnachweis.

Die Datenquelle: IRRd per GraphQL

Als Quelle nutzen wir IRRd.

Die Abfrage erfolgt nicht über klassischen WHOIS-Text, sondern über die GraphQL-Schnittstelle.

Das hat mehrere Vorteile:

  • strukturierte Antworten
  • eindeutige Fehlererkennung
  • saubere Trennung zwischen ASN- und AS-SET-Abfragen
  • einfache Weiterverarbeitung in Ansible
  • keine fehleranfällige Textauswertung

Wir unterstützen zwei Objekttypen:

Copy to Clipboard

und

Copy to Clipboard

Eine anonymisierte Policy sieht beispielsweise so aus:

Copy to Clipboard

Für ein AS-SET entsprechend:

Copy to Clipboard

Das Feld:

Copy to Clipboard

war dabei besonders hilfreich.

Damit konnten wir AS-SET-Abfragen und größere Datenmengen vollständig testen, ohne die Ergebnisse tatsächlich auf einen Router zu schreiben. Die Policy wird abgefragt und validiert, aber nicht angewendet.

Warum Validierung wichtiger ist als die API-Abfrage

Ein erfolgreicher HTTP-Status bedeutet noch lange nicht, dass ein Ergebnis für den Produktivbetrieb geeignet ist.

Deshalb prüfen wir vor jedem Write unter anderem:

  • Ist das Ergebnis überhaupt vorhanden?
  • Ist die Anzahl der Prefixe plausibel?
  • Handelt es sich ausschließlich um IPv4-CIDRs?
  • Ist die Präfixlänge zulässig?
  • Ist 0.0.0.0/0 enthalten?
  • Sind private Netze enthalten?
  • Sind Loopback-, Link-Local- oder Multicast-Bereiche enthalten?
  • Überschreitet die Antwort ein definiertes Maximum?
  • Ist die Antwort ungewöhnlich klein?

Eine typische Begrenzung sieht so aus:

Copy to Clipboard

Damit verhindern wir beispielsweise, dass ein fehlerhaftes IRR-Objekt plötzlich tausende Prefixe in eine Kundenliste schreibt.

Die maximale Prefixanzahl ist keine technische Komfortfunktion.

Sie ist ein Schutzmechanismus gegen fehlerhafte oder unerwartete Quelldaten.

Soll-Ist-Vergleich auf dem Router

Die Router werden über die native RouterOS-API angesprochen.

Zunächst lesen wir die bestehenden Address-List-Einträge:

Copy to Clipboard

Danach werden zwei Mengen berechnet:

Copy to Clipboard

Ein Beispiel:

Copy to Clipboard

Das Ergebnis lautet:

Copy to Clipboard

Dadurch bleibt der Prozess idempotent.

Ein zweiter Lauf mit demselben Datenstand verändert nichts mehr.

Genau das konnten wir anschließend im produktiven Test sehen:

Copy to Clipboard

Beide Router meldeten:

Copy to Clipboard

Das ist das gewünschte Verhalten einer sauberen Infrastrukturautomatisierung.

Additions immer vor Removals

Ein wichtiges Designprinzip war die Reihenfolge.

Neue Prefixe werden immer zuerst hinzugefügt. Erst danach werden veraltete Einträge entfernt.

Damit reduzieren wir das Risiko, bei einem teilweise fehlerhaften Lauf die gültige Erreichbarkeit zu beeinträchtigen.

Der Ablauf lautet:

Copy to Clipboard

Und ausdrücklich nicht:

Copy to Clipboard

Ein vollständiges Löschen und Neuerstellen wäre einfacher zu programmieren, betrieblich aber deutlich riskanter.

Gerade wenn Address-Lists direkt in Routing-Filtern oder in input.accept-nlri verwendet werden, kann ein kurzer leerer Zustand bereits Auswirkungen haben.

Eine Besonderheit von input.accept-nlri

Eine der Address-Lists wurde auf einem Router als input.accept-nlri verwendet.

Das bedeutet: Die Liste wirkt nicht erst innerhalb einer nachgelagerten Routing-Filterregel, sondern bereits beim Empfang der NLRI.

Eine Änderung an der Address-List wird dadurch nicht zwingend sofort auf eine bestehende BGP-Session angewendet. Die betroffene BGP-Verbindung muss neu initialisiert werden.

Dafür wurde die Policy erweitert:

Copy to Clipboard

Damit ist der Neustart nicht pauschal auf allen Routern aktiv.

Er erfolgt nur:

  • bei einer tatsächlichen Prefixänderung
  • auf dem definierten Router
  • für eine passende BGP-Connection
  • wenn die Connection administrativ aktiviert ist

Das war wichtig, weil wir keine absichtlich deaktivierte BGP-Connection automatisch wieder einschalten wollten.

Die erste Hürde: BGP-Session ist kein gültiger API-Pfad

Der erste Ansatz war, die aktive Session direkt über:

Copy to Clipboard

abzufragen.

In der RouterOS-CLI existiert diese Sicht. Das verwendete Ansible-Modul akzeptierte diesen Pfad für api_info allerdings nicht.

Die Lösung war, nicht die dynamische Session zu verwalten, sondern die zugrunde liegende statische Konfiguration:

Copy to Clipboard

Das ist ohnehin der bessere Ort für einen kontrollierten Neustart.

Der Ablauf wurde deshalb geändert zu:

Copy to Clipboard

Damit funktioniert der Neustart auch dann, wenn die Session zum Zeitpunkt des Runs bereits down ist. Eine bestehende Session ist dafür nicht erforderlich.

Die zweite Hürde: RouterOS .id und Ansible

RouterOS liefert interne Objekt-IDs mit einem Schlüssel wie:

Copy to Clipboard

Naheliegend wäre in Ansible:

Copy to Clipboard

Das funktioniert allerdings nicht zuverlässig.

Ansible interpretiert den führenden Punkt als Teil eines Attributpfades und versucht dabei, auf ein leeres Attribut zuzugreifen. Das Ergebnis war:

Copy to Clipboard

Die Lösung war der direkte Dictionary-Zugriff:

Copy to Clipboard

Die IDs werden deshalb in einer Schleife gesammelt:

Copy to Clipboard

Das ist ein kleines Detail. Aber genau solche Details entscheiden darüber, ob eine Automatisierung im Labor oder im Produktionsbetrieb funktioniert.

Fehlerbehandlung mit block und rescue

Ein weiterer wichtiger Punkt war die Behandlung teilweise ausgeführter Runs.

Ein Beispiel aus dem Test:

  • Das Prefix wurde erfolgreich auf beide Router geschrieben.
  • Der anschließende BGP-Neustart schlug fehl.
  • Die Automatisierung durfte jetzt nicht behaupten, es sei nichts passiert.
  • Gleichzeitig durfte sie den bereits angelegten Eintrag nicht ignorieren.

Deshalb läuft jede Policy innerhalb eines Ansible-Blocks:

Copy to Clipboard

Dadurch kann das Ticket auch bei einem Fehler enthalten:

Copy to Clipboard

Das ist wesentlich aussagekräftiger als ein pauschales:

Copy to Clipboard

Ein Ticket pro Run

Wir wollten bewusst kein Ticket pro Router und kein Ticket pro Policy. Stattdessen entsteht genau ein Ticket für den gesamten Lauf.

Das Ticket enthält:

  • Startzeit
  • Endzeit
  • IRRd-Status
  • verwendete Quellen
  • Anzahl der Additions
  • Anzahl der Removals
  • Ergebnis je Router
  • Ergebnis je Policy
  • betroffene Address-List
  • durchgeführter BGP-Neustart
  • Fehlermeldungen
  • nicht angewendete Test-Policies

Die Titel werden dynamisch erzeugt.

Ohne Änderungen:

Copy to Clipboard

Mit Änderungen:

Copy to Clipboard

Bei Fehlern:

Copy to Clipboard

Die Ticketerstellung erfolgt über einen HTTP-POST auf einen Webhook.

Wichtig war die Reihenfolge:

Copy to Clipboard

So bleibt der Job bei Problemen technisch rot, aber die Dokumentation wird trotzdem erstellt.

Der letzte Task lautet sinngemäß:

Copy to Clipboard

Bei einem erfolgreichen Lauf wird dieser Task übersprungen. Bei einem Fehler ist das Ticket bereits vorhanden, bevor Ansible mit einem Fehlercode endet.

Warum auch „keine Änderungen“ ein Ticket erzeugt

Man könnte argumentieren, dass ein No-Change-Run kein Ticket benötigt.

Für uns war die Nachvollziehbarkeit wichtiger.

Ein tägliches Ticket mit:

Copy to Clipboard

beweist:

  • der Timer wurde ausgelöst
  • der Controller war funktionsfähig
  • IRRd war erreichbar
  • die Policies wurden verarbeitet
  • beide Router waren erreichbar
  • die API-Anmeldung funktionierte
  • der Soll-Ist-Vergleich wurde durchgeführt
  • es bestand kein Handlungsbedarf

Ohne dieses Ticket müsste man bei fehlenden Änderungen zusätzlich prüfen, ob der Job überhaupt gelaufen ist.

Die Automatisierung erzeugt damit nicht nur Changes, sondern auch einen täglichen Betriebsnachweis.

Der systemd-Timer

Nach den erfolgreichen manuellen Tests wurde das Playbook über einen systemd-Timer produktiv geschaltet.

Der Service läuft als dedizierter Benutzer:

Copy to Clipboard

Zusätzlich nutzen wir flock, um parallele Läufe zu verhindern.

Der Wrapper startet sinngemäß:

Copy to Clipboard

Damit kann ein verzögerter Lauf nicht mit dem nächsten Zeitplan kollidieren.

Die nächste Hürde: systemd-Hardening und Ansible-Temp-Dateien

Der Service wurde zunächst mit:

Copy to Clipboard

gehärtet.

Das führte beim ersten Start zu:

Copy to Clipboard

Ansible benötigt sowohl ein lokales als auch ein „Remote“-Temp-Verzeichnis. Das gilt auch dann, wenn Tasks auf localhost ausgeführt werden.

Die Lösung war:

Copy to Clipboard

Zusammen mit:

Copy to Clipboard

erhält der Service einen eigenen beschreibbaren Temp-Bereich, während das Home-Verzeichnis weiterhin read-only bleiben kann.

Danach endete der Service sauber mit:

Copy to Clipboard

und

Copy to Clipboard

Das wirkt auf den ersten Blick ungewöhnlich, ist bei einem erfolgreichen Type=oneshot-Service aber genau richtig. Der Job läuft, beendet sich erfolgreich und bleibt nicht dauerhaft aktiv.

Der finale Ablauf

Heute arbeitet die Automatisierung folgendermaßen:

Copy to Clipboard

Bei unverändertem Zustand:

Copy to Clipboard

Bei erfolgreichen Änderungen:

Copy to Clipboard

Bei einem Fehler:

Copy to Clipboard

Was wir bewusst nicht automatisiert haben

Nicht jede Address-List sollte ungeprüft vollständig aus dem IRR verwaltet werden.

Gemeinsam genutzte Listen, in denen statische eigene Prefixe und dynamisch ermittelte Kundenprefixe gemischt sind, benötigen eine andere Logik. Dort wäre ein einfaches Soll-Ist-Verfahren gefährlich.

Für solche Listen braucht es beispielsweise:

  • getrennte statische und dynamische Listen
  • eine kontrollierte Merge-Logik
  • eindeutige Kommentare oder Ownership-Marker
  • Schutz vor dem Löschen manuell verwalteter Einträge

Automatisierung sollte nicht bedeuten, möglichst viel automatisch zu verändern.

Sie sollte bedeuten, klar definierte Zuständigkeiten zuverlässig umzusetzen.

Die wichtigsten Punkte zusammengefasst

  1. Der Router-Write ist der kleinste Teil Ein Prefix auf einen Router zu schreiben, ist trivial. Die eigentliche Arbeit steckt in Datenquelle, Validierung, Fehlerbehandlung und Nachweis.
  2. Ein erfolgreicher API-Status ist kein Freibrief HTTP 200 sagt nichts über die Qualität der Daten. Erst die Validierung entscheidet, ob ein Ergebnis produktiv verwendet werden darf.
  3. Eine maximale Prefixanzahl ist ein Schutzmechanismus max_prefixes verhindert, dass ein fehlerhaftes IRR-Objekt tausende Einträge in eine Kundenliste schreibt.
  4. Additions gehören vor Removals Zuerst hinzufügen, dann entfernen. Das reduziert das Risiko, bei einem Teilfehler gültige Erreichbarkeit zu verlieren.
  5. input.accept-nlri braucht einen gezielten BGP-Neustart Eine geänderte Address-List wirkt nicht automatisch auf eine bestehende Session. Der Neustart muss kontrolliert und nur dort erfolgen, wo er nötig ist.
  6. Teilfehler müssen dokumentiert werden block und rescue sorgen dafür, dass ein Ticket auch bei einem Fehler zeigt, was tatsächlich passiert ist – und nicht nur „Automation failed“.
  7. Auch ein No-Change-Run braucht ein Ticket Ein „keine Änderungen“-Ticket ist der tägliche Beweis, dass der Job wirklich gelaufen ist. Kein Ticket ist ein Alarmsignal, keine Entwarnung.
  8. Erst das Ticket, dann der rote Exit-Code Bei Fehlern muss die Dokumentation entstehen, bevor der Job abbricht. Sonst fehlt genau bei Problemen der Nachweis.

Fazit

Der eigentliche Router-Write war der kleinste Teil dieser Lösung.

Die entscheidenden Fragen waren:

  • Woher kommen die Daten?
  • Wie werden sie validiert?
  • Wie verhindern wir unplausible Änderungen?
  • Was passiert bei einem Teilfehler?
  • Wie gehen wir mit einer bereits ausgeführten Addition um?
  • Wann muss eine BGP-Verbindung neu gestartet werden?
  • Was passiert, wenn die Session bereits down ist?
  • Wie dokumentieren wir einen erfolgreichen Leerlauf?
  • Wie stellen wir sicher, dass der Job täglich wirklich läuft?

Erst durch diese Punkte wurde aus einem Script eine belastbare Betriebsautomatisierung.

Für uns ist das der Unterschied zwischen:

Copy to Clipboard

und:

Copy to Clipboard

Der erste Ansatz spart ein paar Befehle.

Der zweite schafft Konsistenz, Nachvollziehbarkeit und einen reproduzierbaren Betrieb.