防禦節點(主機 B)上除了 WAF,還裝了幾個別人寫的系統。這一系列每篇只談一個,不深入它的設定細節,只回答三件事:它是什麼、它在主機 A 與主機 B 之間佔哪一段、少了它整體會缺什麼。本篇是 Grafana。
先講結論。Grafana 是把查詢結果畫成圖表的網頁工具。安裝包替它做了三件事:建好管理員帳號、裝好 ClickHouse 的外掛、設好通往 ClickHouse 的連線。沒有做的是畫面:登入之後一個儀表板都沒有,要看什麼得自己建。它只讀 ClickHouse,自己不存事件,不參與偵測、建案、處置的任何一段,主機 A 也不經過它。停掉它,除了看不到圖,什麼都不受影響。它補上的是 ClickHouse 那一篇留下的缺口:同樣的 SQL,不必每次重打,結果會自己更新,時間也換成你所在的時區。
Grafana 是 Grafana Labs 公司維護的開源儀表板工具,授權 AGPLv3。它自己不收集資料,而是接上別的資料庫,把查詢結果畫成折線、長條、表格,排成一頁一頁的儀表板(dashboard),頁面上的每一格叫面板(panel)。本安裝包用的容器映像是 grafana/grafana:11.3.0,是官方未修改的版本;另外裝了一個外掛 grafana-clickhouse-datasource,讓它看得懂 ClickHouse。
它在本安裝包裡的樣子:
| 項目 | 內容 |
|---|---|
| 是否安裝 | 預設會裝。安裝時加 --no-ui 就不裝,EveBox 與 Portainer 也一起不裝 |
| 網址 | http://192.168.0.112:3000/,HTTP |
| 誰能連 | 由主機 B 的防火牆限制來源:安裝時 --admin-ips 列出的位址與主機 A。其他來源連線逾時 |
| 帳號 | admin 一個。密碼在安裝時隨機產生,印在安裝結尾,也存在主機 B 的 .env(GRAFANA_ADMIN_PASSWORD)。不開放匿名瀏覽,也不開放自行註冊 |
| 資料來源 | 一個,名稱 ClickHouse-secstack,指向同一台主機上的 ClickHouse、資料庫 secstack |
| 儀表板 | 沒有 |
| 它自己的資料 | 帳號、儀表板、設定存在它自己的一個小資料庫裡,放在名為 grafana-data 的資料卷。這裡面沒有事件 |
| 資源 | 實測一台節點閒置時約佔 130 MB 記憶體 |

圖上有三件事要看。
在 --admin-ips 列出的電腦上開 http://192.168.0.112:3000/,帳號 admin,密碼取自主機 B:
# 主機 B
sudo grep '^GRAFANA_ADMIN_PASSWORD=' /opt/integrated-waf/.env
登入後是一個空的首頁。介面是英文的,以下的選單與按鈕名稱照畫面上的原文寫。左上角的選單裡,與本篇有關的是三項:
| 選單 | 出廠的內容 | 用途 |
|---|---|---|
| Dashboards | 空的 | 儀表板的清單。第四節建的東西會出現在這裡 |
| Explore | 可以直接用 | 臨時下一個查詢、看一次結果,不存檔。確認連線通不通,用它最快 |
| Connections → Data sources | 一筆 ClickHouse-secstack | 通往 ClickHouse 的連線。不必改 |
先確認線是通的。進 Explore,上方的 Editor Type 預設是 Query Builder,把它切到 SQL Editor,貼上搭配元件篇 3 第五節的第一個查詢,按 Run Query:
SELECT source_system, count() AS n, max(event_time) AS latest
FROM events
WHERE event_time > now() - INTERVAL 1 DAY
GROUP BY source_system
ORDER BY n DESC
source_system n latest
suricata 12 2026-10-06 21:57:07
coraza 11 2026-10-06 21:58:13
數字和在 ClickHouse 直接查到的一樣,時間不一樣。ClickHouse 存的是 UTC,同一筆在那邊顯示 13:58:13;Grafana 依瀏覽器所在的時區換算,在台灣看到的是 21:58:13。上一篇提醒過「時間要自己加 8 小時」,在 Grafana 裡不必。
Query Builder 是用下拉選單組查詢的模式。它的資料表清單裡除了 events 與 events_per_minute,還有一個 .inner_id. 開頭的名字,那是每分鐘彙總實際存放資料的地方,不要選它。本篇一律用 SQL Editor。
目標是一張圖:每個來源在每段時間各有幾筆事件。資料取自每分鐘彙總表 events_per_minute,它就是為這種圖準備的。
SELECT $__timeInterval(minute) AS time, source_system, sum(events) AS n
FROM events_per_minute
WHERE $__timeFilter(minute)
GROUP BY time, source_system
ORDER BY time
這個查詢和上一篇的寫法只差兩個 $__ 開頭的字,它們是外掛提供的巨集,送出之前會被換成真正的 SQL:
| 巨集 | 送到 ClickHouse 時變成 | 作用 |
|---|---|---|
| $__timeFilter(minute) | minute >= toDateTime(…) AND minute <= toDateTime(…) | 套用畫面右上角選的時間範圍。改選「過去 7 天」,查詢條件跟著變,不必改 SQL |
| $__timeInterval(minute) | toStartOfInterval(toDateTime(minute), INTERVAL 60 second) | 把時間切成一段一段。每段多長由 Grafana 依時間範圍與面板寬度決定,範圍拉長,每一段就變寬,圖上的點不會多到畫不下 |
Query Type 選 Time Series 時,外掛對查詢結果有兩個要求:第一欄是時間,其餘是數字;多出來的文字欄(這裡是 source_system)會被拿來把資料拆成幾條線,一個來源一條。事件少的節點,圖上會是零星的幾個點;在右側的 Graph styles 把 Style 改成 Bars,比折線好讀。
同一個儀表板可以繼續加面板(Add → Visualization)。下面三個查詢的 Query Type 都選 Table,圖的種類也選 Table,實測都跑得出結果。
各來源的筆數與最後出現的時間。high 是嚴重度最高一級的筆數。
SELECT source_system, count() AS n, countIf(severity_id >= 4) AS high, max(event_time) AS latest
FROM events
WHERE $__timeFilter(event_time)
GROUP BY source_system
ORDER BY n DESC
攻擊者位址前 10 名。位址欄是 IPv6 型態,這裡把 IPv4 位址前面的 ::ffff: 去掉。
SELECT replaceRegexpOne(toString(actor_ip), '^::ffff:', '') AS ip,
count() AS n, uniqExact(source_system) AS sources, max(event_time) AS latest
FROM events
WHERE $__timeFilter(event_time)
GROUP BY actor_ip
ORDER BY n DESC
LIMIT 10
最新 20 筆。
SELECT event_time, source_system, severity_id,
replaceRegexpOne(toString(actor_ip), '^::ffff:', '') AS ip, target_host, finding_title
FROM events
WHERE $__timeFilter(event_time)
ORDER BY event_time DESC
LIMIT 20
這些面板畫的是搭配元件篇 3 所說的全量,也繼承了它的範圍:只有命中規則的才在裡面,WAF 事件只記排第一的那條規則,內網打內網的也算。所以「攻擊者位址前 10 名」裡出現主機 B 自己或管理者的電腦是正常的,那是自己做的測試與主機 B 往外的連線。儀表板右上角可以設定自動重新整理的間隔;一筆事件從發生到圖上看得到,最短是管線本身的 5 秒。
在畫面上建的儀表板,存在 Grafana 自己的資料庫裡,也就是 grafana-data 這個資料卷。它跟著資料卷走:
| 動作 | 儀表板 |
|---|---|
| 重啟容器、--update、--reconfigure | 還在。實測停止再啟動,儀表板與資料來源都在 |
| --uninstall(不加 --purge) | 還在。資料卷與安裝目錄都保留,再裝一次就接回去 |
| --uninstall --purge、把安裝目錄刪掉之後重裝、整台主機重灌 | 沒了。安裝腳本找不到原本的 .env 時,會把殘留的資料卷清掉再裝 |
想讓它不跟著資料卷消失,就把它變成檔案。在儀表板上按 Share,切到 Export 分頁,按 Save to file,會下載一個 JSON 檔。把它放進主機 B 的設定目錄:
# 主機 B
sudo cp 下載的檔案.json /opt/integrated-waf/grafana/provisioning/dashboards/
Grafana 每 30 秒看一次這個目錄,不必重啟。實測放進去之後 30 秒內載入。這個目錄不會被 --update 清掉,全新的 Grafana 啟動時也會讀它,所以資料卷沒了,儀表板還在。放進去之後有兩件事和原本不同,都實測過:
| 限制 | 說明 |
|---|---|
| 出廠沒有畫面 | 沒有儀表板,沒有警示規則。安裝完成時,它能做的只有 Explore 裡的臨時查詢 |
| 不存事件 | 每一張圖都是當下向 ClickHouse 查出來的。ClickHouse 停了,面板就是錯誤訊息;超過 90 天、已經被 ClickHouse 刪除的資料,在這裡也查不到 |
| 看不到 ClickHouse 以外的東西 | 主機 A 上的案件與封鎖決策、Suricata 的連線記錄、WAF 的原始記錄都不在裡面。要把它們畫進來,得自己再接資料來源 |
| 不是唯讀的 | 它連 ClickHouse 用的是 secstack,就是那個能讀、能寫、能刪表的帳號。實測在 Grafana 的查詢編輯區送出建表與刪表的指令,都會執行。能登入 Grafana 的人,拿到的是 ClickHouse 的管理權,不只是看圖的權限 |
| 只有一個共用帳號 | 出廠只有 admin。要分人、分權限,得自己在 Administration 裡建使用者。密碼只在第一次啟動時由 .env 設定,之後改 .env 不會生效,要在畫面上改 |
| 連線不加密 | 3000 埠是 HTTP,登入的密碼在內網是明文傳送。它靠的是防火牆的來源限制,和 ClickHouse 一樣假設管理者的電腦在可信任的網段 |
| 警示沒有設定 | Grafana 有警示功能,但安裝包沒有設任何規則,也沒有設通知管道。本系列裡「有事要通知人」走的是主機 A 的案件流程,不是這裡 |
| 會自己往外連 | 它會定期連 grafana.com 檢查本體與外掛有沒有新版,登入頁因此會顯示有新版可用;Grafana 預設還會回報匿名的使用統計,安裝包沒有關掉。第一次啟動時要從 grafana.com 下載 ClickHouse 外掛,主機 B 當時連不出去的話,外掛裝不起來,資料來源就不能用 |
| 不會自己更新 | 本體的版本跟著安裝包走 |
把它停掉會怎樣,實測過一次:停止 Grafana 容器 14 秒。這段期間 3000 埠連不上;對 WAF 送的攻擊請求照常回 403,那一筆事件照常寫進 ClickHouse,經 tunnel 的對外服務照常。重新啟動後約 14 秒可以登入,儀表板與資料來源都在。
所以它在整體裡的位置是這樣的:事件怎麼來、存在哪裡、誰來判斷,都和它無關;它只負責把 ClickHouse 裡已經有的東西,換成一眼看得出多寡與起伏的畫面。少了它,資料一筆都不會少,只是要看的時候得回去下 SQL。
先看它活著沒有。這個端點不必登入:
# 主機 B
curl -s http://127.0.0.1:3000/api/health
{
"database": "ok",
"version": "11.3.0",
"commit": "d9455ff7db73b694db7d412e49a68bec767f2b5a"
}
這裡的 database 指的是 Grafana 自己的資料庫,不是 ClickHouse。它顯示 ok,只代表 Grafana 本身正常。
通往 ClickHouse 的線通不通,在畫面上看:Connections → Data sources → ClickHouse-secstack,拉到最下面按 Save & test,出現 Data source is working 就是通的。
最後從 ClickHouse 那一側看有沒有人在用它。Grafana 發出的每一個查詢,ClickHouse 都記下了是哪個程式、替哪個 Grafana 使用者發的:
SELECT extract(client_name, 'grafana_user:([^;]+)') AS grafana_user, count() AS n
FROM system.query_log
WHERE client_name LIKE 'grafana%' AND type = 'QueryFinish'
AND event_time > now() - INTERVAL 1 DAY
GROUP BY grafana_user;
┌─grafana_user─┬──n─┐
1. │ admin │ 28 │
└──────────────┴────┘
查不到任何一列,代表過去一天沒有人開過 Grafana 的任何面板。在剛裝好、還沒建儀表板的節點上,這是正常的結果,也正是本篇開頭那句話的意思:線接好了,畫面還沒有人畫。
看 Grafana 的紀錄時,可能會遇到零星的 database is locked。那是它自己的資料庫在主機磁碟忙碌時一時寫不進去,背景工作下一輪會再做,不必處理。實測一台節點上,這個訊息只集中出現在安裝當天與更新當天。