Sémantique des Graphes Kusto
Construisez des graphes transitoires et persistants à partir de données tabulaires à l'aide des opérateurs de graphe KQL. Cette compétence traduit le langage naturel en un modèle de construction de graphe centré sur les arêtes et en opérateurs de requête de graphe.
Déclencheurs d'Activation
Utilisez cette compétence lorsque l'utilisateur :
- Souhaite construire un graphe à partir de données tabulaires (
make-graph) - Demande à trouver des motifs, chemins ou relations dans les données
- Mentionne
graph-match,graph-shortest-paths,graph-to-table,graph-mark-components - Souhaite créer un modèle de graphe persistant ou un snapshot
- Dit « construire un graphe », « trouver le chemin le plus court », « trouver les composants connexes », « afficher les relations »
- Pose des questions sur les graphes transitoires vs persistants
Ce n'est pas un convertisseur langage-naturel-vers-KQL. L'entrée doit généralement être une requête KQL fonctionnelle dont l'utilisateur souhaite convertir les résultats en graphe, accompagnée d'une description en langage naturel de la structure de graphe souhaitée. Les requêtes NL de base ne sont prises en charge que lorsqu'elles correspondent directement à une table connue avec des colonnes évidentes. Pour une conversion NL vers KQL générale, utilisez une compétence dédiée de génération de requêtes (disponible séparément).
Compétences complémentaires :
azure-kusto-irql— primitives de requêtes de sécurité composables qui produisent les entrées tabulaires pour les graphesazure-kusto-irql-graph— système de mappage JSONLift_To_Graphd'IRQL pour des graphes richement typés et décorés d'icônes dans Kusto Explorer
L'Approche Centrée sur les Arêtes
Le motif fondamental pour construire des graphes dans Kusto :
1. Définissez vos ARÊTES -> src --> dest, avec type de relation/propriétés
2. Définissez vos RECHERCHES DE NŒUDS -> noms d'affichage, types, propriétés pour chaque ID de nœud
3. Fusionnez les types d'arêtes -> si vous avez plusieurs types de relations
4. Fusionnez les recherches de nœuds -> si vous avez plusieurs types de nœuds
5. Appelez make-graph -> edges | make-graph Source --> Target with nodes on nodeId
Voici comment penser avec make-graph. Les arêtes sont les relations qui vous intéressent. Les nœuds sont des tables de recherche qui donnent un visage à ces IDs — noms d'affichage, types, propriétés.
Référence des Opérateurs de Graphe
make-graph — Construire un graphe à partir de tables
Edges | make-graph SourceId --> TargetId with Nodes on NodeId
Edges: source tabulaire dont chaque ligne est une arêteSourceId --> TargetId: colonnes contenant les IDs des nœuds source et ciblewith Nodes on NodeId: table optionnelle de propriétés de nœud jointe par ID- Prend en charge plusieurs tables de nœuds :
with Nodes1 on Id1, Nodes2 on Id2 - Les nœuds apparaissant dans les arêtes mais absents de la table de nœuds reçoivent des propriétés vides
graph-match — Trouver des motifs
G | graph-match (a)-[e]->(b) where <constraints> project <output>
Notation des motifs :
| Élément | Nommé | Anonyme |
|---|---|---|
| Nœud | (n) | () |
| Arête gauche->droite | -[e]-> | --> |
| Arête droite->gauche | <-[e]- | <-- |
| N'importe quelle direction | -[e]- | -- |
| Longueur variable | -[e*1..5]-> | -[*1..5]-> |
Motifs multi-sauts : (a)-[e1]->(b)-[e2]->(c)
Motifs en étoile : (a)--(center)--(b), (c)--(center)--(d)
Contrôle des cycles : cycles = all | none | unique_edges (par défaut : unique_edges)
graph-shortest-paths — Trouver les chemins les plus courts
G | graph-shortest-paths (start)-[e*1..20]->(end)
where start.name == "Alice" and end.name == "Server01"
project Path = e, Length = array_length(e)
- Nécessite au moins une arête de longueur variable
output = any(par défaut, un chemin par paire) ououtput = all(tous les chemins les plus courts de même longueur)- Les propriétés des arêtes de longueur variable sont retournées sous forme de tableaux dynamiques
graph-to-table — Exporter le graphe en tables
G | graph-to-table nodes // exporter les nœuds
G | graph-to-table edges // exporter les arêtes
G | graph-to-table nodes as N, edges as E // exporter les deux
G | graph-to-table nodes with_node_id=Id // inclure l'ID de hachage du nœud
G | graph-to-table edges with_source_id=Src with_target_id=Tgt // inclure les IDs des extrémités des arêtes
graph-mark-components — Trouver les composants connexes
G | graph-mark-components with_component_id=ComponentId
| graph-to-table nodes
| summarize Members = make_list(name) by ComponentId
Assigne un ComponentId à chaque nœud. Les nœuds d'un même composant connexe partagent le même ID.
Fonction graph() — Interroger les graphes persistants
graph("MyGraphModel") // dernier snapshot
graph("MyGraphModel", "Snapshot_2025_01") // snapshot spécifique
graph("MyGraphModel", true) // transitoire à partir de la définition du modèle
Graphes Transitoires
Créés dynamiquement pendant l'exécution de la requête. Aucune configuration préalable requise. Idéal pour les analyses ad-hoc, l'exploration et le prototypage.
Modèle : Graphe basique à deux entités
// 1. Définir les arêtes
let edges = <SourceTable>
| summarize <aggregations> by SourceCol, TargetCol;
// 2. Définir les recherches de nœuds
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. Construire et interroger le graphe
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>
Modèle : Graphe multi-relations
// Plusieurs types d'arêtes -> les fusionner avec un schéma commun
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;
// Recherches de nœuds à partir de toutes les sources
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
Graphes Persistants
Pour des graphes réutilisables à grande échelle. Stockés dans les métadonnées de la base de données. Prend en charge les snapshots pour la comparaison historique.
Sécurité : La création ou la modification de modèles de graphe et de snapshots modifie la base de données. Montrez toujours la commande exacte et confirmez avec l'utilisateur avant d'exécuter
.create-or-alter graph_modelou.make graph_snapshot.
Étape 1 : Créer un modèle de graphe
.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"]
}
]
}
}
Étape 2 : Créer un snapshot
.make graph_snapshot SecurityGraph Snapshot_2025_07
Étape 3 : Interroger le snapshot
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
Commandes de gestion
Sécurité : Toutes les commandes de contrôle ci-dessous modifient ou suppriment des objets de la base de données. N'exécutez jamais
.drop,.create-or-alter graph_modelou.make graph_snapshotautomatiquement. Montrez toujours la commande exacte, le cluster, la base de données et l'objet concerné, puis demandez une confirmation explicite de l'utilisateur avant l'exécution.
.show graph_models // lister tous les modèles
.show graph_model SecurityGraph // afficher les détails du modèle
.show graph_snapshots SecurityGraph // lister les snapshots
.drop graph_snapshot SecurityGraph Snapshot_2025_07 // supprimer un snapshot (CONFIRMER D'ABORD)
.drop graph_model SecurityGraph // supprimer le modèle et tous les snapshots (CONFIRMER D'ABORD)
Transitoire vs Persistant : Quand Utiliser Chacun
| Facteur | Transitoire (make-graph) | Persistant (graph()) |
|---|---|---|
| Configuration | Aucune — en ligne dans la requête | Créer modèle + snapshot |
| Durée de vie | Exécution de la requête uniquement | Stocké dans les métadonnées de la base de données |
| Fraîcheur des données | Toujours actuel | Snapshot au moment de la création |
| Échelle | Limitée par la mémoire de la requête | Échelle entreprise |
| Réutilisation | Reconstruit à chaque requête | Partagé entre utilisateurs/requêtes |
| Idéal pour | Chasses ad-hoc, prototypage | Workflows de production, tableaux de bord |
Exemples de Sécurité et Chasse aux Menaces
Graphe d'authentification : qui s'est connecté à quoi et d'où
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;
// Arêtes IP -> Utilisateur
let ip_user = auth_edges
| project Source = src_ip, Target = username, logins, fails;
// Arêtes Utilisateur -> Hôte
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
Détection de mouvement latéral : utilisateurs partageant des hôtes compromis
// Motif : (user1)-[auth1]->(host)<-[auth2]-(user2)
// Deux utilisateurs échouant tous deux sur le même hôte = possible pulvérisation de mots de passe
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
Chemin d'attaque le plus court
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
Composants connexes : trouver les clusters isolés
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
Visualiser dans Kusto Explorer
Terminez une requête par make-graph (sans la rediriger vers graph-match) pour déclencher la fenêtre de visualisation interactive du graphe de Kusto Explorer :
edges
| make-graph Source --> Target with all_nodes on nodeId
// <- s'arrêter ici. Kusto Explorer rend le graphe visuellement.
Pour repasser à une table pour les tableaux de bord ou l'export, utilisez graph-match | project ou graph-to-table.
Utilisation avec IRQL
Lors du traitement de données de sécurité, envisagez d'utiliser les sélecteurs IRQL (Get_*) de la compétence azure-kusto-irql comme source de données. IRQL vous offre un schéma unifié sans mémoriser les noms de tables brutes ou les mappages de colonnes. Pour une visualisation riche avec icônes et pliage de nœuds, le Lift_To_Graph de la compétence azure-kusto-irql-graph est le chemin le plus rapide.
| Approche | Idéal pour |
|---|---|
make-graph brut (cette compétence) | Contrôle total, modèles persistants, chemins les plus courts, composants connexes, schémas personnalisés |
Lift_To_Graph (azure-kusto-irql-graph) | Visualisation rapide avec icônes dans Kusto Explorer, pliage de nœuds |
IRQL Get_* -> make-graph | Schéma unifié d'IRQL en entrée, puis opérateurs de graphe bruts pour l'analyse |
IRQL Get_* -> Lift_To_Graph -> Graph_Render_View | Chemin le plus rapide de la question au graphe visuel |
Remarque :
Lift_To_Graph,Graph_Render_ViewetGraph_Fold_By_Propertysont des fonctions stockées, pas des opérateurs natifs. Elles sont pré-déployées sur le cluster d'exemple kc7001 mais peuvent nécessiter un déploiement sur d'autres clusters. Voirazure-kusto-irql-graph/references/DEPLOY_IRQL_FUNCTIONS.mdpour les définitions des fonctions et les instructions de déploiement.
Exemple : Sélecteurs IRQL -> make-graph -> chemin le plus court
IRQL gère la récupération des données ; make-graph gère le graphe.