收集 OAuth2 代理日志
本文档介绍了如何使用 Google Cloud Storage V2 将 OAuth2 代理日志注入到 Google Security Operations。
OAuth2 代理是一个 CNCF 沙盒反向代理,可使用 OAuth2/OIDC 提供程序(Google、GitHub、Keycloak、Azure AD 等)进行身份验证,并通过电子邮件、网域或群组验证账号。它会生成身份验证日志(登录成功/失败)、请求日志(包含用户身份的代理 HTTP 请求)和标准应用日志。由于 OAuth2 代理在 Kubernetes 中以容器形式运行,并将所有日志写入 stdout,因此使用 Kubernetes 原生日志收集器 (Fluentd) 将日志转发到 GCS 存储桶,以供 Google SecOps 提取。
准备工作
请确保满足以下前提条件:
- Google SecOps 实例
- 已启用 Cloud Storage API 的 GCP 项目
- 创建和管理 GCS 存储分区的权限
- 管理 GCS 存储分区的 IAM 政策的权限
- 已部署 OAuth2 代理(通过 Helm 图表或手动部署)的正在运行的 Kubernetes 集群
kubectl具有创建 DaemonSet、ConfigMap、Secret 和命名空间的权限的 Kubernetes 集群访问权限- 具有目标 GCS 存储桶的
storage.objects.create权限的 GCP 服务账号 JSON 密钥
创建 Google Cloud Storage 存储桶
- 前往 Google Cloud 控制台。
- 选择您的项目或创建新项目。
- 在导航菜单中,依次前往 Cloud Storage > 存储分区。
- 点击创建存储分区。
提供以下配置详细信息:
设置 值 为存储桶命名 输入一个全局唯一的名称(例如 oauth2-proxy-logs-bucket)位置类型 根据您的需求进行选择(区域级、双区域、多区域) 位置 选择营业地点(例如 us-central1)存储类别 标准(建议用于经常访问的日志) 访问权限控制 均匀(推荐) 保护工具 可选:启用对象版本控制或保留政策 点击创建。
配置 OAuth2 代理日志记录
OAuth2 代理会将三种类型的日志写入标准输出:标准日志、身份验证日志和请求日志。默认情况下,这三种日志均处于启用状态。为确保日志包含尽可能多的安全相关详细信息,请使用以下日志记录标志配置 OAuth2 代理。
方法 1:通过 Helm 图表值进行配置
如果 OAuth2 代理是使用官方 Helm 图表部署的,请将以下内容添加到您的
values.yaml文件中:config: configFile: |- standard_logging = true auth_logging = true request_logging = true silence_ping_logging = true standard_logging_format = "[{{.Timestamp}}] [{{.File}}] {{.Message}}" auth_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}" request_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"应用更新后的 Helm 值:
helm upgrade oauth2-proxy oauth2-proxy/oauth2-proxy -f values.yaml -n <your-namespace>
方法 2:通过命令行标志进行配置
如果 OAuth2 代理是使用 Kubernetes Deployment 清单部署的,请将以下实参添加到容器规范中:
args: - --standard-logging=true - --auth-logging=true - --request-logging=true - --silence-ping-logging=true
选项 3:通过环境变量进行配置
在 OAuth2 代理容器上设置以下环境变量:
env: - name: OAUTH2_PROXY_STANDARD_LOGGING value: "true" - name: OAUTH2_PROXY_AUTH_LOGGING value: "true" - name: OAUTH2_PROXY_REQUEST_LOGGING value: "true" - name: OAUTH2_PROXY_SILENCE_PING_LOGGING value: "true"
验证 OAuth2 代理日志记录
应用配置后,验证 OAuth2 代理是否正在生成日志:
kubectl logs -l app=oauth2-proxy -n <your-namespace> --tail=20输出包含类似于以下内容的行:
10.0.0.1 - abc123 - user@example.com [2024/01/15 10:30:00] [AuthSuccess] Authenticated via OAuth2 10.0.0.1 - abc123 - user@example.com [2024/01/15 10:30:01] example.com GET 10.0.0.5:8080 "/dashboard" HTTP/1.1 "Mozilla/5.0" 200 1234 0.005
为 Fluentd 创建 GCP 服务账号
Fluentd 需要具有对 GCS 存储桶的写入权限的 GCP 服务账号。
- 在 GCP 控制台中,依次前往 IAM 和管理 > 服务账号。
- 点击创建服务账号。
- 提供以下配置详细信息:
- 服务账号名称:输入
fluentd-gcs-writer - 服务账号说明:输入
Service account for Fluentd to write OAuth2 Proxy logs to GCS
- 服务账号名称:输入
- 点击创建并继续。
- 在向此服务账号授予对项目的访问权限部分:
- 点击选择角色。
- 搜索并选择 Storage Object Admin。
- 点击继续。
- 点击完成。
创建并下载服务账号密钥
- 在服务账号列表中,点击
fluentd-gcs-writer服务账号。 - 前往密钥标签页。
- 点击添加密钥 > 创建新密钥。
- 选择 JSON 作为密钥类型。
- 点击创建。
- 请妥善保存下载的 JSON 密钥文件。此文件将在下一步中使用。
为服务账号密钥创建 Kubernetes Secret
在将部署 Fluentd 的命名空间中,创建一个包含 GCP 服务账号密钥的 Kubernetes Secret:
kubectl create namespace logging kubectl create secret generic fluentd-gcs-key \ --from-file=service-account-key.json=<path-to-downloaded-key>.json \ -n logging
部署 Fluentd DaemonSet 以收集 OAuth2 代理日志
在 Kubernetes 集群中将 Fluentd 部署为 DaemonSet,以从 OAuth2 代理 pod 收集容器日志并将其转发到 GCS 存储桶。
创建 Fluentd ConfigMap
创建一个名为
fluentd-configmap.yaml的文件,其中包含以下内容:apiVersion: v1 kind: ConfigMap metadata: name: fluentd-gcs-config namespace: logging data: fluent.conf: | <source> @type tail read_from_head true tag kubernetes.* path /var/log/containers/*oauth2-proxy*.log pos_file /var/log/fluentd-oauth2-proxy.log.pos <parse> @type regexp expression /^(?<time>[^ ]+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/ time_format %Y-%m-%dT%H:%M:%S.%N%z </parse> </source> <filter kubernetes.**> @type kubernetes_metadata @id filter_kube_metadata </filter> <match kubernetes.**> @type gcs project YOUR_GCP_PROJECT_ID keyfile /etc/secrets/service-account-key.json bucket oauth2-proxy-logs-bucket path oauth2-proxy-logs/%Y/%m/%d/ object_key_format %{path}%{time_slice}_%{hostname}_%{index}.%{file_extension} <buffer tag,time> @type file path /var/log/fluentd/gcs timekey 300 timekey_wait 60 timekey_use_utc true chunk_limit_size 10MB </buffer> <format> @type json </format> </match>
替换以下值:
YOUR_GCP_PROJECT_ID:您的 GCP 项目 ID(例如my-project-123456)oauth2-proxy-logs-bucket:之前创建的 GCS 存储桶的名称应用 ConfigMap:
kubectl apply -f fluentd-configmap.yaml
创建 Fluentd DaemonSet
创建一个名为
fluentd-daemonset.yaml的文件,其中包含以下内容:apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-gcs namespace: logging labels: k8s-app: fluentd-gcs spec: selector: matchLabels: k8s-app: fluentd-gcs template: metadata: labels: k8s-app: fluentd-gcs spec: tolerations: - key: node-role.kubernetes.io/control-plane effect: NoSchedule - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd-gcs image: fluent/fluentd-kubernetes-daemonset:v1-debian-gcs resources: limits: memory: 512Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: fluentd-gcs-config-volume mountPath: /fluentd/etc/fluent.conf subPath: fluent.conf readOnly: true - name: fluentd-gcs-secrets-volume mountPath: /etc/secrets/service-account-key.json subPath: service-account-key.json readOnly: true - name: varlog mountPath: /var/log - name: dockercontainerlogdirectory mountPath: /var/log/pods readOnly: true - name: fluentd-buffer mountPath: /var/log/fluentd terminationGracePeriodSeconds: 30 volumes: - name: fluentd-gcs-config-volume configMap: name: fluentd-gcs-config - name: fluentd-gcs-secrets-volume secret: secretName: fluentd-gcs-key - name: varlog hostPath: path: /var/log - name: dockercontainerlogdirectory hostPath: path: /var/log/pods - name: fluentd-buffer emptyDir: {}应用 DaemonSet:
kubectl apply -f fluentd-daemonset.yaml
验证 Fluentd 部署
验证 Fluentd pod 是否在每个节点上运行:
kubectl get pods -n logging -l k8s-app=fluentd-gcs检查 Fluentd 日志,确认是否已成功写入 GCS:
kubectl logs -l k8s-app=fluentd-gcs -n logging --tail=20验证日志文件是否显示在 GCS 存储桶中:
- 在 GCP Console 中,前往 Cloud Storage > 存储分区。
- 点击相应存储桶(例如
oauth2-proxy-logs-bucket)。 - 转到
oauth2-proxy-logs/文件夹。 - 验证是否存在具有最新时间戳的
.json文件。
检索 Google SecOps 服务账号
Google SecOps 使用唯一的服务账号从您的 GCS 存储桶中读取数据。您必须授予此服务账号对您的存储桶的访问权限。
获取服务账号电子邮件地址
- 依次前往 SIEM 设置 > Feed。
- 点击添加新 Feed。
- 点击配置单个 Feed。
- 在 Feed 名称字段中,输入 Feed 的名称(例如
OAuth2 Proxy Logs)。 - 选择 Google Cloud Storage V2 作为来源类型。
- 选择 Kubernetes Auth Proxy 作为日志类型。
点击获取服务账号。系统会显示一个唯一的服务账号电子邮件地址,例如:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com复制此电子邮件地址,以便在下一步中使用。
点击下一步。
为以下输入参数指定值:
存储桶网址:输入带有前缀路径的 GCS 存储桶 URI:
gs://oauth2-proxy-logs-bucket/oauth2-proxy-logs/将
oauth2-proxy-logs-bucket替换为您的 GCS 存储桶名称。
来源删除选项:根据您的偏好选择删除选项:
- 永不:转移后永不删除任何文件(建议用于测试)。
- 删除已转移的文件:在成功转移后删除文件。
删除已转移的文件和空目录:成功转移后删除文件和空目录。
文件存在时间上限:包含在过去指定天数内修改的文件(默认值为 180 天)
资产命名空间:资产命名空间
注入标签:要应用于此 Feed 中事件的标签
点击下一步。
在最终确定界面中查看新的 Feed 配置,然后点击提交。
向 Google SecOps 服务账号授予 IAM 权限
Google SecOps 服务账号需要您的 GCS 存储桶的 Storage Object Viewer 角色。
- 前往 Cloud Storage > 存储分区。
- 点击您的存储桶名称(例如
oauth2-proxy-logs-bucket)。 - 前往权限标签页。
- 点击授予访问权限。
- 提供以下配置详细信息:
- 添加主账号:粘贴 Google SecOps 服务账号电子邮件地址(例如
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com) - 分配角色:选择 Storage Object Viewer
- 添加主账号:粘贴 Google SecOps 服务账号电子邮件地址(例如
- 点击保存。
OAuth2 代理日志参考文档
下表介绍了 OAuth2 代理生成的日志字段及其安全相关性:
身份验证日志字段
| 字段 | 示例 | 说明 |
|---|---|---|
| 客户 | 10.0.0.1 |
客户端/远程 IP 地址(如果 --reverse-proxy=true,则使用 X-Real-IP 标头) |
| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f |
X-Request-Id 标头中的请求 ID(如果为空,则为随机 UUID) |
| 用户名 | user@example.com |
身份验证请求的电子邮件地址或用户名 |
| 时间戳 | 2024/01/15 10:30:00 |
身份验证事件的日期和时间 |
| 状态 | AuthSuccess |
身份验证结果:AuthSuccess、AuthFailure 或 AuthError |
| 消息 | Authenticated via OAuth2 |
身份验证尝试的详细信息 |
请求日志字段
| 字段 | 示例 | 说明 |
|---|---|---|
| 客户 | 10.0.0.1 |
客户端/远程 IP 地址 |
| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f |
请求 ID |
| 用户名 | user@example.com |
经过身份验证的用户的电子邮件地址 |
| 时间戳 | 2024/01/15 10:30:01 |
请求的日期和时间 |
| 主机 | app.example.com |
Host 标头的值 |
| RequestMethod | GET |
HTTP 请求方法 |
| 上行 | 10.0.0.5:8080 |
处理请求的上游服务器 |
| RequestURI | /dashboard |
请求的 URI 路径 |
| 协议 | HTTP/1.1 |
请求协议 |
| UserAgent | Mozilla/5.0 |
完整的用户代理字符串 |
| StatusCode | 200 |
HTTP 响应状态代码 |
| ResponseSize | 1234 |
响应大小(以字节为单位) |
| RequestDuration | 0.005 |
请求处理时间(以秒为单位) |
UDM 映射表
| 日志字段 | UDM 映射 | 逻辑 |
|---|---|---|
| 关于 | 关于 | 活动相关信息 |
| http_req_id_field | additional.fields | 标准 UDM 架构未涵盖的其他字段 |
| http_req_path_field | additional.fields | |
| k8s_pod_app_field | additional.fields | |
| k8s_pod_template_hash_field | additional.fields | |
| k8s_pod_tls_mode_field | additional.fields | |
| k8s_pod_canonical_revision_field | additional.fields | |
| k8s_pod_canonical_name_field | additional.fields | |
| pod_name | additional.fields | |
| cntnr_name | additional.fields | |
| destination_canonical_revision | additional.fields | |
| requested_server | additional.fields | |
| nodename_label | additional.fields | |
| componentName_label | additional.fields | |
| componentVersion_label | additional.fields | |
| azureResourceID_label | additional.fields | |
| producer_label | additional.fields | |
| first_label | additional.fields | |
| last_label | additional.fields | |
| meta_name | additional.fields | |
| resource_version_label | additional.fields | |
| request_apiVersion | additional.fields | |
| request_kind_label | additional.fields | |
| request_type_label | additional.fields | |
| response_apiVersion | additional.fields | |
| response_kind_label | additional.fields | |
| response_type_label | additional.fields | |
| jsonPayload.message | metadata.description | 事件的说明 |
| event_type | metadata.event_type | 事件类型 |
| labels.request_id | metadata.product_log_id | 特定于产品的日志标识符 |
| insertId | metadata.product_log_id | |
| jsonPayload.chartVersion | metadata.product_version | 产品版本 |
| httpRequest.protocol | network.application_protocol | 网络连接中使用的应用协议 |
| network.direction | network.direction | 网络流量方向 |
| httpRequest.requestMethod | network.http.method | HTTP 方法 |
| http_method | network.http.method | |
| httpRequest.status | network.http.response_code | HTTP 响应代码 |
| httpRequest.userAgent | network.http.user_agent | HTTP 用户代理 |
| requestMetadata.callerSuppliedUserAgent | network.http.user_agent | |
| labels.protocol | network.ip_protocol | IP 协议 |
| httpRequest.responseSize | network.received_bytes | 接收的字节数 |
| labels.total_received_bytes | network.received_bytes | |
| httpRequest.requestSize | network.sent_bytes | 发送的字节数 |
| labels.total_sent_bytes | network.sent_bytes | |
| jsonPayload.session | network.session_id | 会话标识符 |
| labels.service_authentication_policy | network.tls.cipher | TLS 加密套件 |
| 主账号 | 主账号 | 涉及事件的主账号实体 |
| principal_hostname | principal.hostname | 主账号的主机名 |
| prin_userid | principal.user.userid | 主账号的用户 ID |
| security_result | security_result | 安全评估结果 |
| 目标 | 目标 | 涉及事件的目标实体 |
| target_hostname | target.hostname | 目标的主机名 |
| resource_sub_type | target.resource.resource_subtype | 目标资源的子类型 |
| target_userid | target.user.userid | 目标的相应用户 ID |
| metadata.product_name | metadata.product_name | 产品名称 |
| metadata.vendor_name | metadata.vendor_name | 供应商名称 |