Werkzeuge zur Parser-Generierung gehören seit Urzeiten zum Inventar der Unix-Betriebssysteme. Im praktischen Einsatz geben sich mächtige Tools wie Bison und Yacc aber leider oft sperrig und sind daher hauptsächlich in großen Projekten zu finden. Im Kielwasser der modellgetriebenen Entwicklung sind jedoch in den letzten Jahren alternative Werkzeuge entstanden, die sich durchaus auch für kleinere Aufgaben außerhalb der klassischen Software-Entwicklung verwenden lassen.
Firewall nach Modell
Mit Xtext [1] stellt dieser Artikel ein Werkzeug zur Erstellung Domänen-spezifischer Sprachen (DSL) vor. Als Beispiel erarbeitet er eine Sprache für Firewallregeln und produziert schließlich mit Hilfe eines Generators ein IPtables-Skript.
Naturgemäß lässt sich die Komplexität eines flexiblen Konzepts wie IPtables durch Ändern der Beschreibungsform nicht verringern. Allenfalls erhält man eine ähnlich komplexe, aber besser lesbare Konfiguration. Allein das bedeutet aber bei mancher Software schon einen nicht zu verachtenden Fortschritt.
Einen echten Mehrwert kann allerdings erzielen, wer die für den gewünschten Einsatzzweck invarianten Bestandteile identifiziert und fest in den Generator einbaut. Dadurch muss die Sprache nur noch die eigentlichen Nutzinformationen aufnehmen und lässt sich schneller erstellen und erlernen. Gleichzeitig senkt dieses Vorgehen die Wahrscheinlichkeit von Fehlbedienungen.
Um das Modellieren einer Firewall etwas einfacher zu gestalten, gelten für das Beispiel in diesem Artikel die im Kasten "Annahmen für die Firewall" formulierten Einschränkungen. Die hier beschriebene Lösung kann sicher nicht mit spezialisierten Tools wie Ferm [2] konkurrieren. Anhand dieses Beispiels lässt sich aber die Arbeit mit Xtext gut erläutern. Oft hilft es, sich zu Beginn der Arbeit einen Grobentwurf der gewünschten Sprache zu erstellen. Anhand dieser Skizze fällt es dann leichter, die Grammatik zu schreiben, die zur Grundlage aller weiteren Schritte wird.
Annahmen für die Firewall
- Es handelt sich um eine einfache IPv4-Firewall zwischen Standorten.
- Die Firewall hat je genau ein internes und ein externes Interface.
- Rechner, Netze und Dienste sollen durch symbolische Namen identifiziert werden.
- Alle beteiligten Netzbereiche sind disjunkt, NAT findet nicht statt.
- Es werden nur TCP- und UDP-Regeln auf Port-Basis erstellt.
- Nur explizit konfigurierte Verbindungen zwischen den angegebenen Standorten sind zugelassen.
- Externe Zugriffe auf die Firewall sind verboten, interne Zugriffe sind erlaubt.
Abbildung 1 zeigt ein Beispiel der geplanten Firewall-Sprache. Sie enthält die Namen für Skript und Netzwerk-Schnittstellen, die symbolischen Namen für Rechner und Netze sowie die Dienste-Definitionen. Den letzten Teil bilden die Regeln für ein- und ausgehende Verbindungen. So aufgeschrieben ist die Konfiguration der Firewall auch ohne tiefe Kenntnis der IPtables-Syntax verständlich.
Abbildung 1: Ein Prototyp der gewünschten Firewall-Sprache dient als Grundlage für die Arbeit mit Xtext.
Grammatik
Listing 1 bildet die Grundlage der Grammatik und sieht einer um Steuerinformationen angereicherten erweiterten Backus-Naur-Form ähnlich. Die beiden Kopfzeilen hat Xtext automatisch erzeugt. Die erste Produktionsregel der Grammatik legt das Wurzelelement der künftigen Eingabedateien fest. Aus dieser Regel entsteht später ein Elementtyp namens »Model«
.
01 grammar org.myfirewall.MyFirewall with org.eclipse.xtext.common.Terminals
02 generate myFirewall "http://www.myfirewall.org/MyFirewall"
03
04 Model:
05 scripts+=Script*;
06
07 Script:
08 'script' name=STRING '{'
09 'interfaces internal' ifInternal=ID 'external' ifExternal=ID ';'
10 hosts+=HostAlias*
11 networks+=NetworkAlias*
12 services+=ServiceDefinition*
13 ('allow incoming {' incomingRules+=Rule+ '}')?
14 ('allow outgoing {' outgoingRules+=Rule+ '}')?
15 '}';
16
17 IPAddress:
18 byte1=INT '.' byte2=INT '.' byte3=INT '.' byte4=INT;
19
20 enum Protocol:
21 TCP='tcp' | UDP='udp';
22
23 HostAlias:
24 'host' name=ID '=' ip=IPAddress ';';
25
26 NetworkAlias:
27 'net' name=ID '=' ip=IPAddress '/' cidrSuffix=INT ';';
28
29 Endpoint:
30 ('host' host=[HostAlias]) | ('net' network=[NetworkAlias]);
31
32 ServiceDefinition:
33 'service' name=ID '='
34 ('proto' (protocols+=Protocol |
35 '(' protocols+=Protocol (',' protocols+=Protocol)+ ')'))?
36 ('port' (ports+=INT |
37 '(' ports+=INT (',' ports+=INT)+ ')'))? ';';
38
39 Rule:
40 source=Endpoint 'to' destination=Endpoint ':'
41 services+=[ServiceDefinition] (',' services+=[ServiceDefinition])* ';';
Zeile 5 bewirkt, dass der Typ ein Attribut »scripts«
erhält, das aus beliebig vielen »Script«
-Elementen besteht. Die »Script«
-Produktionsregel ab Zeile 7 legt die Syntax dieses Elements fest: das Schlüsselwort »script«
gefolgt vom Namen des Skripts. Auch hier bewirkt die Notation »name =STRING«
, dass der Typ »Script«
später ein Attribut »name«
zur Aufnahme von Zeichenketten enthält.
Da hier aber kein Quantor wie »*«
angegeben ist, kann dieses Attribut anders als »scripts«
nur genau einen Namen aufnehmen. Die Zeilen 9 bis 12 enthalten keine wesentlichen Neuerungen bis auf den Typ »ID«
, der einen (Java-)Bezeichner ohne Anführungszeichen darstellt, wogegen »STRING«
-Attribute in Anführungszeichen angegeben sind. Die Zeilen 13 und 14 enthalten jeweils Gruppen: Die runden Klammern in Verbindung mit dem Quantor »?«
erlauben es, die gesamte Regelgruppe für ein- oder ausgehende Verbindungen wahlweise anzugeben oder auszulassen.
Die Zeilen 17 und folgende definieren einen Typ zur Angabe von IPv4-Adressen. Die Zeilen 20 und 21 beschreiben einen Aufzählungstyp zur Angabe des Protokolls. Dabei sind »TCP«
und »UDP«
die sprachintern verwendeten Werte, die Literale »tcp«
und »udp«
werden bei der Ein- und Ausgabe verwendet. Die Zeilen 23 bis 27 definieren einfache zusammengesetzte Typen zur Angabe symbolischer Rechner- und Netzwerknamen. Zeile 30 zeigt eine Besonderheit von Xtext: »[Typname]«
definiert einen Verweis auf Elemente dieses Typs.
Die Produktionsregel »Endpoint«
enthält eine weitere Besonderheit: Die Literale »host«
und »net«
sind eine Konzession an den Parser, der ansonsten nicht zwischen einem Verweis auf einen Rechner oder auf ein Netzwerk (jeweils in Form einer ID) unterscheiden könnte und bei der Generierung eine entsprechende Warnung ausgeben würde.
Die Definition eines Dienstes ab Zeile 32 mag zunächst unübersichtlich wirken, enthält aber nichts wesentlich Neues. Protokolle und Portnummern lassen sich entweder als einfacher Wert oder als kommaseparierte Werteliste in Klammern angegeben, daher die etwas umfangreichere Regel. Bei der Definition einer Regel ist ein Unterschied zwischen den Zeilen 40 und 41 wichtig: Während die Dienste-Definitionen in eckigen Klammern stehen und daher referenziert werden, sind die Endpunkte als direkte Attribute in die Regel eingefügt. Die Grammatik ist schon so gut wie alles, was man für eine erste Version des Editors benötigt. Wer sie praktisch mit Eclipse und Xtext-Tools umsetzen möchte, findet im Kasten "Xtext installieren" Hinweise.
Voraussetzung für die Installation von Eclipse und Xtext ist lediglich eine Java-Laufzeitumgebung, wobei sich mindestens Version 5 empfiehlt. Leider gibt es mit den in den gängigen Distributionen enthaltenen Eclipse-Paketen immer wieder Probleme – vor allem sind sie in der Regel veraltet. Zu empfehlen ist dagegen das Paket "Eclipse Modeling Tools" von [3].
Nach dem ersten Start sind über »Help | Install Modeling Components«
die Pakete Xpand und Xtext nachzuinstallieren. Nach der Installation stellt man in den globalen Einstellungen unter »General | Workspace«
die Standardkodierung von Textdateien auf UTF-8 um, damit es keine Schwierigkeiten mit den von Xtext verwendeten Guillemets («») gibt. Um die Übersicht nicht zu verlieren, ist es außerdem ratsam, im Package Explorer die Option »Link with Editor«
(das Icon mit den zwei Pfeilen) zu aktivieren.
Der Befehl «File | New Project | Xtext Project« legt neben dem Hauptprojekt auch noch ein Projekt zur Aufnahme des generierten Editors mit dem Suffix ».ui«
an. Außerdem erzeugt er in der Voreinstellung ein Generator-Projekt, das später benötigt wird. Xtext kann etwas empfindlich auf ungewöhnliche Schreibweisen reagieren, deshalb sollten Einsteiger als Projektnamen »org.myfirewall«
und als Sprachnamen »org.myfirewall.MyFirewall«
wählen. Der Wizard öffnet nach der Anlage der Projekte die Xtext-Datei, die die Beschreibung der Grammatik aus Listing 1 aufnimmt.