Zum Inhalt springen
Avanet

Sophos Fusion Data Lake per Search GraphQL API abfragen

Mit der Search GraphQL API v1 lässt sich der historische Sophos Fusion Data Lake aus einer eigenen Anwendung mit QL durchsuchen. Der Ablauf ist asynchron: Suche starten, ihre Search-URN behalten, den Abschluss und das Ergebnis der Ausführung prüfen und anschliessend alle Ergebnisseiten lesen. COMPLETE allein bedeutet nicht, dass alle Teile der Suche erfolgreich waren.

Die API ist seit 12. Oktober 2026 veröffentlicht. Sie ergänzt bestehende APIs und erzwingt keinen sofortigen Wechsel von der weiterhin verfügbaren XDR Query API. Sie ist auch kein Ersatz für Live Endpoint Search (LES): LES fragt verbundene Endpoints mit osquery-artigem SQL ab. Die Abgrenzung zwischen Classic Live Discover, Data Lake Search und LES hilft bei der Wahl des richtigen Datenpfads.

Vor dem ersten Request

Benötigt werden ein Tenant mit den gesuchten historischen Daten, passende API Credentials, die eindeutige Tenant-UUID und ein HTTP-Client, der JSON-POST-Requests senden und Antworten vollständig speichern kann. Für das Beispiel muss der Tenant Prozessereignisse enthalten. Ohne diese Daten kann eine technisch erfolgreiche Suche leer bleiben.

API-Zugangsdaten sicher verwalten erklärt die Maschinenidentität, den OAuth-Flow mit client_credentials, die Tenant-Zuordnung und die Rotation. Ein Partner- oder Organisations-Identifier ist keine Tenant-UUID. Bei mehreren Tenants wird der Ziel-Tenant anhand einer freigegebenen eindeutigen Zuordnung gewählt, nicht allein anhand seines Anzeigenamens.

Die genaue minimale Rolle und die erforderlichen Entitlements für den konkreten Search-Client müssen vor dessen produktiver Freigabe geklärt werden. Aus dem Namen einer allgemeinen Service-Principal-Rolle folgt keine bestätigte Search-Berechtigung. Bei einer Ablehnung deshalb nicht vorsorglich Super Admin vergeben.

Vor dem Start legt man die Untersuchungsfrage, den Zeitraum und die benötigten Felder fest. Prozessdaten können Benutzernamen, Pfade und Kommandozeilen mit vertraulichen Inhalten enthalten. Abfragen und Exporte gehören in eine freigegebene Umgebung mit geregelten Zugriffs- und Aufbewahrungsrechten. Token und Client Secret dürfen weder in Request-Logs noch in Tickets oder gespeicherten Beispieldateien erscheinen. Auch die vollständigen Ergebniszeilen werden nicht unkontrolliert protokolliert.

Den API-spezifischen Zielhost verwenden

Für die folgenden Operationen gilt ein gemeinsamer Request-Vertrag:

POST https://api.taegis.sophos.com/graphql
Authorization: Bearer <access-token>
X-Tenant-ID: <tenant-uuid>
X-GraphQL-Schema: default
Content-Type: application/json

Im HTTP-Client wird <access-token> durch das aktuelle OAuth-Access-Token und <tenant-uuid> durch die geprüfte UUID ersetzt. Für jeden der folgenden JSON-Bodies bleiben URL und Header gleich. Die Beispiele sind Request-Vorlagen, keine hier ausgeführten Tenant-Tests oder aufgezeichneten Erfolgsausgaben.

Der regionale REST-Host aus apiHosts.dataRegion ist nicht der Zielhost dieses Search-Vertrags. Umgekehrt darf man bestehende REST-Clients nicht global auf den GraphQL-Host umstellen. Aus diesem Hostnamen ergibt sich auch keine Zusage zum Speicherort oder zur Datenresidenz; solche Anforderungen werden anhand der geltenden Tenant- und Vertragsvorgaben geklärt.

Eine Suche starten und eindeutig verfolgen

Einen neuen Lauf mit createSearch anlegen

Die folgende Vorlage sucht Prozessereignisse der letzten Stunde und beschränkt die ausgewählten Ereignisfelder. -1h ist ein relatives Zeitfenster für einen ersten Funktionscheck; für einen späteren Vergleich mit einem anderen Client werden identische explizite Zeitgrenzen festgelegt.

{
  "operationName": "CreateSearch",
  "query": "mutation CreateSearch($input: CreateSearchInput) { createSearch(input: $input) { urn status } }",
  "variables": {
    "input": {
      "text": "from process where earliest = -1h | fields host_id, commandline, image_path"
    }
  }
}

Vor dem Absenden müssen Tenant, Zeitraum und Datenumfang stimmen. createSearch erzeugt und startet einen neuen Suchlauf, verändert aber keine Endpoint-Schutzkonfiguration. Die Ausführung kann Arbeit im Dienst und sensible Ergebnisse erzeugen. Bei einem Clientfehler deshalb nicht automatisch eine unbegrenzte Serie neuer Suchen starten.

Nach dem Request zuerst HTTP-Status und das oberste GraphQL-errors-Array auswerten. Auch HTTP 200 kann GraphQL-Fehler enthalten. Erst nach dieser Prüfung data.createSearch.urn sichern. Die zurückgegebene URN wird unverändert für Status und Ergebnisabrufe verwendet; ihr Format wird weder zerlegt noch neu zusammengesetzt. Der anfängliche Status kann unterschiedlich sein.

Zum Lauf hält der Client mindestens Tenant-UUID, Query-Text, Zeitbezug, Search-URN und Startzeit fest. Eine Search-URN bezeichnet die gesamte Ausführung. Eine Partition-URN bezeichnet dagegen einen Teilauftrag, ein Event Resource Name ein einzelnes Ereignis. Diese Identifikatoren sind nicht austauschbar.

Vorhandene Suchen bewusst wiederverwenden

Wenn eine bereits laufende oder abgeschlossene passende Suche verwendet werden darf, ist getOrCreateSearch eine Alternative:

{
  "operationName": "GetOrCreateSearch",
  "query": "mutation GetOrCreateSearch($input: CreateSearchInput) { getOrCreateSearch(input: $input) { urn status } }",
  "variables": {
    "input": {
      "text": "from process where earliest = -1h | fields host_id, commandline, image_path"
    }
  }
}

Hier wird die URN aus data.getOrCreateSearch.urn übernommen. Die Zuordnung berücksichtigt die Eingabedaten und vergleicht Abfragen anhand ihrer kanonischen strukturierten Darstellung, nicht nur anhand identischen Texts. Wiederverwendung ist keine Garantie für frische Ergebnisse und darf im Client nicht als neuer Lauf ausgewiesen werden. Wenn die Aufgabe eine neue Ausführung verlangt, wird bewusst createSearch verwendet.

Geht nach createSearch die Antwort durch einen Timeout verloren, kann die Suche trotzdem laufen. Eine blinde Wiederholung kann einen weiteren Lauf erzeugen. Diesen Zustand als „Erstellung unklar“ erfassen und den Wiederanlauf anhand der zuvor festgelegten Wiederverwendungs- und Frischeanforderung entscheiden. getOrCreateSearch ist kein nachträglicher Beweis, dass genau der verlorene Lauf gefunden wurde.

Abschluss, Teilausfälle und Ablaufzeit prüfen

Mit der gespeicherten Search-URN wird der Zustand abgefragt. <search-urn> ist ausschliesslich durch den tatsächlich zurückgegebenen Wert zu ersetzen:

{
  "operationName": "SearchDiagnostics",
  "query": "query SearchDiagnostics($urn: URN!) { search(urn: $urn) { urn status errors expireTime outcome { success errors { urn errors } } searchPartitions { urn schemaType status errors } } }",
  "variables": {
    "urn": "<search-urn>"
  }
}

Solange status auf QUEUED oder IN_PROGRESS steht, wartet der Client zwischen den Abfragen. outcome.success: UNKNOWN ist vor Abschluss erwartbar. Ein Polling-Abstand von beispielsweise fünf Sekunden ist eine mögliche Cliententscheidung, kein zugesichertes Dienstlimit. Zusätzlich braucht jeder Request einen Timeout und der gesamte Ablauf eine zur Arbeitslast passende Deadline. Nach deren Ablauf bleibt der Lauf ungeklärt; das Stoppen des lokalen Pollings ist kein Abbruch der serverseitigen Suche.

Bei status: COMPLETE wird outcome.success getrennt bewertet:

WertBedeutung
ALLAlle Partitionen waren erfolgreich.
PARTIALNur ein Teil der Partitionen war erfolgreich.
NONEKeine Partition war erfolgreich.

Für PARTIAL wird vor der produktiven Nutzung festgelegt, ob und wofür Teilergebnisse zulässig sind. Ein vollständiger Untersuchungsbericht darf sie nicht stillschweigend als Gesamtbefund übernehmen. Bei NONE gibt es keinen erfolgreichen Suchumfang, auf den eine Entwarnung gestützt werden könnte. outcome.errors ordnet Meldungen den Partitionen zu; searchPartitions liefert deren Schema, Status und Fehler. Auch bei erfolgreicher Ausführung können errors auf Such- oder Partitionsebene Warnungen enthalten.

Mit expireTime wird zugleich die spätere Abrufplanung geprüft: Bis dahin müssen die benötigten Ergebnisse gelesen und bei längerem Bedarf im eigenen freigegebenen System gesichert werden. Eine gespeicherte URN oder ein Cursor verlängert diese Verfügbarkeit nicht. Es gibt hier keine aus einem Einzelbeispiel abgeleitete feste TTL.

Alle Ergebnisseiten lesen

Nach Abschluss und Bewertung des Ausführungsergebnisses wird die erste Seite ohne cursor in den Variablen angefordert:

{
  "operationName": "SearchResults",
  "query": "query SearchResults($urn: URN!, $limit: Int, $cursor: String) { searchResults(urn: $urn, limit: $limit, cursor: $cursor) { fields { name baseType originSchemaTypes } values totals { total { value } subtotals { schemaType value } } nextCursor } }",
  "variables": {
    "urn": "<search-urn>",
    "limit": 2
  }
}

limit: 2 macht die Seitenfolge im Pilot leicht überprüfbar. Es begrenzt die Zeilen pro Seite, nicht die gesamte Trefferzahl. Für einen produktiven Client wird die Seitengrösse passend zur Verarbeitung gewählt, ohne daraus ein dokumentiertes Maximal- oder Gesamtergebnislimit abzuleiten.

Nach jedem Abruf werden HTTP-Status, GraphQL-errors und der erwartete Antwortpfad geprüft. Die Seite wird erst danach verarbeitet. Selbst nach COMPLETE kann der Ergebnisabruf noch Zeit benötigen; ein Lese-Timeout beweist keinen Suchfehler. Vorübergehende Lesefehler mit begrenztem Backoff wiederholen, dabei dieselbe Search-URN und denselben Cursor verwenden. Der Consumer muss verhindern, dass eine wiederholt gelesene Seite doppelt in den Export eingeht.

Enthält data.searchResults.nextCursor einen Wert, wird er unverändert in die Variablen des gleichen SearchResults-Requests übernommen:

{
  "urn": "<search-urn>",
  "limit": 2,
  "cursor": "<next-cursor>"
}

Dies ist nur der ersetzte variables-Teil, kein vollständiger GraphQL-Body. <next-cursor> stammt aus der unmittelbar zuvor erfolgreich verarbeiteten Seite. Jeder Cursor bleibt an die Search-URN gebunden, die ihn geliefert hat. Nicht interpretieren, verändern oder auf einen anderen Lauf übertragen. Den Ablauf fortsetzen, bis nextCursor null ist oder fehlt. Ein wiederholter Cursor oder eine überschrittene Client-Deadline wird als Abruffehler behandelt, nicht als erfolgreiches Ende.

Werte, Metadaten und Gesamtzahlen auseinanderhalten

values enthält JSON-Zeilenobjekte, keine SQL-Spaltenliste und keinen JSON-kodierten additionalColumns-String wie im LES-Ergebnisformat. Die Einträge können verschachtelte Objekte und Listen enthalten. fields beschreibt die verfügbaren Feldnamen und Typen; ein Metadateneintrag garantiert keinen befüllten Wert in jeder Zeile. Fehlende Felder und explizites null müssen beide verarbeitet werden, ohne sie pauschal mit einem leeren String oder null Treffern gleichzusetzen.

baseType dient zur Interpretation der Werte. Beispielsweise unterscheidet TIMESTAMP_SECONDS Sekunden von TIMESTAMP_MILLISECONDS und TIMESTAMP_MICROSECONDS; TIMESTAMP_ISO_8601 bezeichnet einen ISO-8601-String. Listen- und Map-Typen werden entsprechend als Sammlungen verarbeitet, nicht wie ein einzelner Skalar. expireTime ist ebenfalls ein ISO-8601-Zeitstempel. Metadaten können verschachtelte Felder mit Punktnotation wie process_file_hash.md5 benennen. Der Parser darf deshalb nicht annehmen, dass jede solche Bezeichnung ein flacher Schlüssel im Zeilenobjekt ist.

Die optionalen totals.total- und totals.subtotals-Angaben beziehen sich auf die gesamte Ergebnismenge beziehungsweise deren Schemas, nicht auf die Länge der aktuellen values-Liste. Für einen vollständigen Abruf werden alle Seiten verarbeitet und die gesammelten Zeilen mit den verfügbaren Gesamtzahlen abgeglichen. Fehlen Gesamtzahlen, ist das kein Anlass, eine Zahl zu erfinden; der Cursor-Abschluss und der dokumentierte Abrufumfang bleiben erforderlich. Auch vollständige Pagination macht ein PARTIAL-Ergebnis nicht vollständig erfolgreich.

QL-Projektion und Enrichment erhalten

Im Beispiel begrenzt | fields host_id, commandline, image_path die Ereignisfelder der QL-Abfrage. Das GraphQL-Selection-Set bestimmt dagegen, ob Antwortbereiche wie fields, totals oder nextCursor mitkommen. values ist ein JSON-Skalar: Seine einzelnen Zeilenfelder lassen sich nicht durch ein verschachteltes GraphQL-Selection-Set auswählen.

Enrichment ergänzt abgeleitete Informationen. hostname und enrichSummary dürfen nicht direkt in | fields stehen. Für die Hostname-Auflösung muss host_id erhalten bleiben. Selbst dann ist ein auflösbarer Hostname nicht garantiert. Die Eingabefelder für enrichSummary hängen vom Ereignis ab; eine engere Projektion kann die Zusammenfassung verkürzen. Vor einer Reduktion deshalb repräsentative Ereignisse mit den vom Consumer benötigten Anreicherungen vergleichen.

Eine Projektion kann die Antwort verkleinern. Sie belegt weder niedrigere Scan-Kosten noch eine schnellere Ausführung. Nur die tatsächlich benötigten Antwortbereiche wegzulassen darf insbesondere nicht bedeuten, nextCursor und damit die Möglichkeit zum vollständigen Abruf zu verlieren.

Einen bestehenden XDR-Query-Client kontrolliert vergleichen

Der Wechsel von historischem SQL zu Search-QL ist kein Austausch der URL und kein mechanischer SQL-Translator. Zuerst werden bisherige Queries, erforderliche Felder und nachgelagerte Berichte inventarisiert. Die allgemeine XDR-API-Abgrenzung bleibt für Cases, Detections und andere Workflows separat relevant.

Für jede historische Abfrage erfolgt der Vergleich in dieser Reihenfolge:

  1. Die fachliche Frage und die benötigten Quellen festhalten. SQL-Tabellen begründen keine direkte Zuordnung zu gleichnamigen QL-Schemas; Schema und Feldnamen einzeln prüfen.
  2. Identische explizite earliest- und latest-Grenzen für beide Suchen festlegen und den Zeitbezug dokumentieren. Zwei nacheinander ausgeführte „letzte Stunde“-Abfragen sind kein sauberer Gleichheitsvergleich.
  3. Stringvergleiche, Gross-/Kleinschreibung und reguläre Ausdrücke prüfen. SQL-Joins werden von QL nicht unterstützt; Join- und abgeleitete Spalten benötigen einen fachlich neu entworfenen Ablauf. Ohne geklärte Entsprechung wird nicht umgeschaltet.
  4. Die QL-Abfrage zunächst interaktiv unter Security Operations > Data Lake Search prüfen. Der API-Lauf wird danach mit denselben fachlichen Bedingungen durchgeführt.
  5. Repräsentative Datensätze, Pflichtfelder, Typen, Zeitstempel und Enrichment vergleichen, nicht nur Trefferzahlen. Beide Clients müssen ihre jeweiligen Ergebnisse vollständig lesen.
  6. Den neuen Consumer auf values/fields, Search-URN und Cursor umstellen. Die alten REST-Zustände finished und succeeded werden nicht blind auf ein einziges GraphQL-Statusfeld abgebildet: COMPLETE und outcome.success bleiben getrennte Prüfungen.

Vor einem Cutover müssen ausserdem Teilausfallpolitik, begrenzte Wiederholungen, unklare Erstellung, Ablaufzeit und Exportaufbewahrung funktionieren. Der genehmigte Tenant-Pilot protokolliert Query, Zeitfenster, erforderliche Felder und alle Seiten, aber keine Secrets. Eine leere Suche gilt erst nach Prüfung von Quelle, Zeitraum, Filtern, Outcome und vollständig gelesenem Ergebnis als fachlich eingeordnet; sie ist keine allgemeine Entwarnung.

Bis zur Abnahme bleibt der bisherige XDR-Query-Client der vereinbarte Betriebsweg. Scheitert der neue Ablauf, wird die Umstellung gestoppt beziehungsweise die Anwendung auf diesen vorher erhaltenen Client zurückgeführt. Damit werden keine bereits exportierten Daten gelöscht oder entstandene Nachweislücken behoben. Ein späterer neuer Suchlauf kann wegen anderer Zeitbezüge oder nicht mehr vorhandener Daten vom ersten Lauf abweichen.

Fehler gezielt eingrenzen

  • Request abgewiesen: URL, X-GraphQL-Schema: default, JSON-Body, gültigen Bearer-Token und richtige Tenant-UUID prüfen. Authentifizierung und operationsspezifische Freigabe getrennt klären; keine Region durch Probieren suchen. Secrets nicht in Diagnoseausgaben aufnehmen.
  • HTTP 200, aber kein nutzbares data: Top-Level-errors lesen. Query, Variablentypen und angeforderte Felder prüfen, bevor nachgelagerte Daten verarbeitet werden.
  • COMPLETE mit PARTIAL oder NONE: outcome.errors, Suchfehler und betroffene searchPartitions auswerten. Export als unvollständig behandeln oder gemäss der vorher freigegebenen Teilergebnisregel zurückhalten, nicht mit null Treffern verwechseln.
  • Kein Hostname oder kürzere Zusammenfassung: Projektion und Ereignistyp prüfen. host_id für die Hostname-Auflösung erhalten; fehlende Enrichment-Eingaben nicht durch hostname oder enrichSummary in | fields ersetzen.
  • Abruf hängt oder endet zu früh: Request-Timeout, Gesamtablauf-Deadline, unveränderte URN, letzte erfolgreich verarbeitete Seite und Cursor-Zuordnung prüfen. Ein kleines limit ist kein Gesamtergebnislimit.
  • Gespeicherte URN liefert später keine Ergebnisse mehr: expireTime und bereits gesicherte Daten prüfen. Weder URN noch Cursor ist ein dauerhaftes Archiv. Ist ein neuer Lauf nötig, diesen als neue Ausführung dokumentieren und nicht als Wiederherstellung des ursprünglichen Ergebnisses ausweisen.

Die API wird weiterentwickelt. Der Client bleibt auf die dokumentierten Operationen createSearch, getOrCreateSearch, search und searchResults sowie die unterstützten Felder beschränkt. Zusätzliche Felder aus Introspection sind keine automatische Implementierungsfreigabe.