Kusto Graph Semantik
Erstelle transiente und persistente Graphen aus tabellarischen Daten mit KQL-Graph-Operatoren. Diese Fähigkeit übersetzt natürliche Sprache in das Edges-First-Graphenkonstruktionsmuster und Graph-Abfrageoperatoren.
Aktivierungstrigger
Verwende diese Fähigkeit, wenn der Benutzer:
- Einen Graphen aus tabellarischen Daten erstellen möchte (
make-graph) - Nach Mustern, Pfaden oder Beziehungen in Daten sucht
graph-match,graph-shortest-paths,graph-to-table,graph-mark-componentserwähnt- Ein persistentes Graphenmodell oder einen Snapshot erstellen möchte
- Sagt "Erstelle einen Graphen", "Finde den kürzesten Pfad", "Finde verbundene Komponenten", "Zeige Beziehungen"
- Nach transienten vs. persistenten Graphen fragt
Kein natürlicher-Sprache-zu-KQL-Konverter. Die Eingabe sollte im Allgemeinen eine funktionierende KQL-Abfrage sein, deren Ergebnisse der Benutzer in einen Graphen umwandeln möchte, plus eine natürlichsprachliche Beschreibung der gewünschten Graphenstruktur. Einfache NL-Quellenanfragen werden nur unterstützt, wenn sie direkt auf eine bekannte Tabelle mit offensichtlichen Spalten abgebildet werden können. Für die allgemeine NL-zu-KQL-Konvertierung verwende eine dedizierte Abfragegenerierungsfähigkeit (separat erhältlich).
Komplementäre Fähigkeiten:
azure-kusto-irql-- zusammensetzbare Sicherheitsabfrageprimitive, die die tabellarischen Eingaben für Graphen liefernazure-kusto-irql-graph-- IRQLsLift_To_GraphJSON-Mapping-System für reich typisierte, ikonendekorierte Graphen in Kusto Explorer
Der Edges-First-Ansatz
Das grundlegende Muster für die Erstellung von Graphen in Kusto:
1. Definiere deine KANTEN -> src --> dest, mit Beziehungstyp/-eigenschaften
2. Definiere deine KNOTEN-LOOKUPS -> Anzeigenamen, Typen, Eigenschaften für jede Knoten-ID
3. Vereinige Kantentypen -> wenn du mehrere Beziehungstypen hast
4. Vereinige Knoten-Lookups -> wenn du mehrere Knotentypen hast
5. Rufe make-graph auf -> edges | make-graph Source --> Target with nodes on nodeId
So denkst du in make-graph. Kanten sind die Beziehungen, die dich interessieren. Knoten sind Lookup-Tabellen, die diesen IDs ein Gesicht geben -- Anzeigenamen, Typen, Eigenschaften.
Graph-Operatoren-Referenz
make-graph -- Erstelle einen Graphen aus Tabellen
Edges | make-graph SourceId --> TargetId with Nodes on NodeId
Edges: tabellarische Quelle, in der jede Zeile eine Kante istSourceId --> TargetId: Spalten, die Quell- und Zielknoten-IDs enthaltenwith Nodes on NodeId: optionale Knoteneigenschaftstabelle, die über die ID verknüpft wird- Unterstützt mehrere Knotentabellen:
with Nodes1 on Id1, Nodes2 on Id2 - Knoten, die in Kanten vorkommen, aber in der Knotentabelle fehlen, erhalten leere Eigenschaften
graph-match -- Muster finden
G | graph-match (a)-[e]->(b) where <constraints> project <output>
Muster-Notation:
| Element | Benannt | Anonym |
|---|---|---|
| Knoten | (n) | () |
| Kante links->rechts | -[e]-> | --> |
| Kante rechts->links | <-[e]- | <-- |
| Beliebige Richtung | -[e]- | -- |
| Variable Länge | -[e*1..5]-> | -[*1..5]-> |
Mehr-Sprung-Muster: (a)-[e1]->(b)-[e2]->(c)
Stern-Muster: (a)--(center)--(b), (c)--(center)--(d)
Zyklussteuerung: cycles = all | none | unique_edges (Standard: unique_edges)
graph-shortest-paths -- Kürzeste Pfade finden
G | graph-shortest-paths (start)-[e*1..20]->(end)
where start.name == "Alice" and end.name == "Server01"
project Path = e, Length = array_length(e)
- Erfordert mindestens eine Kante mit variabler Länge
output = any(Standard, ein Pfad pro Paar) oderoutput = all(alle gleich langen kürzesten Pfade)- Kanteneigenschaften mit variabler Länge werden als dynamische Arrays zurückgegeben
graph-to-table -- Exportiere Graphen in Tabellen
G | graph-to-table nodes // Knoten exportieren
G | graph-to-table edges // Kanten exportieren
G | graph-to-table nodes as N, edges as E // beides exportieren
G | graph-to-table nodes with_node_id=Id // Knoten-Hash-ID einschließen
G | graph-to-table edges with_source_id=Src with_target_id=Tgt // Kantenendpunkt-IDs einschließen
graph-mark-components -- Verbundene Komponenten finden
G | graph-mark-components with_component_id=ComponentId
| graph-to-table nodes
| summarize Members = make_list(name) by ComponentId
Weist jedem Knoten eine ComponentId zu. Knoten in derselben verbundenen Komponente teilen dieselbe ID.
graph()-Funktion -- Persistente Graphen abfragen
graph("MyGraphModel") // neuester Snapshot
graph("MyGraphModel", "Snapshot_2025_01") // bestimmter Snapshot
graph("MyGraphModel", true) // transient aus Modelldefinition
Transiente Graphen
Werden dynamisch während der Abfrageausführung erstellt. Keine Einrichtung erforderlich. Ideal für Ad-hoc-Analysen, Erkundungen und Prototyping.
Vorlage: Einfacher Zwei-Entitäten-Graph
// 1. Kanten definieren
let edges = <SourceTable>
| summarize <aggregations> by SourceCol, TargetCol;
// 2. Knoten-Lookups definieren
let source_nodes = edges
| distinct SourceCol
| project nodeId = SourceCol, label = SourceCol, nodeType = "<SourceType>";
let target_nodes = edges
| distinct TargetCol
| project nodeId = TargetCol, label = TargetCol, nodeType = "<TargetType>";
let all_nodes = union source_nodes, target_nodes;
// 3. Graph erstellen und abfragen
edges
| make-graph SourceCol --> TargetCol with all_nodes on nodeId
| graph-match (s)-[e]->(t)
where <constraints>
project Source = s.label, Target = t.label, <edge properties>
Vorlage: Multi-Beziehungs-Graph
// Mehrere Kantentypen -> vereinige sie mit einem gemeinsamen Schema
let auth_edges = AuthEvents
| project Source = username, Target = hostname, edgeType = "authenticates", ts = timestamp;
let net_edges = NetworkEvents
| project Source = src_ip, Target = url, edgeType = "connects", ts = timestamp;
let all_edges = union auth_edges, net_edges;
// Knoten-Lookups aus allen Quellen
let user_nodes = Employees | project nodeId = username, label = name, nodeType = "User";
let host_nodes = AuthEvents | distinct hostname | project nodeId = hostname, label = hostname, nodeType = "Host";
let all_nodes = union user_nodes, host_nodes;
all_edges
| make-graph Source --> Target with all_nodes on nodeId
Persistente Graphen
Für groß angelegte, wiederverwendbare Graphen. In Datenbankmetadaten gespeichert. Unterstützen Snapshots für historische Vergleiche.
Sicherheit: Das Erstellen oder Ändern von Graphenmodellen und Snapshots modifiziert die Datenbank. Zeige immer den genauen Befehl an und bestätige mit dem Benutzer, bevor du
.create-or-alter graph_modeloder.make graph_snapshotausführst.
Schritt 1: Ein Graphenmodell erstellen
.create-or-alter graph_model SecurityGraph
{
"Schema": {
"Nodes": {
"User": {"name": "string", "role": "string"},
"Host": {"hostname": "string"},
"IP": {"ip": "string"}
},
"Edges": {
"AuthenticatesTo": {"timestamp": "datetime", "result": "string"},
"ConnectsFrom": {"timestamp": "datetime"}
}
},
"Definition": {
"Steps": [
{
"Kind": "AddNodes",
"Query": "Employees | project name, role",
"NodeIdColumn": "name",
"Labels": ["User"]
},
{
"Kind": "AddNodes",
"Query": "AuthenticationEvents | distinct hostname | project hostname",
"NodeIdColumn": "hostname",
"Labels": ["Host"]
},
{
"Kind": "AddEdges",
"Query": "AuthenticationEvents | project username, hostname, timestamp, result",
"SourceColumn": "username",
"TargetColumn": "hostname",
"Labels": ["AuthenticatesTo"]
}
]
}
}
Schritt 2: Einen Snapshot erstellen
.make graph_snapshot SecurityGraph Snapshot_2025_07
Schritt 3: Den Snapshot abfragen
graph("SecurityGraph")
| graph-match (user)-[auth]->(host)
where user.role == "Admin" and auth.result == "Failed Login"
project User = user.name, Host = host.hostname, Time = auth.timestamp
Verwaltungsbefehle
Sicherheit: Alle untenstehenden Steuerbefehle modifizieren oder löschen Datenbankobjekte. Führe niemals
.drop,.create-or-alter graph_modeloder.make graph_snapshotautomatisch aus. Zeige immer den genauen Befehl, Cluster, Datenbank und das betroffene Objekt an und fordere dann eine explizite Benutzerbestätigung vor der Ausführung an.
.show graph_models // alle Modelle auflisten
.show graph_model SecurityGraph // Modelldetails anzeigen
.show graph_snapshots SecurityGraph // Snapshots auflisten
.drop graph_snapshot SecurityGraph Snapshot_2025_07 // Snapshot löschen (Zuerst BESTÄTIGEN)
.drop graph_model SecurityGraph // Modell und alle Snapshots löschen (Zuerst BESTÄTIGEN)
Transient vs. Persistent: Wann was verwenden
| Faktor | Transient (make-graph) | Persistent (graph()) |
|---|---|---|
| Einrichtung | Keine -- inline in Abfrage | Modell + Snapshot erstellen |
| Lebensdauer | Nur während der Abfrageausführung | In Datenbankmetadaten gespeichert |
| Datenaktualität | Immer aktuell | Snapshot zum Zeitpunkt der Erstellung |
| Skalierung | Begrenzt durch Abfragespeicher | Unternehmensskalierung |
| Wiederverwendung | Wird bei jeder Abfrage neu erstellt | Gemeinsam über Benutzer/Abfragen hinweg |
| Am besten geeignet für | Ad-hoc-Suchen, Prototyping | Produktionsworkflows, Dashboards |
Sicherheits- & Jagdbeispiele
Authentifizierungsgraph: Wer hat sich wo von wo aus angemeldet
let auth_edges = AuthenticationEvents
| summarize
logins = count(),
fails = countif(result == "Failed Login")
by src_ip, username, hostname;
let ip_nodes = auth_edges | distinct src_ip
| project nodeId = src_ip, label = src_ip, nodeType = "IP";
let user_nodes = auth_edges | distinct username
| project nodeId = username, label = username, nodeType = "User";
let host_nodes = auth_edges | distinct hostname
| project nodeId = hostname, label = hostname, nodeType = "Host";
let all_nodes = union ip_nodes, user_nodes, host_nodes;
// IP -> User-Kanten
let ip_user = auth_edges
| project Source = src_ip, Target = username, logins, fails;
// User -> Host-Kanten
let user_host = auth_edges
| project Source = username, Target = hostname, logins, fails;
union ip_user, user_host
| make-graph Source --> Target with all_nodes on nodeId
| graph-match (ip)-[e1]->(user)-[e2]->(host)
where e2.fails > 20
project
IP = ip.label,
User = user.label,
Host = host.label,
Failures = e2.fails
| order by Failures desc
Erkennung lateraler Bewegung: Benutzer, die kompromittierte Hosts teilen
// Muster: (user1)-[auth1]->(host)<-[auth2]-(user2)
// Zwei Benutzer, die beide auf demselben Host fehlschlagen = möglicher Credential Spray
let edges = AuthenticationEvents
| summarize fails = countif(result == "Failed Login"), logins = count()
by username, hostname;
let nodes = union
(edges | distinct username | project nodeId = username, nodeType = "User"),
(edges | distinct hostname | project nodeId = hostname, nodeType = "Host");
edges
| make-graph username --> hostname with nodes on nodeId
| graph-match (u1)-[e1]->(h)<-[e2]-(u2)
where u1.nodeId != u2.nodeId and e1.fails > 10 and e2.fails > 10
project
User1 = u1.nodeId, User2 = u2.nodeId,
SharedHost = h.nodeId,
User1Fails = e1.fails, User2Fails = e2.fails
| distinct User1, SharedHost, User2, User1Fails, User2Fails
| order by User1Fails + User2Fails desc
Kürzester Angriffspfad
let edges = SecurityEvents
| project Source = source_entity, Target = target_entity, action, timestamp;
let nodes = union
(edges | distinct Source | project nodeId = Source),
(edges | distinct Target | project nodeId = Target);
edges
| make-graph Source --> Target with nodes on nodeId
| graph-shortest-paths (start)-[e*1..10]->(end)
where start.nodeId == "ExternalIP_1.2.3.4" and end.nodeId == "DatabaseServer"
project
PathLength = array_length(e),
Actions = e.action,
Hops = e.Target
Verbundene Komponenten: Isolierte Cluster finden
let edges = NetworkFlows
| project Source = src_ip, Target = dst_ip;
let nodes = union
(edges | distinct Source | project nodeId = Source),
(edges | distinct Target | project nodeId = Target);
edges
| make-graph Source --> Target with nodes on nodeId
| graph-mark-components with_component_id = ComponentId
| graph-to-table nodes
| summarize Members = make_list(nodeId), Size = count() by ComponentId
| order by Size desc
Visualisieren in Kusto Explorer
Beende eine Abfrage mit make-graph (ohne Weiterleitung an graph-match), um das interaktive Graph-Visualisierungsfenster des Kusto Explorers auszulösen:
edges
| make-graph Source --> Target with all_nodes on nodeId
// <- hier anhalten. Kusto Explorer rendert den Graphen visuell.
Um für Dashboards oder Export wieder in eine Tabelle umzuwandeln, leite durch graph-match | project oder graph-to-table.
Verwenden mit IRQL
Wenn du mit Sicherheitsdaten arbeitest, erwäge die Verwendung von IRQL-Selektoren (Get_*) aus der azure-kusto-irql-Fähigkeit als Datenquelle. IRQL bietet ein einheitliches Schema, ohne sich rohe Tabellennamen oder Spaltenzuordnungen merken zu müssen. Für reichhaltige Visualisierungen mit Symbolen und Knotenfaltung ist Lift_To_Graph der azure-kusto-irql-graph-Fähigkeit der schnellere Weg.
| Ansatz | Am besten geeignet für |
|---|---|
Roh make-graph (diese Fähigkeit) | Volle Kontrolle, persistente Modelle, kürzeste Pfade, verbundene Komponenten, benutzerdefinierte Schemata |
Lift_To_Graph (azure-kusto-irql-graph) | Schnelle symbolverzierte Visualisierung in Kusto Explorer, Knotenfaltung |
IRQL Get_* -> make-graph | IRQLs einheitliches Schema als Eingabe, dann rohe Graph-Operatoren zur Analyse |
IRQL Get_* -> Lift_To_Graph -> Graph_Render_View | Schnellster Weg von der Frage zum visuellen Graphen |
Hinweis:
Lift_To_Graph,Graph_Render_ViewundGraph_Fold_By_Propertysind gespeicherte Funktionen, keine integrierten Operatoren. Sie sind auf dem Beispielcluster kc7001 vorinstalliert, müssen aber möglicherweise auf anderen Clustern bereitgestellt werden. Sieheazure-kusto-irql-graph/references/DEPLOY_IRQL_FUNCTIONS.mdfür Funktionsdefinitionen und Bereitstellungsanweisungen.
Beispiel: IRQL-Selektoren -> make-graph -> kürzester Pfad
IRQL übernimmt den Datenabruf; make-graph übernimmt die Graph