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-- IRQL 的Lift_To_GraphJSON 映射系統,用於在 Kusto Explorer 中建立具有豐富型別和圖示裝飾的圖形
邊優先方法
在 Kusto 中建立圖形的基本模式:
1. 定義你的邊 (EDGES) --> src --> dest,包含關係類型和屬性
2. 定義你的節點查閱表 (NODE LOOKUPS) -> 每個節點 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 的欄位with Nodes on NodeId: 可選的節點屬性表,透過 ID 進行連接- 支援多個節點表:
with Nodes1 on Id1, Nodes2 on Id2 - 出現在邊中但節點表中不存在的節點將獲得空屬性
graph-match -- 尋找模式
G | graph-match (a)-[e]->(b) where <約束條件> project <輸出>
模式記法:
| 元素 | 具名 | 匿名 |
|---|---|---|
| 節點 | (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)
- 需要至少一條可變長度的邊
output = any(預設,每對一條路徑)或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) // 從模型定義建立的暫態圖形
暫態圖形
在查詢執行期間動態建立。無需設定。適合臨時分析、探索和原型設計。
範本:基本雙實體圖形
// 1. 定義邊
let edges = <SourceTable>
| summarize <彙總> 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 <約束條件>
project Source = s.label, Target = t.label, <邊屬性>
範本:多重關係圖形
// 多種邊類型 -> 使用通用結構聯集它們
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 負責圖形
…
[為翻譯而截斷]