azure-kusto-graph について
KQL のグラフ演算子で表形式データからグラフを構築し、クエリするスキルです。関係、経路、連結成分を分析する用途に向け、テンポラリーグラフとスナップショット付きの永続グラフモデルを扱います。
主な機能
- 01make-graph によるグラフ構築
- 02パターンと最短経路の検索
- 03連結成分の特定
- 04グラフのノードとエッジのテーブル出力
- 05永続モデルとスナップショットへの対応
活用シーン
- 01セキュリティイベントデータの関係の可視化
- 02エンティティ間の経路検索
- 03接続された IP やドメイン群の特定
- 04再利用可能なグラフモデルとスナップショットの保存
MCP 対応
KQL のグラフクエリを説明していますが、MCP 連携についての記載はありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ azure-skills --skill azure-kusto-graphインストール後、Claude Code で/azure-kusto-graphを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/EXAMPLES.mdKUSTO_EXPLORER_LAUNCH.mdSCENARIOS.md詳細
Kusto のグラフセマンティクス
KQL のグラフ演算子を使って、表形式のデータから一時的および永続的なグラフを構築します。このスキルは、自然言語をエッジ優先のグラフ構築パターンとグラフクエリ演算子に変換します。
使用するタイミング
次の場合にこのスキルを使用します。
- 表形式のデータからグラフを構築したい(
make-graph) - データ内のパターン、パス、関係を見つけたい
graph-match、graph-shortest-paths、graph-to-table、graph-mark-componentsについて言及している- 永続的なグラフモデルまたはスナップショットを作成したい
- 「グラフを構築して」「最短パスを見つけて」「連結成分を見つけて」「関係を表示して」と依頼している
- 一時的なグラフと永続的なグラフの違いについて尋ねている
自然言語から KQL への変換ツールではありません。 入力には通常、グラフに変換したい結果を返す実行可能な KQL クエリと、目的とするグラフ構造の自然言語による説明が必要です。基本的な自然言語によるソース指定は、明らかな列を持つ既知のテーブルに直接対応する場合にのみサポートされます。一般的な自然言語から KQL への変換には、専用のクエリ生成スキル(別途利用可能)を使用してください。
関連スキル:
azure-kusto-irql-- グラフの表形式入力を生成する、組み合わせ可能なセキュリティクエリプリミティブazure-kusto-irql-graph-- Kusto Explorer で型情報やアイコンを豊富に備えたグラフを作成する、IRQL のLift_To_GraphJSON マッピングシステム
エッジ優先のアプローチ
Kusto でグラフを構築する基本パターン:
1. Define your EDGES -> src --> dest, with relationship type/properties
2. Define your NODE LOOKUPS -> display names, types, properties for each node ID
3. Union edge types -> if you have multiple relationship types
4. Union node lookups -> if you have multiple node types
5. Call 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 を含む列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 // export nodes
G | graph-to-table edges // export edges
G | graph-to-table nodes as N, edges as E // export both
G | graph-to-table nodes with_node_id=Id // include node hash ID
G | graph-to-table edges with_source_id=Src with_target_id=Tgt // include edge endpoint IDs
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") // latest snapshot
graph("MyGraphModel", "Snapshot_2025_01") // specific snapshot
graph("MyGraphModel", true) // transient from model definition
一時的なグラフ
クエリ実行中に動的に作成されます。セットアップは不要です。アドホックな分析、調査、プロトタイピングに最適です。
テンプレート:基本的な 2 エンティティのグラフ
// 1. Define edges
let edges = <SourceTable>
| summarize <aggregations> by SourceCol, TargetCol;
// 2. Define node lookups
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. Build and query the graph
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>
テンプレート:複数の関係を持つグラフ
// Multiple edge types -> union them with a common 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;
// Node lookups from all 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
永続的なグラフ
大規模で再利用可能なグラフ向けです。データベースのメタデータに保存されます。履歴比較用のスナップショットをサポートします。
安全性: グラフモデルやスナップショットの作成または変更は、データベースを変更します。
.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 // list all models
.show graph_model SecurityGraph // show model details
.show graph_snapshots SecurityGraph // list snapshots
.drop graph_snapshot SecurityGraph Snapshot_2025_07 // delete a snapshot (CONFIRM FIRST)
.drop graph_model SecurityGraph // delete model and all snapshots (CONFIRM FIRST)
一時的なグラフと永続的なグラフ:どちらを使うか
| 要素 | 一時的(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 edges
let ip_user = auth_edges
| project Source = src_ip, Target = username, logins, fails;
// User -> Host edges
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
ラテラルムーブメントの検出:侵害されたホストを共有するユーザー
// Pattern: (user1)-[auth1]->(host)<-[auth2]-(user2)
// Two users both failing on the same host = possible 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
最短攻撃経路
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
// <- stop here. Kusto Explorer renders the graph visually.
ダッシュボードやエクスポート用にテーブルへ戻すには、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 が担当します。認証イベントを経由して、外部 IP からメールサーバーまでの最短経路を見つけます。
// IRQL provides unified columns (ClientIp, Hostname, Username, Result)
let auth = Get_Event_Authentication_All
| where Result == "Failed Login";
let edges = auth
| summarize Failures = count() by ClientIp, Hostname;
let nodes = union
(edges | distinct ClientIp | project nodeId = ClientIp, nodeType = "IP"),
(edges | distinct Hostname | project nodeId = Hostname, nodeType = "Host");
edges
| make-graph ClientIp --> Hostname with nodes on nodeId
| graph-shortest-paths (src)-[e*1..5]->(dest)
where src.nodeType == "IP" and dest.nodeId == "MAIL-SERVER01"
project
SourceIP = src.nodeId,
PathLength = array_length(e),
Hops = e.Hostname
例:IRQL セレクター -> make-graph -> 連結成分
相互に接続された IP とドメインのクラスター(潜在的な C2 インフラストラクチャ)を見つけます。
let dns = Get_Dns_All;
let edges = dns | project Source = ClientIp, Target = Domain;
let nodes = union
(edges | distinct Source | project nodeId = Source, nodeType = "IP"),
(edges | distinct Target | project nodeId = Target, nodeType = "Domain");
edges
| make-graph Source --> Target with nodes on nodeId
| graph-mark-components with_component_id = ComponentId
| graph-to-table nodes
| summarize
IPs = make_set_if(nodeId, nodeType == "IP"),
Domains = make_set_if(nodeId, nodeType == "Domain"),
Size = count()
by ComponentId
| where Size > 3
| order by Size desc
例:IRQL と make-graph の統合
IRQL セレクターと make-graph を組み合わせた複数ソースの調査グラフ、および Lift_To_Graph を使ったビジュアルグラフの例については、references/EXAMPLES.md を参照してください。
実践的な使用シナリオ
以下を含む詳しい実例については、references/SCENARIOS.md を参照してください。
- 到達可能性分析(重要な資産への最短経路)
- ネットワークセグメンテーションの検証(連結成分)
- 侵害されたアカウントの影響範囲(可変長パスの照合)
- SOC チーム向けの永続グラフモデル(graph_model + スナップショット)
使用する MCP ツール
| ツール | 用途 |
|---|---|
kusto_query | make-graph、graph-match、管理コマンドを含む KQL クエリを実行 |
kusto_table_schema_get | エッジ/ノードのプロジェクションを作成する前にテーブル列を確認 |
kusto_cluster_list | 利用可能な ADX クラスターを一覧表示 |
kusto_database_list | クラスター内のデータベースを一覧表示 |
Kusto Explorer でのクエリの起動(Windows のみ)
任意の便利機能です。 デフォルトのワークフローでは、KQL をチャットに出力し、ユーザーが手動で Kusto Explorer または VS Code Kusto 拡張機能にコピーします。自動起動はオプトインの場合のみです。
デフォルト:チャットに KQL を出力
Step 1(接続)と Step 2(クエリ)を明確に示し、完全な KQL を必ず出力します。
// Step 1: Connect to your cluster (skip if already connected)
// Example: uncomment to connect to the KC7 training cluster
// #connect cluster('kc7001.eastus.kusto.windows.net').database('ValdyTimes')
// Or replace with your own cluster:
// #connect cluster('<YOUR_CLUSTER>').database('<YOUR_DATABASE>')
// Step 2: Run the query below
<KQL_QUERY ending at make-graph>
続けてその直下に、| graph-to-table nodes as N, edges as E を追加した ADX Web Explorer バージョンを出力します。ADX Web Explorer では make-graph を直接レンダリングできないためです。
// ADX Web Explorer version (tabular output):
<SAME_QUERY>
| graph-to-table nodes as N, edges as E
これにより、ユーザーが何も変更しなくても、Kusto Explorer(グラフの可視化)と ADX Web Explorer(表形式の結果)の両方で出力を利用できます。
任意:保存して起動
ユーザーから Kusto Explorer でクエリを保存または開くよう求められた場合は、references/KUSTO_EXPLORER_LAUNCH.md の手順に従います。主なルールは次のとおりです。
- ファイルへの書き込みや実行ファイルの起動前に、必ず
ask_userで確認します - 開く前にユーザーが確認できるよう、必ずファイルの内容をチャットに表示します
- シェルのインターポレーションやヒア文字列は使用せず、
Set-Content/Add-Contentでファイルを書き込みます - クエリをブラウザーの URL にエンコードしません
- macOS/Linux では、
.kqlファイルを保存し、VS Code Kusto 拡張機能または ADX Web Explorer を提案します
make-graph の可視化(グラフウィンドウ)では、クエリは make-graph で終わる必要があります。graph-match にはパイプしないでください。Kusto Explorer がグラフの可視化ウィンドウを開くのは、出力がテーブルではなくグラフオブジェクトの場合のみです。