收集 Keycloak 記錄
本文說明如何設定 Keycloak,透過 Webhook 將記錄檔推送至 Google Security Operations。
Keycloak 是一項開放原始碼的身分與存取權管理 (IAM) 解決方案,提供單一登入 (SSO)、使用者同盟、身分中介服務和社群登入功能。支援 OpenID Connect、OAuth 2.0 和 SAML 2.0 通訊協定,並追蹤使用者事件 (登入、登出、註冊、變更密碼) 和管理員事件 (使用者、用戶端、領域和角色管理作業),以進行安全稽核。
事前準備
請確認您已完成下列事前準備事項:
- Google SecOps 執行個體
- 正在執行的 Keycloak 執行個體 (建議使用 20 以上版本)
- Keycloak 管理控制台的管理員存取權
- 存取 Keycloak 伺服器檔案系統或容器,以部署擴充功能
- 存取 Google Cloud 控制台 (用於建立 API 金鑰)
在 Google SecOps 中建立 Webhook 動態饋給
建立動態饋給
- 依序前往「SIEM 設定」>「動態饋給」。
- 按一下「新增動態消息」。
- 在下一個頁面中,按一下「設定單一動態饋給」。
- 在「動態饋給名稱」欄位中輸入動態饋給名稱 (例如
Keycloak Events)。 - 選取「Webhook」做為「來源類型」。
- 選取「Keycloak」做為「記錄類型」。
- 點選「下一步」。
- 指定下列輸入參數的值:
- 分割分隔符號 (選填):輸入
\n分割多行事件 (每個 Webhook POST 都包含單一事件,因此可以留空)。 - 資產命名空間:資產命名空間
- 擷取標籤:要套用至這個動態饋給事件的標籤
- 分割分隔符號 (選填):輸入
- 點選「下一步」。
- 在「Finalize」(完成) 畫面中檢查新的動態饋給設定,然後按一下「Submit」(提交)。
產生並儲存密鑰
建立動態饋給後,您必須產生驗證用的密鑰:
- 在動態饋給詳細資料頁面中,按一下「產生密鑰」。
- 對話方塊會顯示密鑰。
- 複製並妥善儲存密鑰。
重要事項:密鑰只會顯示一次,之後便無法擷取,如果遺失,就必須產生新的密鑰。
取得動態消息端點網址
- 前往動態消息的「詳細資料」分頁。
- 在「端點資訊」部分,複製「動態消息端點網址」。
網址格式為:
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate或
https://<REGION>-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate請儲存這個網址,以供後續步驟使用。
按一下 [完成]。
建立 Google Cloud API 金鑰
Chronicle 需要 API 金鑰才能進行驗證。在 Google Cloud 控制台中建立受限制的 API 金鑰。
建立 API 金鑰
- 前往 Google Cloud 控制台的「憑證」頁面。
- 選取專案 (與 Chronicle 執行個體相關聯的專案)。
- 依序按一下「建立憑證」>「API 金鑰」。
- 系統會建立 API 金鑰,並顯示在對話方塊中。
- 按一下「編輯 API 金鑰」即可限制金鑰。
限制 API 金鑰
- 在「API 金鑰」設定頁面中:
- 名稱:輸入描述性名稱 (例如
Chronicle Webhook API Key)
- 名稱:輸入描述性名稱 (例如
- 在「API 限制」下方:
- 選取「Restrict key」(限制金鑰)。
- 在「選取 API」下拉式選單中,搜尋並選取「Google SecOps API」 (或「Chronicle API」)。
- 按一下 [儲存]。
- 從頁面頂端的「API key」(API 金鑰) 欄位複製 API 金鑰值。
- 安全地儲存 API 金鑰。
在 Keycloak 中啟用事件儲存空間
設定 Webhook 擴充功能前,請先在 Keycloak 中啟用事件儲存功能,以便產生事件並轉送。
啟用使用者事件
- 登入 Keycloak 管理控制台。
- 從左上角的領域下拉式選單中,選取要監控的領域。
- 依序前往「領域設定」>「活動」。
- 選取「使用者事件設定」子分頁標籤。
- 啟用「儲存活動」切換鈕。
- 設定「到期」期限 (建議至少 7 天)。
- 按一下 [儲存]。
啟用管理員事件
- 在同一個「事件」分頁中,選取「管理事件設定」子分頁。
- 啟用「儲存活動」切換鈕。
- 啟用「Include representation」(包含代表項目) 切換鈕,即可擷取變更物件的完整詳細資料。
- 設定「到期」期限 (建議至少 7 天)。
- 按一下 [儲存]。
安裝 Webhook 事件監聽器擴充功能
Keycloak 不含原生 Webhook 事件監聽器。從 Phase Two (p2-inc) 安裝 keycloak-events 擴充功能,啟用 Webhook 傳送功能。
下載及部署擴充功能
從 Maven Central 的 keycloak-events 發布頁面下載最新發布的 JAR,或從來源建構:
git clone https://github.com/p2-inc/keycloak-events.git cd keycloak-events mvn clean install將產生的 Fat JAR 檔案複製到 Keycloak
providers目錄:cp target/keycloak-events-*.jar /opt/keycloak/providers/重建並重新啟動 Keycloak:
/opt/keycloak/bin/kc.sh build /opt/keycloak/bin/kc.sh start
啟用 Webhook 事件監聽器
- 登入 Keycloak 管理控制台。
- 從領域下拉式選單中選取目標領域。
- 依序前往「領域設定」>「活動」。
- 在「事件監聽器」下拉式選單中,選取「ext-event-webhook」。
- 按一下 [儲存]。
設定 Keycloak Webhook
建構 Webhook 網址
合併 Chronicle 端點網址和 API 金鑰:
<ENDPOINT_URL>?key=<API_KEY>範例:
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...
透過 Keycloak REST API 建立 Webhook 訂閱項目
keycloak-events 擴充功能提供 REST 端點,用於管理 Webhook 訂閱項目。使用 Keycloak Admin REST API 建立 Webhook。
步驟 1:取得存取權杖
使用管理員帳戶向 Keycloak 要求存取權杖:
TOKEN=$(curl -sS -X POST "https://<KEYCLOAK_HOST>/realms/master/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=password" \ --data-urlencode "client_id=admin-cli" \ --data-urlencode "username=<ADMIN_USERNAME>" \ --data-urlencode "password=<ADMIN_PASSWORD>" \ | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
更改下列內容:
<KEYCLOAK_HOST>:Keycloak 伺服器主機名稱和通訊埠 (例如keycloak.example.com:8443)<ADMIN_USERNAME>:Keycloak 管理員使用者名稱<ADMIN_PASSWORD>:Keycloak 管理員密碼
步驟 2:建立 Webhook
傳送 POST 要求,為目標領域建立 Webhook 訂閱項目:
curl -sS -X POST "https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/webhooks" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "enabled": "true", "url": "<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>", "secret": "<WEBHOOK_HMAC_SECRET>", "eventTypes": ["*"] }'
更改下列內容:
<KEYCLOAK_HOST>:Keycloak 伺服器主機名稱<REALM_NAME>:要監控的領域名稱 (例如master或my-realm)<ENDPOINT_URL>:先前複製的 Chronicle 動態消息端點網址<API_KEY>:先前建立的 Google Cloud API 金鑰<SECRET_KEY>:先前產生的 Chronicle Webhook 密鑰<WEBHOOK_HMAC_SECRET>:任意密碼字串,用於對 webhook 酬載進行 HMAC 簽署 (例如mySecretKey123)
步驟 3:驗證 Webhook
列出領域的所有 Webhook,確認是否已建立 Webhook:
curl -sS -X GET "https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/webhooks" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Accept: application/json"
回應會傳回 Webhook 物件清單。確認網路鉤子顯示 "enabled": "true" 和正確的網址。
Webhook 事件類型
eventTypes 欄位接受運算式陣列,可篩選要傳送的事件:
*- 傳送所有事件 (建議用於 SIEM 整合)access.*- 傳送所有存取事件admin.*- 傳送所有管理員事件admin.USER-*- 傳送與使用者相關的所有管理員事件admin-USER-CREATE- 只傳送使用者建立管理員事件
Webhook 酬載格式
Webhook 會以 HTTP POST 要求的形式傳送事件,並附上 JSON 酬載。使用者事件酬載範例:
{ "id": "987865-1a2b-3c4d-9876-654321abc", "time": 1767799710612, "type": "LOGIN", "realmId": "12345abcde-1a2b-4d3c-9876-abcd456", "clientId": "account-console", "userId": "abcd456-1234-5678-abc9-987gfed654", "sessionId": "efghij-9876-abcd-456-11223344", "ipAddress": "203.0.113.45", "details": { "auth_method": "openid-connect", "auth_type": "code", "redirect_uri": "https://app.example.com/callback", "consent": "no_consent_required", "username": "jdoe" } }
Webhook 重試行為
如果收到非 2xx 的回應,擴充功能會自動以指數輪詢方式重試:
| 參數 | 預設值 | 說明 |
|---|---|---|
| backoffInitialInterval | 500 毫秒 | 初始重試間隔 |
| backoffMaxElapsedTime | 900000 毫秒 (15 分鐘) | 重試總時間上限 |
| backoffMaxInterval | 180000 毫秒 (3 分鐘) | 重試間隔時間上限 |
| backoffMultiplier | 5 | 每次重試間隔的乘數 |
| backoffRandomizationFactor | 0.5 | 抖動的隨機化係數 |
驗證方法參考資料
Chronicle 網頁掛鉤動態消息支援多種驗證方法。選擇供應商支援的方法。
方法 1:自訂標頭 (建議)
如果供應商支援自訂 HTTP 標頭,請使用這個方法,以提升安全性。
要求格式:
POST <ENDPOINT_URL> HTTP/1.1 Content-Type: application/json x-goog-chronicle-auth: <API_KEY> x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
優點:
- 網址中不會顯示 API 金鑰和密鑰
- 更安全 (標頭不會記錄在網路伺服器存取記錄中)
- 如果供應商支援,則為首選方法
方法 2:查詢參數
如果供應商不支援自訂標頭,請將憑證附加至網址。
網址格式:
<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>範例:
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...&secret=abcd1234...要求格式:
POST <ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY> HTTP/1.1 Content-Type: application/json { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
缺點:
- 網址中顯示憑證
- 可能會記錄在網路伺服器存取記錄中
- 安全性不如標頭
方法 3:混合式 (網址 + 標頭)
部分設定會在網址中使用 API 金鑰,並在標頭中使用密鑰。
要求格式:
POST <ENDPOINT_URL>?key=<API_KEY> HTTP/1.1 Content-Type: application/json x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
驗證標頭名稱
Chronicle 接受下列驗證標頭名稱:
API 金鑰:
x-goog-chronicle-auth(建議)X-Goog-Chronicle-Auth(不區分大小寫)
密鑰:
x-chronicle-auth(建議)X-Chronicle-Auth(不區分大小寫)
Webhook 限制和最佳做法
要求限制
| 限制 | 值 |
|---|---|
| 要求大小上限 | 4 MB |
| 每秒查詢次數 (QPS) 上限 | 15,000 |
| 要求逾時 | 30 秒 |
| 重試行為 | 自動執行指數輪詢 |
UDM 對應表
| 記錄欄位 | UDM 對應 | 邏輯 |
|---|---|---|
| payload.client_id | additional.fields | 與從 payload.client_id、payload.realm_id 建立的欄位合併 |
| payload.realm_id | additional.fields | |
| source_timestamp | metadata.event_timestamp | 使用日期篩選器和 ISO8601 與 yyyy-MM-dd'T'HH:mm:ss.SSSZ 模式剖析 |
| payload.ip_address | metadata.event_type | 如果 payload.ip_address 不為空白,則設為「STATUS_UPDATE」;如果 uuid 不為空白,則設為「USER_UNCATEGORIZED」;否則設為「GENERIC_EVENT」 |
| uuid | metadata.event_type | |
| payload.type | metadata.product_event_type | 直接複製值 |
| payload.session_id | network.session_id | 直接複製值 |
| payload.ip_address | principal.ip | 直接複製值 |
| source_metadata.schema | principal.resource.attribute.labels | 與從 source_metadata.schema、source_metadata.table、source_metadata.is_deleted (轉換為字串)、source_metadata.change_type、source_metadata.tx_id、source_metadata.lsn 建立的標籤合併 |
| source_metadata.table | principal.resource.attribute.labels | |
| source_metadata.is_deleted | principal.resource.attribute.labels | |
| source_metadata.change_type | principal.resource.attribute.labels | |
| source_metadata.tx_id | principal.resource.attribute.labels | |
| source_metadata.lsn | principal.resource.attribute.labels | |
| uuid | principal.user.userid | 直接複製值 |
| 物件 | security_result.detection_fields | 與從物件、read_method、payload.id 建立的標籤合併 |
| read_method | security_result.detection_fields | |
| payload.id | security_result.detection_fields | |
| redirect_uri | target.url | 直接複製值 |
| 使用者名稱 | target.user.userid | 直接複製值 |
| metadata.product_name | metadata.product_name | 設為「KEYCLOAK」 |
| metadata.vendor_name | metadata.vendor_name | 設為「KEYCLOAK」 |
username" from "details_json |
target.user.userid |
從變更記錄對應 |
redirect_uri" from "details_json |
target.url |
從變更記錄對應 |
realm_id" and "client_id |
additional.fields |
從變更記錄對應 |
變更記錄
還有其他問題嗎?向社群成員和 Google SecOps 專業人員尋求答案。