收集 CrowdStrike Falcon Event Streams 日志

解析器版本:12.0

支持的平台:

本文档介绍了如何使用 Google Cloud Storage V2 将 CrowdStrike Falcon Event Streams 日志注入到 Google Security Operations。CrowdStrike Falcon Event Streams 提供实时流式传输 API,可从 Falcon 平台传送安全事件数据,包括检测事件、审核事件、身份验证活动和突发事件更新。Event Streams API 使用与 /sensors/entities/datafeed/v2 端点的持久 HTTP 连接来近乎实时地推送事件,并支持基于偏移量的恢复,以实现可靠的传送。

准备工作

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

  • Google SecOps 实例。
  • 启用了 Cloud Storage API 的 GCP 项目。
  • 创建和管理 GCS 存储桶的权限。
  • 管理 GCS 存储桶的 IAM 政策的权限。
  • 创建 Cloud Run 服务、Pub/Sub 主题和 Cloud Scheduler 作业的权限。
  • 对 CrowdStrike Falcon 控制台的特权访问权限,并具有创建 API 客户端的权限。

创建 Google Cloud Storage 存储桶

  1. 前往 Google Cloud 控制台。
  2. 选择您的项目或创建一个新项目。
  3. 在导航菜单中,依次前往 Cloud Storage > 存储桶。
  4. 点击创建存储分区。
  5. 提供以下配置详细信息:

    设置 值
    为存储桶命名 输入一个全局唯一的名称(例如 cs-stream-logs)
    位置类型 根据您的需求进行选择(区域级、双区域级、多区域级)
    位置 选择位置(例如 us-central1)
    存储类别 标准(建议用于频繁访问的日志)
    访问权限控制 均匀(推荐)
    保护工具 可选:启用对象版本控制或保留政策
  6. 点击创建。

配置 CrowdStrike Falcon API 访问权限

创建 API 客户端

  1. 登录 CrowdStrike Falcon 控制台。
  2. 在导航菜单中,依次前往支持和资源 > 资源和工具 > API 客户端和密钥。
  3. 点击创建 API 客户端。
  4. 提供以下配置详细信息:
    • 客户端名称:输入一个描述性名称(例如 Google SecOps Event Streams Integration)。
    • 说明(可选):输入说明(例如 API client for streaming events to GCS)。
  5. 在 API 范围部分,选择以下权限:
    • 事件流:选择读取(使用流式传输 API 所必需)。
  6. 点击创建。

记录 API 凭据

创建 API 客户端后,您会收到以下凭据:

  • 客户端 ID:一个 32 字符的小写十六进制字符串。
  • 客户端密钥:一个包含 40 个字符的字母数字字符串。
  • 基础网址:CrowdStrike API 的完全限定域名(例如 api.crowdstrike.com 或 api.us-2.crowdstrike.com)。

重要提示:请立即复制并保存客户端密钥。它只会显示一次,之后无法再检索。

区域端点

CrowdStrike 在多个区域运营,并提供不同的 API 端点:

区域 基础网址
US-1 api.crowdstrike.com
US-2 api.us-2.crowdstrike.com
EU-1 api.eu-1.crowdstrike.com
US-GOV-1 api.laggar.gcw.crowdstrike.com

重要提示:请使用与您的 CrowdStrike Falcon 控制台登录区域相对应的基础网址。

验证权限

如需验证 API 客户端是否具有所需的权限,请执行以下操作:

  1. 登录 CrowdStrike Falcon 控制台。
  2. 依次前往支持和资源 > 资源和工具 > API 客户端和密钥。
  3. 找到您创建的 API 客户端,并验证事件流范围是否显示读取访问权限。

测试 API 访问权限

  • 在继续进行集成之前,请测试您的凭据:

    CLIENT_ID="<your-client-id>"
    CLIENT_SECRET="<your-client-secret>"
    BASE_URL="https://api.crowdstrike.com"
    
    # Get OAuth2 token
    TOKEN=$(curl -s -X POST "${BASE_URL}/oauth2/token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
    
    # Test Event Streams API access (discover available data feeds)
    curl -s -H "Authorization: Bearer ${TOKEN}" \
      "${BASE_URL}/sensors/entities/datafeed/v2?appId=secops-test&format=flatjson"
    

如果请求成功,则会返回一个 JSON 对象,其中包含 resources,以及每个可用流分区的 dataFeedURL 和 sessionToken 字段。

为 Cloud Run 函数创建服务账号

Cloud Run 函数需要一个服务账号,该账号具有写入 GCS 存储桶和被 Pub/Sub 调用的权限。

创建服务账号

  1. 在 GCP 控制台中,依次前往 IAM 和管理 > 服务账号。
  2. 点击创建服务账号。
  3. 提供以下配置详细信息:
    • 服务账号名称:输入 cs-stream-collector-sa
    • 服务账号说明:输入 Service account for Cloud Run function to collect CrowdStrike Event Streams logs
  4. 点击创建并继续。
  5. 在向此服务账号授予对项目的访问权限部分,添加以下角色:
    1. 点击选择角色。
    2. 搜索并选择 Storage Object Admin。
    3. 点击 + 添加其他角色。
    4. 搜索并选择 Cloud Run Invoker。
    5. 点击 + 添加其他角色。
    6. 搜索并选择 Cloud Functions Invoker。
  6. 点击继续。
  7. 点击完成。

以下操作需要这些角色:

  • Storage Object Admin:将日志写入 GCS 存储桶并管理状态文件
  • Cloud Run Invoker:允许 Pub/Sub 调用函数
  • Cloud Functions Invoker:允许调用函数

授予对 GCS 存储桶的 IAM 权限

向服务账号授予对 GCS 存储桶的写入权限:

  1. 前往 Cloud Storage > 存储桶。
  2. 点击您的存储桶名称(例如 cs-stream-logs)。
  3. 前往权限标签页。
  4. 点击授予访问权限。
  5. 提供以下配置详细信息:
    • 添加主账号:输入服务账号电子邮件地址(例如 cs-stream-collector-sa@PROJECT_ID.iam.gserviceaccount.com)
    • 分配角色:选择 Storage Object Admin
  6. 点击保存。

创建 Pub/Sub 主题

创建一个 Pub/Sub 主题,供 Cloud Scheduler 发布消息,并供 Cloud Run 函数订阅。

  1. 在 GCP 控制台中,前往 Pub/Sub > 主题。
  2. 点击创建主题。
  3. 提供以下配置详细信息:
    • 主题 ID:输入 cs-stream-trigger
    • 将其他设置保留为默认值
  4. 点击创建。

创建 Cloud Run 函数以收集日志

Cloud Run 函数将由来自 Cloud Scheduler 的 Pub/Sub 消息触发,以连接到 CrowdStrike Event Streams API、使用事件并将其写入 GCS。

  1. 在 GCP 控制台中,前往 Cloud Run。
  2. 点击创建服务。
  3. 选择函数(使用内嵌编辑器创建函数)。
  4. 在配置部分中,提供以下配置详细信息:

    设置 值
    Service 名称 cs-stream-collector
    区域 选择与您的 GCS 存储桶匹配的区域(例如 us-central1)
    运行时 选择 Python 3.12 或更高版本
  5. 在触发器(可选)部分中:

    1. 点击 + 添加触发器。
    2. 选择 Cloud Pub/Sub。
    3. 在选择 Cloud Pub/Sub 主题中,选择 cs-stream-trigger。
    4. 点击保存。
  6. 在身份验证部分中:

    1. 选择需要进行身份验证。
    2. 检查 Identity and Access Management (IAM)。
  7. 向下滚动并展开容器、网络、安全性。

  8. 前往安全标签页:

    • 服务账号:选择 cs-stream-collector-sa
  9. 前往容器标签页:

    1. 点击变量和密钥。
    2. 为每个环境变量点击 + 添加变量:
    变量名称 示例值 说明
    GCS_BUCKET cs-stream-logs GCS 存储桶名称
    GCS_PREFIX crowdstrike/stream 日志文件的前缀
    STATE_KEY crowdstrike/stream/state.json 状态文件路径
    CS_BASE_URL https://api.crowdstrike.com CrowdStrike API 基础网址
    CS_CLIENT_ID your-client-id API 客户端 ID
    CS_CLIENT_SECRET your-client-secret API 客户端密钥
    CS_APP_ID secops-stream-collector 事件流的唯一应用 ID(最多 32 个字母数字字符)
    STREAM_TIMEOUT 300 以秒为单位的流读取超时时间
    MAX_RECORDS 10000 每次运行的记录数上限
  10. 在变量和密钥部分中,向下滚动到请求:

    • 请求超时:输入 600 秒(10 分钟)
  11. 前往设置标签页:

    • 在资源部分中:
      • 内存:选择 512 MiB 或更高值
      • CPU:选择 1
  12. 在修订版本伸缩部分中:

    • 实例数下限:输入 0
    • 实例数上限:输入 1(一次只能运行一个流消费者)
  13. 点击创建。

  14. 等待服务创建完成(1-2 分钟)。

  15. 创建服务后,系统会自动打开内嵌代码编辑器。

添加函数代码

  1. 在入口点字段中输入 main。
  2. 在内嵌代码编辑器中,创建两个文件:

    • main.py:

      import functions_framework
      from google.cloud import storage
      import json
      import os
      import urllib3
      from datetime import datetime, timezone
      
      # Initialize HTTP client with timeouts
      http = urllib3.PoolManager(
          timeout=urllib3.Timeout(connect=5.0, read=30.0),
          retries=False,
      )
      
      # Initialize Storage client
      storage_client = storage.Client()
      
      # Environment variables
      GCS_BUCKET = os.environ.get('GCS_BUCKET')
      GCS_PREFIX = os.environ.get('GCS_PREFIX', 'crowdstrike/stream')
      STATE_KEY = os.environ.get('STATE_KEY', 'crowdstrike/stream/state.json')
      CS_BASE_URL = os.environ.get('CS_BASE_URL', 'https://api.crowdstrike.com')
      CS_CLIENT_ID = os.environ.get('CS_CLIENT_ID')
      CS_CLIENT_SECRET = os.environ.get('CS_CLIENT_SECRET')
      CS_APP_ID = os.environ.get('CS_APP_ID', 'secops-stream-collector')
      STREAM_TIMEOUT = int(os.environ.get('STREAM_TIMEOUT', '300'))
      MAX_RECORDS = int(os.environ.get('MAX_RECORDS', '10000'))
      
      def get_oauth_token(base_url: str, client_id: str, client_secret: str) -> str:
          """Get CrowdStrike OAuth2 access token."""
          token_url = f"{base_url}/oauth2/token"
          body = f"client_id={client_id}&client_secret={client_secret}"
          response = http.request(
              'POST', token_url,
              body=body.encode('utf-8'),
              headers={'Content-Type': 'application/x-www-form-urlencoded'}
          )
          if response.status != 200:
              raise Exception(f"OAuth token request failed: {response.status}")
          data = json.loads(response.data.decode('utf-8'))
          return data['access_token']
      
      def refresh_stream_session(base_url: str, token: str, app_id: str, partition: int = 0):
          """Refresh an active stream session to keep it alive."""
          refresh_url = (
              f"{base_url}/sensors/entities/datafeed-actions/v1/{partition}"
              f"?appId={app_id}&action_name=refresh_active_stream_session"
          )
          response = http.request(
              'POST', refresh_url,
              headers={
                  'Authorization': f'Bearer {token}',
                  'Content-Type': 'application/json',
              }
          )
          if response.status == 200:
              print("Stream session refreshed successfully")
          else:
              print(f"Warning: Stream session refresh returned {response.status}")
      
      @functions_framework.cloud_event
      def main(cloud_event):
          """
          Cloud Run function triggered by Pub/Sub to fetch CrowdStrike
          Event Streams data and write to GCS.
          """
      
          if not all([GCS_BUCKET, CS_BASE_URL, CS_CLIENT_ID, CS_CLIENT_SECRET]):
              print('Error: Missing required environment variables')
              return
      
          try:
              bucket = storage_client.bucket(GCS_BUCKET)
      
              # Load state
              state = load_state(bucket, STATE_KEY)
      
              # Get OAuth token
              token = get_oauth_token(CS_BASE_URL, CS_CLIENT_ID, CS_CLIENT_SECRET)
              headers = {
                  'Authorization': f'Bearer {token}',
                  'Accept': 'application/json',
              }
      
              # Discover available data feeds
              discover_url = (
                  f"{CS_BASE_URL}/sensors/entities/datafeed/v2"
                  f"?appId={CS_APP_ID}&format=flatjson"
              )
              response = http.request('GET', discover_url, headers=headers)
      
              if response.status != 200:
                  print(f"Failed to discover data feed: {response.status}")
                  print(f"Response: {response.data.decode('utf-8')}")
                  return
      
              feed_data = json.loads(response.data.decode('utf-8'))
              resources = feed_data.get('resources', [])
      
              if not resources:
                  print("No data feed resources available")
                  return
      
              feed_url = resources[0].get('dataFeedURL', '')
              session_token = resources[0].get(
                  'sessionToken', {}
              ).get('token', '')
      
              if not feed_url or not session_token:
                  print("Missing feed URL or session token")
                  return
      
              # Build stream URL with offset if available
              stream_url = feed_url
              offset = state.get('offset')
              if offset:
                  stream_url = f"{feed_url}&offset={offset}"
      
              # Connect to Event Stream
              stream_headers = {
                  'Authorization': f'Token {session_token}',
                  'Accept': 'application/json',
                  'Connection': 'keep-alive',
              }
      
              stream_http = urllib3.PoolManager(
                  timeout=urllib3.Timeout(
                      connect=10.0, read=float(STREAM_TIMEOUT)
                  ),
                  retries=False,
              )
      
              print(f"Connecting to Event Stream...")
              resp = stream_http.request(
                  'GET', stream_url,
                  headers=stream_headers,
                  preload_content=False
              )
      
              if resp.status != 200:
                  print(f"Stream connection failed: {resp.status}")
                  return
      
              records = []
              latest_offset = offset
              now = datetime.now(timezone.utc)
      
              for line in resp.stream(4096):
                  decoded = line.decode('utf-8').strip()
                  if not decoded:
                      continue
      
                  try:
                      event = json.loads(decoded)
                      records.append(event)
      
                      event_offset = event.get(
                          'metadata', {}
                      ).get('offset')
                      if event_offset is not None:
                          latest_offset = event_offset
      
                      if len(records) >= MAX_RECORDS:
                          print(
                              f"Reached max records limit ({MAX_RECORDS})"
                          )
                          break
                  except json.JSONDecodeError:
                      continue
      
              resp.release_conn()
      
              if not records:
                  print("No new events found in stream.")
                  # Refresh stream session to keep it alive
                  refresh_stream_session(
                      CS_BASE_URL, token, CS_APP_ID
                  )
                  return
      
              # Write to GCS as NDJSON
              timestamp = now.strftime('%Y%m%d_%H%M%S')
              object_key = f"{GCS_PREFIX}/logs_{timestamp}.ndjson"
              blob = bucket.blob(object_key)
      
              ndjson = '\n'.join(
                  [json.dumps(r, ensure_ascii=False) for r in records]
              ) + '\n'
              blob.upload_from_string(
                  ndjson, content_type='application/x-ndjson'
              )
      
              print(
                  f"Wrote {len(records)} records to "
                  f"gs://{GCS_BUCKET}/{object_key}"
              )
      
              # Save state with latest offset
              new_state = {
                  'offset': latest_offset,
                  'last_run': now.isoformat()
              }
              save_state(bucket, STATE_KEY, new_state)
      
              # Refresh stream session to keep it alive
              refresh_stream_session(
                  CS_BASE_URL, token, CS_APP_ID
              )
      
              print(f"Successfully processed {len(records)} events")
      
          except Exception as e:
              print(f'Error processing logs: {str(e)}')
              raise
      
      def load_state(bucket, key):
          """Load state from GCS."""
          try:
              blob = bucket.blob(key)
              if blob.exists():
                  state_data = blob.download_as_text()
                  return json.loads(state_data)
          except Exception as e:
              print(f"Warning: Could not load state: {e}")
          return {}
      
      def save_state(bucket, key, state: dict):
          """Save state to GCS."""
          try:
              blob = bucket.blob(key)
              blob.upload_from_string(
                  json.dumps(state, indent=2),
                  content_type='application/json'
              )
              print(f"Saved state: {state}")
          except Exception as e:
              print(f"Warning: Could not save state: {e}")
      

    requirements.txt:

    ```none
    functions-framework==3.*
    google-cloud-storage==2.*
    urllib3>=2.0.0
    ```
    
  3. 点击部署以保存并部署函数。

  4. 等待部署完成(2-3 分钟)。

创建 Cloud Scheduler 作业

Cloud Scheduler 将定期向 Pub/Sub 主题发布消息,从而触发 Cloud Run 函数。

  1. 在 GCP 控制台中,前往 Cloud Scheduler。
  2. 点击创建作业。
  3. 提供以下配置详细信息:

    设置 值
    名称 cs-stream-collector-scheduled
    区域 选择与 Cloud Run 函数相同的区域
    频率 */15 * * * *(每 15 分钟)
    时区 选择时区(建议选择世界协调时间 [UTC])
    目标类型 Pub/Sub
    主题 选择cs-stream-trigger
    消息正文 {}(空 JSON 对象)
  4. 点击创建。

测试集成

  1. 在 Cloud Scheduler 控制台中,找到您的作业。
  2. 点击强制运行以手动触发作业。
  3. 等待几秒钟。
  4. 前往 Cloud Run > 服务。
  5. 点击 cs-stream-collector。
  6. 点击日志标签页。
  7. 验证函数是否已成功执行。查找:

    Connecting to Event Stream...
    Wrote X records to gs://cs-stream-logs/crowdstrike/stream/logs_YYYYMMDD_HHMMSS.ndjson
    Stream session refreshed successfully
    Successfully processed X events
    
  8. 前往 Cloud Storage > 存储桶。

  9. 点击 cs-stream-logs。

  10. 转到 crowdstrike/stream/ 文件夹。

  11. 验证是否已创建具有当前时间戳的新 .ndjson 文件。

如果您在日志中看到错误,请执行以下操作:

  • HTTP 401:检查环境变量中的 API 凭据
  • HTTP 403:验证 API 客户端是否具有事件流:读取范围
  • HTTP 429:速率限制 - 调整调度程序频率或增加 STREAM_TIMEOUT
  • 缺少环境变量:检查是否已设置所有必需的变量
  • 没有可用的数据 Feed 资源:验证 CS_APP_ID 是否唯一且不超过 32 个字母数字字符

检索 Google SecOps 服务账号

Google SecOps 使用唯一的服务账号从您的 GCS 存储桶中读取数据。您必须向此服务账号授予对您的存储桶的访问权限。

获取服务账号电子邮件地址

  1. 前往 SIEM 设置 > Feed。
  2. 点击添加新 Feed。
  3. 点击配置单个 Feed。
  4. 在 Feed 名称字段中,输入 Feed 的名称(例如 CrowdStrike Event Streams logs)。
  5. 选择 Google Cloud Storage V2 作为来源类型。
  6. 选择 CrowdStrike Falcon Streaming 作为日志类型。
  7. 点击获取服务账号。
  8. 系统会显示一个唯一的服务账号电子邮件地址,例如:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  9. 复制此电子邮件地址,以便在下一步中使用。

  10. 点击下一步。

  11. 为以下输入参数指定值:

    • 存储桶网址:输入带有前缀路径的 GCS 存储桶 URI:

      gs://cs-stream-logs/crowdstrike/stream/
      
    • 来源删除选项:根据您的偏好选择删除选项:

      • 永不:转移后永不删除任何文件(建议用于测试)。
      • 删除已转移的文件:成功转移后删除文件。
      • 删除已转移的文件和空目录:成功转移后删除文件和空目录。

    • 文件存在时间上限:包含在过去指定天数内修改过的文件(默认值为 180 天)

    • 资源命名空间:资源命名空间

    • 注入标签:要应用于此 Feed 中事件的标签

  12. 点击下一步。

  13. 在最终确定界面中查看新的 Feed 配置,然后点击提交。

向 Google SecOps 服务账号授予 IAM 权限

Google SecOps 服务账号需要您的 GCS 存储桶的 Storage Object Viewer 角色。

  1. 前往 Cloud Storage > 存储桶。
  2. 点击 cs-stream-logs。
  3. 前往权限标签页。
  4. 点击授予访问权限。
  5. 提供以下配置详细信息:
    • 添加主账号:粘贴 Google SecOps 服务账号电子邮件地址
    • 分配角色:选择 Storage Object Viewer
  6. 点击保存。

需要更多帮助?

如需详细了解 Google Security Operations Feed,请参阅 Google Security Operations Feed 文档。如需了解每种 Feed 类型的要求,请参阅按类型划分的 Feed 配置。

如需了解相关信息,请参阅 CrowdStrike Falcon Event Streams 解析器文档。

UDM 映射增量参考信息

2026 年 9 月 15 日,Google SecOps 发布了新版 CrowdStrike Falcon Event Streams 解析器,其中对 CrowdStrike Falcon Event Streams 日志字段到 UDM 字段的映射进行了重大更改。

日志字段映射增量

下表列出了 2026 年 9 月 15 日之前和之后公开的 CrowdStrike Falcon Event Streams 日志到 UDM 字段的映射增量(分别列在旧映射和新映射列中):

日志字段 旧版映射 新建映射 更改的原因
deviceId additional.fields principal.asset.asset_id 映射到更具体的 UDM 字段,而不是标签。
event.AuditKeyValues (report_name) security_result.detection_fields target.file.names 映射到更具体的 UDM 字段,而不是标签。
event.IdpPolicyRuleName security_result.detection_fields security_result.rule_name 映射到更具体的 UDM 字段,而不是标签。
event.MitreAttack.TacticID security_result.detection_fields security_result.tactic.id 映射到更具体的 UDM 字段,而不是标签。
event.MitreAttack.TechniqueID security_result.detection_fields security_result.technique.id 映射到更具体的 UDM 字段,而不是标签。
event.NetworkAccesse.RemoteAddress principal.ip target.ip 已更新,因为 event.NetworkAccesse.RemoteAddress 表示目标设备地址。
event.TacticId security_result.detection_fields security_result.tactic.id 映射到更具体的 UDM 字段,而不是标签。
event.TechniqueId security_result.detection_fields security_result.technique.id 映射到更具体的 UDM 字段,而不是标签。
event.UserId principal.user.userid principal.user.email_addresses,
principal.user.userid
如果 event.UserId 是电子邮件地址,则映射到 principal.user.email_addresses;否则,映射到 principal.user.userid。
remoteAddress principal.ip target.ip 已更新,因为 remoteAddress 表示目标设备地址。
ruleName security_result.detection_fields security_result.rule_name 映射到更具体的 UDM 字段,而不是标签。
serviceName metadata.description,
target.application
target.application 移除了冗余的映射。

更新日志

查看相应解析器的更新日志

需要更多帮助?获得社区成员和 Google SecOps 专业人士的解答。