收集 Keycloak 日志

支持的平台:

本文档介绍了如何配置 Keycloak 以使用 Webhook 将日志推送到 Google Security Operations。

Keycloak 是一款开源身份和访问权限管理 (IAM) 解决方案,提供单点登录 (SSO)、用户联合身份验证、身份代理和社交登录功能。它支持 OpenID Connect、OAuth 2.0 和 SAML 2.0 协议,并跟踪用户事件(登录、退出、注册、密码更改)和管理员事件(用户、客户端、Realm 和角色管理操作),以进行安全审核。

准备工作

请确保您满足以下前提条件:

  • Google SecOps 实例
  • 正在运行的 Keycloak 实例(建议使用 20 或更高版本)
  • 对 Keycloak 管理控制台的管理员访问权限
  • 对 Keycloak 服务器文件系统或容器的访问权限,用于部署扩展程序
  • 对 Google Cloud Console 的访问权限(用于创建 API 密钥)

在 Google SecOps 中创建 Webhook Feed

创建 Feed

  1. 依次前往 SIEM 设置 > Feed
  2. 点击添加新 Feed
  3. 在下一页上,点击配置单个 Feed
  4. Feed 名称字段中,输入 Feed 的名称(例如 Keycloak Events)。
  5. 选择 Webhook 作为来源类型
  6. 选择 Keycloak 作为日志类型
  7. 点击下一步
  8. 为以下输入参数指定值:
    • 定界符(可选):输入 \n 以拆分多行事件(每个网络钩子 POST 都包含一个事件,因此可以留空)。
    • 资产命名空间资产命名空间
    • 注入标签:要应用于此 Feed 中事件的标签
  9. 点击下一步
  10. 最终确定界面中查看新的 Feed 配置,然后点击提交

生成并保存密钥

创建 Feed 后,您必须生成用于身份验证的密钥:

  1. 在 Feed 详情页面上,点击生成密钥
  2. 系统会显示一个对话框,其中包含密钥。
  3. 复制并妥善保存此密钥。

重要提示:密钥只会显示一次,之后无法再检索。如果丢失,您必须生成新的密钥。

获取 Feed 端点网址

  1. 前往相应 Feed 的详细信息标签页。
  2. 端点信息部分,复制 Feed 端点网址
  3. 网址格式为:

    https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate
    

    https://<REGION>-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate
    
  4. 保存此网址以供后续步骤使用。

  5. 点击完成

创建 Google Cloud API 密钥

Chronicle 需要 API 密钥才能进行身份验证。在 Google Cloud Console 中创建受限 API 密钥。

创建 API 密钥

  1. 前往 Google Cloud 控制台的“凭据”页面
  2. 选择您的项目(与您的 Chronicle 实例关联的项目)。
  3. 依次点击创建凭据> API 密钥
  4. 系统会创建一个 API 密钥,并在对话框中显示该密钥。
  5. 点击修改 API 密钥以限制密钥。

限制 API 密钥

  1. API 密钥设置页面中:
    • 名称:输入一个描述性名称(例如 Chronicle Webhook API Key
  2. API 限制下:
    1. 选择限制密钥
    2. 选择 API 下拉菜单中,搜索并选择 Google SecOps API(或 Chronicle API)。
  3. 点击保存
  4. 从页面顶部的 API 密钥字段复制 API 密钥值。
  5. 安全地保存 API 密钥。

在 Keycloak 中启用事件存储

在配置 webhook 扩展程序之前,请在 Keycloak 中启用事件存储,以便生成事件并可供转发。

启用用户事件

  1. 登录 Keycloak 管理控制台
  2. 从左上角的下拉菜单中选择要监控的大区
  3. 前往 Realm Settings > Events
  4. 选择用户事件设置子标签页。
  5. 启用保存活动切换开关。
  6. 设置到期期限(建议至少 7 天)。
  7. 点击保存

启用管理员事件

  1. 在同一事件标签页中,选择管理事件设置子标签页。
  2. 启用保存活动切换开关。
  3. 启用包含表示形式切换开关,以捕获已更改对象的完整详细信息。
  4. 设置到期期限(建议至少 7 天)。
  5. 点击保存

安装 Webhook 事件监听器扩展程序

Keycloak 不包含原生网络钩子事件监听器。安装第二阶段 (p2-inc) 的 keycloak-events 扩展程序,以启用 webhook 传送。

下载并部署扩展程序

  1. Maven Central 上的 keycloak-events 发布页面下载最新的发布 JAR,或从源代码构建:

    git clone https://github.com/p2-inc/keycloak-events.git
    cd keycloak-events
    mvn clean install
    
  2. 将生成的胖 JAR 文件复制到 Keycloak providers 目录中:

    cp target/keycloak-events-*.jar /opt/keycloak/providers/
    
  3. 重新构建并重启 Keycloak:

    /opt/keycloak/bin/kc.sh build
    /opt/keycloak/bin/kc.sh start
    

启用 webhook 事件监听器

  1. 登录 Keycloak 管理控制台
  2. 从 realm 下拉菜单中选择目标 realm
  3. 前往 Realm Settings > Events
  4. 事件监听器下拉菜单中,选择 ext-event-webhook
  5. 点击保存

配置 Keycloak 网络钩子

构建网络钩子网址

  • 将 Chronicle 端点网址和 API 密钥组合在一起:

    <ENDPOINT_URL>?key=<API_KEY>
    
  • 示例

    https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...
    

通过 Keycloak REST API 创建 Webhook 订阅

keycloak-events 扩展程序提供用于管理 Webhook 订阅的 REST 端点。使用 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 请求,为目标 realm 创建 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>:要监控的大区的名称(例如 mastermy-realm
  • <ENDPOINT_URL>:之前复制的 Chronicle Feed 端点网址
  • <API_KEY>:之前创建的 Google Cloud API 密钥
  • <SECRET_KEY>:之前生成的 Chronicle Webhook 密钥
  • <WEBHOOK_HMAC_SECRET>:用于对 Webhook 载荷进行 HMAC 签名的任意密文(例如,mySecretKey123

第 3 步:验证 Webhook

  • 通过列出相应 realm 的所有 webhook 来确认 webhook 是否已创建:

    curl -sS -X GET "https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/webhooks" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Accept: application/json"
    

响应会返回一个 webhook 对象列表。验证您的 Webhook 是否显示 "enabled": "true" 和正确的网址。

网络钩子事件类型

eventTypes 字段接受一个表达式数组,用于过滤要发送的事件:

  • * - 发送所有事件(建议用于 SIEM 集成)
  • access.* - 发送所有访问事件
  • admin.* - 发送所有管理员事件
  • admin.USER-* - 发送与用户相关的所有管理员事件
  • admin-USER-CREATE - 仅发送用户创建管理员事件

网络钩子载荷格式

  • Webhook 以包含 JSON 载荷的 HTTP POST 请求的形式发送事件。用户事件载荷示例:

    {
      "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 webhook Feed 支持多种身份验证方法。选择供应商支持的方法。

如果您的供应商支持自定义 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 密钥和密文在网址中不可见
  • 更安全(标头不会记录在 Web 服务器访问日志中)
  • 供应商支持时的首选方法

方法 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"
    }
    

缺点:

  • 网址中显示的凭据
  • 可能会记录在 Web 服务器访问日志中
  • 安全性不如标头

方法 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(每秒查询次数) 15000
请求超时 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 专业人士的解答。