Kusto Graph Semantics
表形式データからKQLグラフ演算子を使用して、一時的および永続的なグラフを構築します。このスキルは、自然言語をエッジ優先のグラフ構築パターンとグラフクエリ演算子に変換します。
起動トリガー
以下の場合にこのスキルを使用します:
- ユーザーが表形式データからグラフを構築したい場合(
make-graph) - データ内のパターン、パス、または関係を見つけたい場合
graph-match、graph-shortest-paths、graph-to-table、graph-mark-componentsに言及した場合- 永続的なグラフモデルまたはスナップショットを作成したい場合
- 「グラフを構築する」「最短経路を見つける」「連結成分を見つける」「関係を表示する」と言った場合
- 一時的グラフと永続的グラフの違いについて質問した場合
自然言語からKQLへのコンバーターではありません。 入力は通常、ユーザーがグラフに変換したい結果を持つ動作中のKQLクエリと、目的のグラフ構造の自然言語による説明である必要があります。基本的なNLソースリクエストは、既知のテーブルと明白な列に直接マッピングされる場合にのみサポートされます。一般的なNLからKQLへの変換には、専用のクエリ生成スキル(別途利用可能)を使用してください。
補完スキル:
azure-kusto-irql-- グラフへの表形式入力を生成する、構成可能なセキュリティクエリプリミティブazure-kusto-irql-graph-- Kusto Explorerでリッチに型付けされ、アイコンで装飾されたグラフのためのIRQLのLift_To_GraphJSONマッピングシステム
エッジ優先アプローチ
Kustoでグラフを構築するための基本的なパターン:
1. エッジを定義 -> src --> dest、関係タイプとプロパティ付き
2. ノードルックアップを定義 -> 各ノードIDの表示名、タイプ、プロパティ
3. エッジタイプを結合 -> 複数の関係タイプがある場合
4. ノードルックアップを結合 -> 複数のノードタイプがある場合
5. make-graphを呼び出す -> edges | make-graph Source --> Target with nodes on nodeId
これがmake-graphでの考え方です。エッジは注目する関係です。ノードはそれらのIDに顔を与えるルックアップテーブルです -- 表示名、タイプ、プロパティ。
グラフ演算子リファレンス
make-graph -- テーブルからグラフを構築
Edges | make-graph SourceId --> TargetId with Nodes on NodeId
Edges: 各行がエッジである表形式ソースSourceId --> TargetId: ソースノードIDとターゲットノードIDを含む列with Nodes on NodeId: IDで結合されるオプションのノードプロパティテーブル- 複数のノードテーブルをサポート:
with Nodes1 on Id1, Nodes2 on Id2 - エッジに現れるがノードテーブルにないノードは空のプロパティを取得します
graph-match -- パターンを見つける
G | graph-match (a)-[e]->(b) where <constraints> project <output>
パターン表記:
| 要素 | 名前付き | 匿名 |
|---|---|---|
| ノード | (n) | () |
| エッジ 左->右 | -[e]-> | --> |
| エッジ 右->左 | <-[e]- | <-- |
| 任意方向 | -[e]- | -- |
| 可変長 | -[e*1..5]-> | -[*1..5]-> |
マルチホップパターン: (a)-[e1]->(b)-[e2]->(c)
スター型パターン: (a)--(center)--(b), (c)--(center)--(d)
サイクル制御: cycles = all | none | unique_edges (デフォルト: unique_edges)
graph-shortest-paths -- 最短経路を見つける
G | graph-shortest-paths (start)-[e*1..20]->(end)
where start.name == "Alice" and end.name == "Server01"
project Path = e, Length = array_length(e)
- 少なくとも1つの可変長エッジが必要です
output = any(デフォルト、ペアごとに1つのパス) またはoutput = all(同じ長さのすべての最短経路)- 可変長エッジのプロパティは動的配列として返されます
graph-to-table -- グラフをテーブルにエクスポート
G | graph-to-table nodes // ノードをエクスポート
G | graph-to-table edges // エッジをエクスポート
G | graph-to-table nodes as N, edges as E // 両方をエクスポート
G | graph-to-table nodes with_node_id=Id // ノードハッシュIDを含める
G | graph-to-table edges with_source_id=Src with_target_id=Tgt // エッジエンドポイントIDを含める
graph-mark-components -- 連結成分を見つける
G | graph-mark-components with_component_id=ComponentId
| graph-to-table nodes
| summarize Members = make_list(name) by ComponentId
各ノードにComponentIdを割り当てます。同じ連結成分内のノードは同じIDを共有します。
graph() 関数 -- 永続的グラフのクエリ
graph("MyGraphModel") // 最新のスナップショット
graph("MyGraphModel", "Snapshot_2025_01") // 特定のスナップショット
graph("MyGraphModel", true) // モデル定義から一時的に構築
一時的グラフ
クエリ実行中に動的に作成されます。セットアップは不要です。アドホック分析、探索、プロトタイピングに最適です。
テンプレート: 基本2エンティティグラフ
// 1. エッジを定義
let edges = <SourceTable>
| summarize <aggregations> by SourceCol, TargetCol;
// 2. ノードルックアップを定義
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. グラフを構築してクエリ
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>
テンプレート: マルチリレーションシップグラフ
// 複数のエッジタイプ -> 共通スキーマで結合
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;
// すべてのソースからのノードルックアップ
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
永続的グラフ
大規模で再利用可能なグラフ向け。データベースメタデータに保存されます。履歴比較のためのスナップショットをサポートします。
安全注意: グラフモデルやスナップショットの作成・変更はデータベースを変更します。常に正確なコマンドを表示し、
.create-or-alter graph_modelや.make graph_snapshotを実行する前にユーザーの確認を得てください。
ステップ1: グラフモデルを作成
.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"]
}
]
}
}
ステップ2: スナップショットを作成
.make graph_snapshot SecurityGraph Snapshot_2025_07
ステップ3: スナップショットをクエリ
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
管理コマンド
安全注意: 以下のすべての制御コマンドはデータベースオブジェクトを変更または削除します。
.drop、.create-or-alter graph_model、または.make graph_snapshotを自動的に実行しないでください。常に正確なコマンド、クラスター、データベース、影響を受けるオブジェクトを表示し、実行前に明示的なユーザー確認を要求してください。
.show graph_models // すべてのモデルを一覧表示
.show graph_model SecurityGraph // モデルの詳細を表示
.show graph_snapshots SecurityGraph // スナップショットを一覧表示
.drop graph_snapshot SecurityGraph Snapshot_2025_07 // スナップショットを削除(最初に確認)
.drop graph_model SecurityGraph // モデルとすべてのスナップショットを削除(最初に確認)
一時的 vs 永続的: いつどちらを使うか
| 要素 | 一時的 (make-graph) | 永続的 (graph()) |
|---|---|---|
| セットアップ | 不要 -- クエリ内にインライン | モデル + スナップショットを作成 |
| ライフタイム | クエリ実行のみ | データベースメタデータに保存 |
| データ鮮度 | 常に最新 | スナップショット作成時点 |
| 規模 | クエリメモリに制限 | エンタープライズ規模 |
| 再利用 | クエリごとに再構築 | ユーザー/クエリ間で共有 |
| 最適な用途 | アドホックハント、プロトタイピング | 本番ワークフロー、ダッシュボード |
セキュリティと脅威ハンティングの例
認証グラフ: 誰がどこから何にログインしたか
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 エッジ
let ip_user = auth_edges
| project Source = src_ip, Target = username, logins, fails;
// User -> Host エッジ
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
ラテラルムーブメント検出: 侵害されたホストを共有するユーザー
// パターン: (user1)-[auth1]->(host)<-[auth2]-(user2)
// 同じホストで両方のユーザーが失敗している = 可能性のあるクレデンシャルスプレー
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
最短攻撃経路
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
連結成分: 孤立したクラスターを見つける
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
Kusto Explorerでの可視化
make-graph(graph-matchにパイプせず)でクエリを終了すると、Kusto Explorerのインタラクティブなグラフ可視化ウィンドウが起動します:
edges
| make-graph Source --> Target with all_nodes on nodeId
// <- ここで停止。Kusto Explorerがグラフを視覚的にレンダリングします。
ダッシュボードやエクスポート用にテーブルに戻すには、graph-match | project または graph-to-table にパイプします。
IRQLとの使用
セキュリティデータを扱う場合は、azure-kusto-irql スキルのIRQLセレクター(Get_*)をデータソースとして使用することを検討してください。IRQLは、生のテーブル名や列マッピングを覚えることなく、統一されたスキーマを提供します。アイコンとノード折りたたみによるリッチな可視化には、azure-kusto-irql-graph スキルの Lift_To_Graph がより高速なパスです。
| アプローチ | 最適な用途 |
|---|---|
生の make-graph(このスキル) | 完全な制御、永続的モデル、最短経路、連結成分、カスタムスキーマ |
Lift_To_Graph(azure-kusto-irql-graph) | Kusto Explorerでのアイコン装飾付き高速可視化、ノード折りたたみ |
IRQL Get_* -> make-graph | IRQLの統合スキーマを入力として、分析用に生のグラフ演算子を使用 |
IRQL Get_* -> Lift_To_Graph -> Graph_Render_View | 質問からグラフ可視化への最速パス |
注:
Lift_To_Graph、Graph_Render_View、Graph_Fold_By_Propertyは組み込みの演算子ではなく、保存された関数です。これらはkc7001サンプルクラスターに事前デプロイされていますが、他のクラスターではデプロイが必要な場合があります。関数の定義とデプロイ手順については、azure-kusto-irql-graph/references/DEPLOY_IRQL_FUNCTIONS.mdを参照してください。
例: IRQLセレクター -> make-graph -> 最短経路
IRQLがデータ取得を処理し、make-graphがグラフ処理を担当します。