Security Testing
Tomaso Vasella
So nutzen Sie Web Services als Datenquellen für Splunk
Themen wie Microservices oder IoT wären ohne derartige Möglichkeiten niemals so populär geworden, wie sie es heute sind. Mit der rasant zunehmenden Menge von erzeugten und gespeicherten Daten geht meistens auch der Wunsch einher, diese Daten über standardisierte Schnittstellen auszutauschen, zu analysieren und nutzbringende Informationen zu extrahieren. APIs in Form von Web Services spielen bei der Maschine-zu-Maschine Kommunikation eine zentrale Rolle und immer mehr Daten und Informationen sind auf diesem Weg verfügbar.
Solche Datenquellen für Analysen, Korrelationen usw. zu nutzen, ist eine häufige Aufgabenstellung. Dieser Beitrag zeigt ein mögliches Vorgehen, um mit Hilfe einer dafür erstellten Splunk App Daten von webbasierten APIs zu beziehen.
Splunk ist ein populäres Tool, das Maschinendaten wie Logs, Metriken, usw. indexiert, speichert und visualisiert. Damit lassen sich umfangreiche Analysen und Korrelationen umsetzen und mittels Grafiken, Reports und Warnmeldungen anspruchsvolle Monitoring- und Reporting-Aufgaben lösen. Splunk lässt sich unter vielen modernen Betriebssystemen nutzen und kann mit einer Evaluierungslizenz während 60 Tagen mit vollem Funktionsumfang kostenlos genutzt werden.
Splunk besitzt von Haus aus viele Möglichkeiten, Daten von verschiedenen Quellen zu beziehen wie z.B. syslog über UDP/TCP oder das Lesen von Dateien mittels File Readern und Forwardern. Will man aber Daten von einer Schnittstelle beziehen, die nicht von Produkt bereits unterstützt wird oder vollständige Kontrolle über den Datenbezug ausüben, so braucht es eine entsprechende Erweiterung der Funktionalität. Dies wird meistens mit Hilfe einer App bzw. einem Add-on erreicht.
Für den Zugriff auf webbasierte APIs wie REST-Schnittstellen sind keine vorgefertigten Möglichkeiten in Splunk vorhanden, so dass diese Funktionalität hier entsprechend als App realisiert wird. Hierbei erweist sich das in verschiedenen Entwicklungssprachen verfügbare SDK als nützlich; im Folgenden wird die Version für Python verwendet.
Datenquellen werden in Splunk über sogenannte Data Inputs angesteuert. Dies sind im Wesentlichen Definitionen, mit welchem Protokoll und ggf. mit welchen Credentials auf Datenquellen zugegriffen wird. Eine App kann eigene Data Inputs definieren, welche anschliessend über das GUI konfiguriert werden können. Diese Methode wird häufig für API-basierte Datenquellen eingesetzt. Das SDK enthält Funktionen, welche das Erstellen solcher Data Inputs stark vereinfacht. Die hier verwendete Datenquelle ist die API der VulDB; die nachfolgenden Ausführungen lassen sich jedoch einfach verallgemeinern und somit analog auf andere Datenquellen anwenden.
Die einfachste Möglichkeit, mit dem Erstellen einer neuen App zu beginnen, bietet das Web GUI: Auf der Home Page auf Manage Apps
(Gear Icon) klicken und dann Create app
wählen. Für dieses Beispiel wurde als Name der App und des Verzeichnisses VulDB
gewählt. Mit dem Klick auf App erstellen
wird eine minimale App erzeugt, deren Verzeichnisstruktur im Pfad $SPLUNK_HOME/etc/apps/VulDB/
abgelegt wird:
bin/ README default/ app.conf data/ ui/ nav/ default.xml views/ README local/ app.conf metadata/ default.meta local.meta
Weiterführende Informationen zu den verschiedenen Ordnern und Dateien von Apps finden sich in der Dokumentation.
Die benötigen Dateien aus dem Python SDK werden im Unterordner bin
abgelegt. Es empfiehlt sich, nur diejenigen Ordner und Dateien des SDK nach bin
zu kopieren, die in der App auch tatsächlich verwendet werden. Im vorliegenden Fall wird der Ordner splunklib
aus dem SDK in das Verzeichnis bin/packages/
kopiert. Selbst entwickelte Scripts werden dann ebenfalls unter bin
abgelegt.
Für den Zugriff auf die Datenquelle sind ein Client (im vorliegenden Fall die Python Library requests), eine geeignete Verarbeitung der Daten (hier JSON) und deren anschliessende Speicherung und Indexierung in Splunk nötig.
Das Erstellen eines Modular Input (eine bestimmte Art Data Input) ist dafür prädestiniert und lässt sich mit Hilfe des SDK recht einfach umsetzen. Dazu muss ein entsprechendes Skript geschrieben werden, welches folgende drei Aktivitäten ausführt:
Siehe hierzu auch die Dokumentation und weiterführende Beispiele.
Als erstes wird eine von splunklib.modularinput.script
abgeleitete Klasse erstellt. Die genannten drei Schritte werden dann mit den dafür vorgesehenen Methoden get_scheme
, validate_input
und stream_events
implementiert. Zusätzlich muss die Methode __main__
definiert werden, welche das Skript ausführt.
Um programmatisch auf APIs zuzugreifen ist es oft sinnvoll, einen dedizierten API Client zu implementieren. Damit lassen sich die Details der API-Interaktion besser abstrahieren und modularisieren und Themen wie Session Handling und Error Handling können eleganter gehandhabt werden. Eine beispielhafte Methode eines API Clients könnte wie folgt aussehen:
@request_error_handler def get_latest_entries(self, latest=1000, details=0): post_data = {'format' : 'json', 'recent' : str(latest), 'details' : str(details)} return requests.post(self.url, data=post_data, headers=self.headers, proxies=self.proxies, verify=self.verify)
Die nachfolgenden Zeilen zeigen ein stark vereinfachtes Beispiel eines Skripts für einen modular Input anhand der erwähnten drei Methoden aus dem SDK:
import sys import os sys.path.insert(0, os.path.sep.join([os.path.dirname(__file__), 'packages'])) import splunklib.modularinput as mi import json class MyScript(Script): def get_scheme(self): scheme = mi.Scheme("VulDB") scheme.description = "Get information from VulDB, the number one vulnerability database." scheme.add_argument(mi.Argument(name="api_key", title="VulDB API Key", description="The key for accessing the VulDB API", data_type=mi.Argument.data_type_string, required_on_create=True, required_on_edit=False)) return scheme def validate_input(self, v): if 'api_key' in v.parameters: try: if not v.parameters['api_key'].isalnum(): raise ValueError('VulDB API key must be alphanumeric') except Exception as e: raise ValueError('VulDB API key must be alphanumeric') def stream_events(self, inputs, ew): for input_name, input_item in inputs.inputs.iteritems(): # res contains the response of a web request to the VulDB API # such as the above example for data in res.json()['result']: try: event = mi.Event() event.stanza = input_name event.data = json.dumps(data) ew.write_event(event) except Exception as e: ew.log('ERROR', 'An error has occurred writing data: {}'.format(e)) if __name__ == "__main__": sys.exit(MyScript().run(sys.argv))
Logging ist optional, wird aber wärmstens empfohlen. Dafür steht die Methode log()
mit verschiedenen Kritikalitätsstufen wie INFO
oder ERROR
zur Verfügung. Die Logmeldungen werden in die Datei splunkd.log
geschrieben.
Fragt ein API Client grosse Mengen an Daten ab, beispielsweise alle verfügbaren Daten in einem Zeitraum, kann es wünschenswert oder nötig sein, die Anfrage in Teilanfragen zu unterteilen, denn viele APIs schränken die Menge an Daten ein, die mit einem einzelnen Request angefragt werden kann. Um dies korrekt zu handhaben, wird in der Regel ein Paginierungsverfahren (Pagination) eingesetzt. Dabei gibt der API Client entsprechende Parameter in der Anfrage mit und definiert so, wo in der Sequenz der verfügbaren Daten die Anfrage beginnt (Seitennummer) und wie viele Daten ausgegeben werden sollen (Anzahl Datensätze pro Seite). Dies erfordert einerseits eine entsprechende Logik im Client und andererseits muss sich der Client merken, welche Datenpunkte er als letzte abgefragt hat, um in nachfolgenden Anfragen dort anzuknüpfen.
Häufig können APIs nur mit entsprechenden Schlüsseln verwendet werden. Solche Schlüssel sollten geschützt werden und nicht im Klartext in Skripts oder Konfigurationsdateien einsehbar sein. Splunk bietet die Möglichkeit sogenannter Storage Passwords. Das Passwort bzw. der API Key wird mit einem Schlüssel verschlüsselt, der sich auf dem Splunk Server befindet. Nur Benutzer mit der Rolle Admin können solche Passwörter einsehen, abgesehen von Systemadministratoren mit Zugriff auf das Dateisystem.
Nachfolgend sind Beispiele für das ablegen und auslesen solcher Credentials aufgeführt, wobei in diesem Beispiel als Benutzername der String api_key_label
verwendet wird.
def protect_key(self, key): try: for storage_password in self.service.storage_passwords: if storage_password.username == self.api_key_label: self.service.storage_passwords.delete(username = self.api_key_label) break self.service.storage_passwords.create(key, self.api_key_label) except Exception as e: raise Exception("An error occurred protecting key: {}".format(e)) def get_protected_key(self): try: for storage_password in self.service.storage_passwords: if storage_password.username == self.api_key_label: return storage_password.content.clear_password except Exception as e: raise Exception("An error occurred retrieving protected key: {}".format(e))
Nachdem das Skript fertiggestellt wurde, kann es in eine App integriert werden. Dazu sind folgende Schritte erforderlich:
app.conf
inputs.conf.spec
$SPLUNK_HOME/etc/apps/
Ausgehend von der oben erzeugten Ordnerstruktur wird das erstellte Modular Input Script in das Verzeichnis bin
kopiert, ebenso alle weiteren erstellten Scripts wie z.B. der oben erwähnte API Client. Die resultierende Verzeichnisstruktur sieht damit so aus:
bin/ packages/ splunklib/ VulDB.py VulDBApi.py README default/ app.conf data/ ui/ nav/ default.xml views/ README local/ app.conf metadata/ default.meta local.meta
Nun wird die Datei app.conf
angepasst, welche verschiedene Aspekte der App definiert.
[install] is_configured = 0 [ui] is_visible = 1 label = VulDB [launcher] author = VulDB description = VulDB version = 0.0.1
Die Konfiguration des Modular Input muss manuell erstellt werden. Als erstes muss dazu das Verzeichnis README
auf der gleichen Ebene wie bin erstellt werden. Darin wird nun die Datei inputs.conf.spec
erzeugt:
[VulDB://<name>] vuldb_lang = api_key = details =
Die Zeilen müssen den im Modular Input Script mit der Methode scheme.add_argument
definierten Parametern entsprechen. Weiterführende Infos finden sich in der Dokumentation.
Es resultiert damit folgende Verzeichnisstruktur:
bin/ packages/ splunklib/ VulDB.py VulDBApi.py README default/ app.conf data/ ui/ nav/ default.xml views/ README local/ app.conf metadata/ default.meta local.meta README/ inputs.conf.spec
Als letzter Schritt muss diese gesamte Verzeichnisstruktur unter $SPLUNK_HOME/etc/apps/
abgelegt werden. Damit ist die App und der enthaltene Modular Input installiert. Splunk muss neu gestartet werden, damit diese Erweiterungen im Web GUI sichtbar werden. Die App sollte nun im GUI erscheinen und der definierte Modular Input ist im Menu Settings unter Data Inputs zu finden.
Die in diesem Beispiel bezogenen Daten werden im JSON Format vom API zurückgeliefert. Damit Splunk die Daten korrekt indexieren kann, wird eine entsprechende Konfiguration definiert. Dies geschieht in der Datei props.conf
, die sich im Verzeichnis default/
befindet. Hier kann ausserdem definiert werden, wie der Zeitstempel aus den Daten extrahiert wird, sofern ein solcher in den Daten enthalten ist bzw. dieser verwendet werden soll.
[VulDB] TRUNCATE = 0 TIME_PREFIX = timestamp":.*create": TIME_FORMAT = %s TZ = UTC INDEXED_EXTRACTIONS = JSON
Als letzter Schritt wird nun im Web GUI eine neue Instanz des Modular Input erstellt und konfiguriert, entsprechend der im Skript und in der Datei inputs.conf.spec
definierten Parameter. Hier kann auch festgelegt werden, welcher Splunk Source Type und welcher Index für die Datenspeicherung verwendet werden soll. Sobald das Skript erfolgreich ausgeführt wurde, werden die bezogenen Daten indexiert und gespeichert und stehen für die weitere Verwendung zur Verfügung.
Die Kommunikation mit webbasierten APIs und deren Verwendung als Datenquellen für Analysen ist ein gefragter Anwendungsfall. Mit vergleichsweise geringem Aufwand lassen sich mit Splunk und dem zugehörigen SDK Lösungen in Form von Apps dafür entwickeln. Die native Unterstützung gängiger Datenformate wie JSON durch Splunk und die Verfügbarkeit leistungsfähiger Bibliotheken für den Webzugriff erweisen sich dabei als sehr nützlich. Es empfiehlt sich in der Regel, für die Kommunikation mit dem API und die Fehlerbehandlung einen dedizierten API Client zu implementieren. Ausserdem muss darauf geachtet werden, Credentials bzw. API Keys angemessen zu schützen und eventuelle Besonderheiten des angesprochenen APIs wie Paginierung zu berücksichtigen. Sobald die Daten in Splunk indexiert und gespeichert sind, stehen sie für Analysen, Korrelationen und Visualisierungen zur Verfügung. Das beschriebene Vorgehen lässt sich analog auch für andere Datenquellen verwenden und bietet somit eine rasch realisierbare und allgemein nützliche Lösung für Datenanalysen.
Unsere Spezialisten kontaktieren Sie gern!
Tomaso Vasella
Tomaso Vasella
Tomaso Vasella
Tomaso Vasella
Unsere Spezialisten kontaktieren Sie gern!