收集 BeyondTrust Endpoint Privilege Management (EPM) 日志
本文档介绍了如何使用 Cloud Storage 将 BeyondTrust Endpoint Privilege Management (EPM) 日志注入到 Google Security Operations。该解析器专注于将 BeyondTrust Endpoint 的原始 JSON 日志数据转换为符合 Google SecOps UDM 的结构化格式。它首先初始化各个字段的默认值,然后解析 JSON 载荷,随后将原始日志中的特定字段映射到 event.idm.read_only_udm 对象中的相应 UDM 字段。
准备工作
请确保满足以下前提条件:
- Google SecOps 实例
- 启用了 Cloud Storage API 的 Google Cloud 项目
- 创建和管理 Cloud Storage 存储分区的权限
- 管理 Cloud Storage 存储分区的 Identity and Access Management (IAM) 政策的权限
- 创建 Cloud Run 函数服务、Pub/Sub 主题和 Cloud Scheduler 作业的权限
- 对 BeyondTrust Endpoint Privilege Management 租户或 API 的特权访问权限
创建 Cloud Storage 存储桶
- 前往 Google Cloud 控制台。
- 选择您的项目或创建新项目。
- 在导航菜单中,依次前往 Cloud Storage > 存储分区。
- 点击创建存储分区。
提供以下配置详细信息:
设置 值 为存储桶命名 输入一个全局唯一的名称(例如 beyondtrust-epm-logs)位置类型 根据您的需求进行选择(区域级、双区域、多区域) 位置 选择营业地点(例如 us-central1)存储类别 标准(建议用于经常访问的日志) 访问权限控制 均匀(推荐) 保护工具 可选:启用对象版本控制或保留政策 点击创建。
收集 BeyondTrust EPM API 凭据
- 以管理员身份登录 BeyondTrust Privilege Management Web 控制台。
- 依次前往配置 > 设置 > API 设置。
- 点击 Create an API Account。
- 提供以下配置详细信息:
- 名称:输入
Google SecOps Collector。 - API 访问权限:启用报告(只读)。此收集器使用的
/management-api/v3/Events/FromStartDate端点属于报告权限类别。如果您还打算收集 Web 控制台活动审核日志,请同时启用审核(只读)。
- 名称:输入
- 复制并保存客户端 ID 和客户端密钥。
复制 API 设置页面顶部显示的 API 基准网址。通常为
https://<your-tenant>-services.pm.beyondtrustcloud.com。您将使用此值作为BPT_API_URL。
为 Cloud Run 函数创建服务账号
Cloud Run 函数需要一个服务账号,该账号具有写入 Cloud Storage 存储桶的权限,并且可以由 Pub/Sub 调用。
创建服务账号
- 在 Google Cloud 控制台中,依次前往 IAM 和管理 > 服务账号。
- 点击创建服务账号。
- 提供以下配置详细信息:
- 服务账号名称:输入
beyondtrust-epm-collector-sa。 - 服务账号说明:输入
Service account for Cloud Run function to collect BeyondTrust EPM logs。
- 服务账号名称:输入
- 点击创建并继续。
- 在向此服务账号授予对项目的访问权限部分中,添加以下角色:
- 点击选择角色。
- 搜索并选择 Storage Object Admin。
- 点击 + 添加其他角色。
- 搜索并选择 Cloud Run Invoker。
- 点击 + 添加其他角色。
- 搜索并选择 Cloud Functions Invoker。
- 点击继续。
- 点击完成。
必须拥有这些角色,才能:
- Storage Object Admin:将日志写入 Cloud Storage 存储桶并管理状态文件
- Cloud Run Invoker:允许 Pub/Sub 调用该函数
- Cloud Functions Invoker:允许调用函数
授予对 Cloud Storage 存储桶的 IAM 权限
向服务账号授予对 Cloud Storage 存储桶的写入权限:
- 前往 Cloud Storage > 存储分区。
- 点击您的存储桶名称。
- 前往权限标签页。
- 点击授予访问权限。
- 提供以下配置详细信息:
- 添加主账号:输入服务账号电子邮件地址。例如
beyondtrust-epm-collector-sa@PROJECT_ID.iam.gserviceaccount.com。 - 分配角色:选择 Storage Object Admin。
- 添加主账号:输入服务账号电子邮件地址。例如
- 点击保存。
创建 Pub/Sub 主题
创建一个 Pub/Sub 主题,Cloud Scheduler 将向该主题发布消息,而 Cloud Run 函数将订阅该主题。
- 在 Google Cloud 控制台中,前往 Pub/Sub > 主题。
- 点击创建主题。
- 提供以下配置详细信息:
- 主题 ID:输入
beyondtrust-epm-trigger。 - 将其他设置保留为默认值。
- 主题 ID:输入
- 点击创建。
创建 Cloud Run 函数以收集日志
Cloud Run 函数由来自 Cloud Scheduler 的 Pub/Sub 消息触发,用于从 BeyondTrust EPM API 中提取日志并将其写入 Cloud Storage。
- 在 Google Cloud 控制台中,前往 Cloud Run。
- 点击创建服务。
- 选择函数。使用内嵌编辑器创建函数。
在配置部分中,提供以下配置详细信息:
设置 值 Service 名称 beyondtrust-epm-collector区域 选择与您的 Cloud Storage 存储桶匹配的区域(例如 us-central1)运行时 选择 Python 3.12 或更高版本 在触发器(可选)部分中:
- 点击 + 添加触发器。
- 选择 Cloud Pub/Sub。
- 在选择 Cloud Pub/Sub 主题部分,选择主题
beyondtrust-epm-trigger。 - 点击保存。
在身份验证部分中:
- 选择需要进行身份验证。
- 检查 Identity and Access Management (IAM)。
前往并展开容器、网络、安全性。
前往安全性标签页:
- 服务账号:选择服务账号
beyondtrust-epm-collector-sa。
- 服务账号:选择服务账号
前往容器标签页:
- 点击变量和密钥。
- 为每个环境变量点击+ 添加变量:
变量名称 示例值 GCS_BUCKETbeyondtrust-epm-logsGCS_PREFIXbeyondtrust-epm/STATE_KEYbeyondtrust-epm-state.jsonBPT_API_URLhttps://yourtenant-services.pm.beyondtrustcloud.comCLIENT_IDyour-client-idCLIENT_SECRETyour-client-secretRECORD_SIZE1000MAX_BATCHES50LOOKBACK_HOURS24RECORD_SIZE:每个请求的记录数。/Events/FromStartDate接受 1 到 1000 之间的值;该函数会限制更高的值。MAX_BATCHES:每次运行的请求数量的安全限制。如果运行遇到此标记,光标仍会前进到写入的内容,因此下一次运行会从该位置继续,而不是重复工作。LOOKBACK_HOURS:第一次运行的追溯时间。BeyondTrust 会将被动事件(代码 106、107、603、706)保留 30 天,并将所有其他事件保留 90 天,因此更长的回溯期不会返回比这些时间更早的任何内容。LOOKBACK_HOURS:在没有任何游标的情况下,首次运行要回溯多远。
在变量和 Secret 标签页中,前往请求:
- 请求超时:输入
600秒(10 分钟)。
- 请求超时:输入
前往容器中的设置标签页:
- 在资源部分中:
- 内存:选择 512 MiB 或更高值。
- CPU:选择 1。
- 点击完成。
- 在资源部分中:
滚动到执行环境:
- 选择默认(推荐)。
在修订版本伸缩部分中:
- 实例数下限:输入
0。 - 实例数上限:输入
100(或根据预期负载进行调整)。
- 实例数下限:输入
点击创建。
等待服务创建完成(1-2 分钟)。
创建服务后,系统会自动打开内嵌代码编辑器。
添加函数代码
- 在函数入口点中输入 main
在内嵌代码编辑器中,创建两个文件:
- 第一个文件 - main.py::
import hashlib import json import os import re import time import urllib3 import uuid from datetime import datetime, timedelta, timezone from urllib.parse import urlencode import functions_framework from google.cloud import storage from google.cloud.exceptions import NotFound # Initialize HTTP client with timeouts http = urllib3.PoolManager( timeout=urllib3.Timeout(connect=5.0, read=60.0), retries=False, ) # Initialize Storage client storage_client = storage.Client() TOKEN_PATH = '/oauth/connect/token' # Events/FromStartDate is the endpoint BeyondTrust documents for bulk extraction: # "A new API is exposed to extract the events in bulk." It takes only StartDate and # RecordSize. Events/search is not usable here: it requires an OperatingSystem value, # it is a single string with no documented "all" value, so it can only ever return one # operating system per call. EVENTS_PATH = '/management-api/v3/Events/FromStartDate' OAUTH_SCOPE = 'urn:management:api' # RecordSize accepts 1 to 1000. MAX_RECORD_SIZE = 1000 class FetchError(Exception): """Raised when the BeyondTrust API call fails. The cursor must never advance on a failed fetch, otherwise the events in the failed window are skipped permanently. """ def rfc3339(dt: datetime) -> str: """Render a datetime the way StartDate accepts it: milliseconds and a literal Z.""" return dt.astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' def parse_iso(stamp: str) -> datetime: """Parse any ISO 8601 timestamp shape the API emits. event.ingested comes back with a +00:00 offset and up to seven fractional digits, e.g. 2026-08-03T13:29:55.1109163+00:00. fromisoformat's tolerance for long fractions varies across Python versions, so the fraction is trimmed to microseconds first. """ text = stamp.strip() if text.endswith('Z'): text = text[:-1] + '+00:00' text = re.sub(r'\.(\d{1,6})\d*', r'.\1', text, count=1) parsed = datetime.fromisoformat(text) if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) return parsed def canonical(stamp: str) -> str: """Re-render an API timestamp into the one form StartDate accepts. The API returns event.ingested as +00:00-offset ISO 8601 but rejects that same shape as a StartDate value with 400 "Invalid Start date format": it accepts only Z-suffixed values. Every timestamp that came from the API must pass through here before being sent back or persisted. """ return rfc3339(parse_iso(stamp)) def event_id(evt: dict) -> str: """Return the event's identity for deduplication. Falls back to a content hash when event.id is absent, so an id-less event still deduplicates instead of being re-ingested on every boundary re-read. """ explicit = str((evt.get('event') or {}).get('id') or '') if explicit: return explicit digest = hashlib.sha256( json.dumps(evt, sort_keys=True, ensure_ascii=False).encode('utf-8') ).hexdigest() return f'sha256:{digest}' def event_ingested(evt: dict) -> str: """Return the Elastic ingestion timestamp, which is what StartDate filters on.""" return str((evt.get('event') or {}).get('ingested') or '') def next_millisecond(stamp: str) -> str: """Return the canonical timestamp one millisecond later. Used only when a full batch fits inside a single ingestion millisecond. Without this the cursor cannot move and the collector stalls on that timestamp forever. """ return rfc3339(parse_iso(stamp) + timedelta(milliseconds=1)) @functions_framework.cloud_event def main(cloud_event): """Fetch BeyondTrust EPM events and write them to Cloud Storage as NDJSON. Args: cloud_event: CloudEvent object containing the Pub/Sub message. """ bucket_name = os.environ.get('GCS_BUCKET') prefix = os.environ.get('GCS_PREFIX', 'beyondtrust-epm/') state_key = os.environ.get('STATE_KEY', 'beyondtrust-epm-state.json') api_url = (os.environ.get('BPT_API_URL') or '').rstrip('/') client_id = os.environ.get('CLIENT_ID') client_secret = os.environ.get('CLIENT_SECRET') # Clamp both ends: the API rejects RecordSize outside 1 to 1000, and a # misconfigured 0 would otherwise crash-loop on a 400 every run. record_size = min(max(int(os.environ.get('RECORD_SIZE', '1000')), 1), MAX_RECORD_SIZE) max_batches = int(os.environ.get('MAX_BATCHES', '50')) lookback_hours = int(os.environ.get('LOOKBACK_HOURS', '24')) if not all([bucket_name, api_url, client_id, client_secret]): raise RuntimeError( 'Missing required environment variables: ' 'GCS_BUCKET, BPT_API_URL, CLIENT_ID, CLIENT_SECRET' ) bucket = storage_client.bucket(bucket_name) state = load_state(bucket, state_key) # StartDate is a rising cursor on the Elastic ingestion timestamp, not a closed # window. There is no EndDate on this endpoint. if state.get('last_ingested'): # canonical() also repairs state written by the previous script revision, # which persisted the API's raw +00:00 form that StartDate rejects. try: start_date = canonical(state['last_ingested']) except (ValueError, TypeError, AttributeError) as e: raise RuntimeError( f'Unparseable last_ingested in gs://{bucket_name}/{state_key}: ' f'{state["last_ingested"]!r}. Fix or delete that object; deleting ' f'restarts collection from the lookback window.' ) from e else: start_date = rfc3339(datetime.now(timezone.utc) - timedelta(hours=lookback_hours)) seen_ids = set(state.get('seen_ids', [])) print(f'Collecting events ingested from {start_date}') token = get_oauth_token(api_url, client_id, client_secret) fresh, cursor, drained = fetch_events( api_url, token, start_date, record_size, max_batches, seen_ids ) if not fresh: print('No new events. Cursor left unchanged.') return # Everything that can fail is computed before the upload: a crash between the # upload and save_state replays the batch on the next run, so the window where # side effects exist without recorded state must stay minimal. The retained ids # share the cursor millisecond, because those are exactly the ones the # inclusive StartDate will return again. retained = { event_id(e) for e in fresh if event_ingested(e) and canonical(event_ingested(e)) == cursor } if cursor == start_date: # The cursor millisecond did not advance, so ids retained by earlier runs # are still on the boundary; dropping them would re-ingest their events. retained |= seen_ids timestamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') # The random suffix keeps concurrent executions (Pub/Sub delivers at least # once) from overwriting each other's object within the same second. filename = ( f'{prefix}beyondtrust-epm-events-{timestamp}-' f'{uuid.uuid4().hex[:8]}.ndjson' ) ndjson = '\n'.join(json.dumps(e, ensure_ascii=False) for e in fresh) + '\n' bucket.blob(filename).upload_from_string( ndjson, content_type='application/x-ndjson' ) print(f'Wrote {len(fresh)} events to gs://{bucket_name}/{filename}') save_state(bucket, state_key, { 'last_ingested': cursor, 'seen_ids': sorted(retained), 'updated_at': rfc3339(datetime.now(timezone.utc)), }) if not drained: print( f'Stopped after {max_batches} batches with more events available. ' 'The cursor advanced, so the next run continues from here.' ) def fetch_events(api_url, token, start_date, record_size, max_batches, seen_ids): """Read forward from start_date until the API returns a short batch. The response envelope has no page count and no next-page token, so a batch shorter than record_size is the only documented end-of-data signal. StartDate is inclusive and the cursor lands on the newest event's timestamp, so every batch re-returns the events sharing it. Deduplication by event id therefore has to happen as batches arrive, not only between runs. Returns: Tuple of (new events, cursor to persist, whether the stream was drained). Raises: FetchError: on any API or transport failure, so the caller cannot mistake a failed fetch for an empty result and advance the cursor. """ headers = { 'Authorization': f'Bearer {token}', 'Accept': 'application/json', } all_events = [] seen = set(seen_ids) cursor = start_date for batch in range(1, max_batches + 1): query = urlencode({'StartDate': cursor, 'RecordSize': record_size}) body = request_with_retry(f'{api_url}{EVENTS_PATH}?{query}', headers) events = body.get('events') or [] new = [e for e in events if event_id(e) not in seen] seen.update(event_id(e) for e in events if event_id(e)) all_events.extend(new) print(f'Batch {batch}: {len(events)} events, {len(new)} new') # Termination is judged on what the API returned, not on what survived # deduplication: a batch can be full and still be entirely duplicates. # The newest stamp in the batch, not events[-1]: ordering is a client-side # convention, not a documented guarantee, and a misordered tail would # regress the cursor and re-ingest events already written. stamps = [canonical(s) for s in (event_ingested(e) for e in events) if s] if len(events) < record_size: # Short batch: the stream is drained. if events and not stamps: print( 'Warning: no event in the final batch carries event.ingested; ' 'cursor left unchanged.' ) if stamps: cursor = max(max(stamps), cursor) return all_events, cursor, True if not stamps: raise FetchError( 'No event in a full batch carries event.ingested, ' 'so the cursor cannot advance' ) batch_max = max(stamps) if batch_max <= cursor: # A full batch fits inside one ingestion millisecond. Stepping past it is the only # way to make progress; holding the cursor here stalls collection forever. # Events beyond record_size at that exact millisecond are unreachable, which # needs more than 1000 events in one millisecond. print( f'Warning: a full batch shares ingestion timestamp {batch_max}; ' 'stepping past it. Events beyond RecordSize at that timestamp are skipped.' ) cursor = next_millisecond(cursor) else: cursor = batch_max return all_events, cursor, False def request_with_retry(url, headers, attempts=4): """GET with backoff on 429 and 5xx. The documented rate limit is 1000 requests per 100 seconds. """ backoff = 1.0 for attempt in range(1, attempts + 1): try: response = http.request('GET', url, headers=headers) except Exception as e: raise FetchError(f'Request to {url} failed: {e}') from e if response.status in (429, 500, 502, 503, 504) and attempt < attempts: retry_after = response.headers.get('Retry-After') try: delay = int(retry_after) if retry_after else backoff except (TypeError, ValueError): delay = backoff # Retry-After is server-controlled input: bound it so a bogus value # cannot sleep past the function timeout or crash time.sleep. delay = min(max(delay, 1.0), 60.0) print(f'HTTP {response.status}. Retrying in {delay}s...') time.sleep(delay) backoff = min(backoff * 2, 30.0) continue if response.status != 200: raise FetchError( f'Request failed: {response.status} {response.data.decode("utf-8")}' ) text = response.data.decode('utf-8') # BeyondTrust's sample folds "Owner" into "owner" before parsing, but that # workaround exists for PowerShell's case-insensitive ConvertFrom-Json. # Python parses case-sensitively, and the SecOps parser maps file.Owner.* # and file.owner to different UDM fields, so both keys must survive. try: return json.loads(text) except json.JSONDecodeError as e: raise FetchError(f'Malformed JSON response: {e}') from e raise FetchError(f'Giving up on {url} after {attempts} attempts') def get_oauth_token(api_url, client_id, client_secret): """Get an access token using the OAuth client credentials flow. The token is valid for one hour. """ body = urlencode({ 'grant_type': 'client_credentials', 'client_id': client_id, 'client_secret': client_secret, 'scope': OAUTH_SCOPE, }) response = http.request( 'POST', f'{api_url}{TOKEN_PATH}', body=body, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) if response.status != 200: raise FetchError( f'Token request failed: {response.status} ' f'{response.data.decode("utf-8")}' ) return json.loads(response.data.decode('utf-8'))['access_token'] def load_state(bucket, key): """Read the collector state from Cloud Storage. Only a missing object is a cold start. Any other error is raised: swallowing it would reset collection to the lookback window and re-ingest that period. """ blob = bucket.blob(key) try: return json.loads(blob.download_as_text()) except NotFound: print('No state file found. Starting from the lookback window.') return {} def save_state(bucket, key, state): """Write the collector state to Cloud Storage. Failures are raised, not logged. A run that cannot record its cursor must fail, otherwise the next run repeats the same window. """ bucket.blob(key).upload_from_string( json.dumps(state, indent=2), content_type='application/json' ) print(f"Saved state: last_ingested={state.get('last_ingested')}")- 第二个文件:requirements.txt::
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0点击部署以保存并部署该函数。
等待部署完成(2-3 分钟)。
创建 Cloud Scheduler 作业
Cloud Scheduler 会定期向 Pub/Sub 主题发布消息,从而触发 Cloud Run 函数。
- 在 Google Cloud 控制台中,前往 Cloud Scheduler。
- 点击创建作业。
提供以下配置详细信息:
设置 值 名称 beyondtrust-epm-collector-hourly区域 选择与 Cloud Run 函数相同的区域 频率 0 * * * *(每小时一次,整点时)时区 选择时区(建议选择 UTC) 目标类型 Pub/Sub 主题 选择主题 beyondtrust-epm-trigger消息正文 {}(空 JSON 对象)点击创建。
时间表频率选项
根据日志量和延迟时间要求选择频次:
| 频率 | Cron 表达式 | 使用场景 |
|---|---|---|
| 每隔 5 分钟 | */5 * * * * |
大批量、低延迟 |
| 每隔 15 分钟 | */15 * * * * |
搜索量中等 |
| 每小时 | 0 * * * * |
标准(推荐) |
| 每 6 小时 | 0 */6 * * * |
低成交量、批处理 |
| 每天 | 0 0 * * * |
历史数据收集 |
测试调度器作业
- 在 Cloud Scheduler 控制台中,找到您的作业。
- 点击强制运行以手动触发。
- 等待几秒钟,然后前往 Cloud Run > 服务 > beyondtrust-epm-collector > 日志。
- 验证函数是否已成功执行。
- 检查 Cloud Storage 存储桶,确认日志是否已写入。
检索 Google SecOps 服务账号
Google SecOps 使用唯一的服务账号从您的 Cloud Storage 存储桶中读取数据。您必须向此服务账号授予对您的存储桶的访问权限。
获取服务账号电子邮件地址
- 依次前往 SIEM 设置 > Feed。
- 点击添加新 Feed。
- 点击配置单个 Feed。
- 在 Feed 名称字段中,输入 Feed 的名称(例如
BeyondTrust EPM logs)。 - 选择 Google Cloud Storage V2 作为来源类型。
- 选择 BeyondTrust Endpoint Privilege Management 作为日志类型。
点击获取服务账号。系统会显示一个唯一的服务账号电子邮件地址,例如:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com复制此电子邮件地址,以便在下一步中使用。
向 Google SecOps 服务账号授予 IAM 权限
Google SecOps 服务账号需要您的 Cloud Storage 存储桶具备两个角色:用于读取日志对象的 Storage Object Viewer,以及用于读取存储桶元数据的存储桶级角色。
- 前往 Cloud Storage > 存储分区。
- 点击您的存储桶名称。
- 前往权限标签页。
- 点击授予访问权限。
- 提供以下配置详细信息:
- 添加主账号:粘贴 Google SecOps 服务账号电子邮件地址。
- 分配角色:选择以下两个角色:
- Storage Object Viewer:读取日志对象。
- Storage Legacy Bucket Reader:读取存储桶元数据。如果您选择了删除已转移的文件和空目录删除选项,请改为选择存储空间旧版存储分区写入者,该角色也会授予删除权限。
点击保存。
在 Google SecOps 中配置 Feed 以注入 BeyondTrust EPM 日志
- 依次前往 SIEM 设置 > Feed。
- 点击添加新 Feed。
- 点击配置单个 Feed。
- 在 Feed 名称字段中,输入 Feed 的名称(例如
BeyondTrust EPM logs)。 - 选择 Google Cloud Storage V2 作为来源类型。
- 选择 BeyondTrust Endpoint Privilege Management 作为日志类型。
- 点击下一步。
为以下输入参数指定值:
存储桶网址:输入带有前缀路径的 Cloud Storage 存储桶 URI:
gs://beyondtrust-epm-logs/beyondtrust-epm/替换:
beyondtrust-epm-logs:您的 Cloud Storage 存储桶名称。beyondtrust-epm/:存储日志的可选前缀/文件夹路径(留空表示根目录)。
示例:
- 根存储桶:
gs://beyondtrust-epm-logs/ - 带前缀:
gs://beyondtrust-epm-logs/beyondtrust-epm/
- 根存储桶:
来源删除选项:根据您的偏好选择删除选项:
- 永不删除文件:永不从源中删除文件。建议用于测试目的。
删除已转移的文件和空目录:在成功提取完成后,从来源中删除文件和空目录。
文件存在时间上限:包含在过去指定天数内修改过的文件。默认值为 180 天。
资产命名空间:资产命名空间。
注入标签:要应用于此 Feed 中事件的标签。
点击下一步。
在最终确定界面中查看新的 Feed 配置,然后点击提交。
UDM 映射表
| 日志字段 | UDM 映射 | 逻辑 |
|---|---|---|
DomainIdentifier_label |
additional.fields |
已合并 |
WatsonEventType_label |
additional.fields |
已合并 |
action_label |
additional.fields |
已合并 |
activity_id_label |
additional.fields |
已合并 |
app_id_label |
additional.fields |
已合并 |
app_insights_instrumentation_key_label |
additional.fields |
已合并 |
asimov_instrumentation_key_label |
additional.fields |
已合并 |
authorization_request_control_authorization_label |
additional.fields |
已合并 |
bundle_name_label |
additional.fields |
已合并 |
changedBy_label |
additional.fields |
已合并 |
code_signature_subject_name_label |
additional.fields |
已合并 |
collector_api_key_label |
additional.fields |
已合并 |
command_label |
additional.fields |
已合并 |
configuration_application_group_description_label |
additional.fields |
已合并 |
configuration_application_group_identifier_label |
additional.fields |
已合并 |
configuration_application_group_name_label |
additional.fields |
已合并 |
configuration_application_identifier_label |
additional.fields |
已合并 |
configuration_application_type_label |
additional.fields |
已合并 |
configuration_identifier_label |
additional.fields |
已合并 |
configuration_message_identifier_label |
additional.fields |
已合并 |
configuration_message_name_label |
additional.fields |
已合并 |
configuration_message_type_label |
additional.fields |
已合并 |
configuration_name_label |
additional.fields |
已合并 |
configuration_revision_number_label |
additional.fields |
已合并 |
configuration_rule_identifier_label |
additional.fields |
已合并 |
configuration_rule_on_demand_label |
additional.fields |
已合并 |
configuration_rule_script_outcome_rule_affected_label |
additional.fields |
已合并 |
configuration_token_identifier_label |
additional.fields |
已合并 |
configuration_token_name_label |
additional.fields |
已合并 |
configuration_workstyle_description_label |
additional.fields |
已合并 |
configuration_workstyle_identifier_label |
additional.fields |
已合并 |
configuration_workstyle_name_label |
additional.fields |
已合并 |
content_length_label |
additional.fields |
已合并 |
content_type_label |
additional.fields |
已合并 |
domainNetBIOSName_label |
additional.fields |
已合并 |
domain_label |
additional.fields |
已合并 |
entity_label |
additional.fields |
已合并 |
entity_name_label |
additional.fields |
已合并 |
event_action_label |
additional.fields |
已合并 |
exitstatus_label |
additional.fields |
已合并 |
file_hash_md5_label |
additional.fields |
已合并 |
file_hash_sha1_label |
additional.fields |
已合并 |
file_hash_sha256_label |
additional.fields |
已合并 |
file_version_label |
additional.fields |
已合并 |
gid_label |
additional.fields |
已合并 |
group_data_id_label |
additional.fields |
已合并 |
group_id_label |
additional.fields |
已合并 |
group_label |
additional.fields |
已合并 |
handle_label |
additional.fields |
已合并 |
host_name_label |
additional.fields |
已合并 |
host_uptime_labels |
additional.fields |
已合并 |
http_host_label |
additional.fields |
已合并 |
id_label |
additional.fields |
已合并 |
iolog_label |
additional.fields |
已合并 |
is_opted_in_label |
additional.fields |
已合并 |
linenum_label |
additional.fields |
已合并 |
local_identifier_label |
additional.fields |
已合并 |
locale_label |
additional.fields |
已合并 |
master_utcoffset_label |
additional.fields |
已合并 |
masterlocale_label |
additional.fields |
已合并 |
owner_identifier_label |
additional.fields |
已合并 |
parent_entity_id_label |
additional.fields |
已合并 |
parent_process_exec_label |
additional.fields |
已合并 |
parent_process_label |
additional.fields |
已合并 |
pbmasterdnodename_label |
additional.fields |
已合并 |
pipeName_label |
additional.fields |
已合并 |
process_entity_id_label |
additional.fields |
已合并 |
process_hash_label |
additional.fields |
已合并 |
process_name_label |
additional.fields |
已合并 |
process_parent_name_label |
additional.fields |
已合并 |
process_start_time_label |
additional.fields |
已合并 |
processexe_label |
additional.fields |
已合并 |
product_label |
additional.fields |
已合并 |
product_type_label |
additional.fields |
已合并 |
product_version_label |
additional.fields |
已合并 |
requestuser_label |
additional.fields |
已合并 |
runargv_label |
additional.fields |
已合并 |
runcwd_label |
additional.fields |
已合并 |
runeffectivegroup_label |
additional.fields |
已合并 |
runeffectiveuser_label |
additional.fields |
已合并 |
runhost_label |
additional.fields |
已合并 |
schema_version_label |
additional.fields |
已合并 |
sku_name_label |
additional.fields |
已合并 |
telemetry_level_label |
additional.fields |
已合并 |
tenant_id_label |
additional.fields |
已合并 |
type_label |
additional.fields |
已合并 |
uid_label |
additional.fields |
已合并 |
user_id_label |
additional.fields |
已合并 |
user_name_label |
additional.fields |
已合并 |
vs_exe_version_label |
additional.fields |
已合并 |
inter_host |
intermediary.hostname |
直接映射 |
Processes.description |
metadata.description |
直接映射 |
details |
metadata.description |
直接映射 |
event_data.reason |
metadata.description |
直接映射 |
file.pe.description |
metadata.description |
直接映射 |
created |
metadata.event_timestamp |
解析为 ISO8601 |
datetime |
metadata.event_timestamp |
解析为 MMM dd HH:mm:ss |
has_principal |
metadata.event_type |
已映射:true → STATUS_UPDATE |
has_user |
metadata.event_type |
已映射:true → USER_UNCATEGORIZED |
parent_working_directory_label |
metadata.ingestion_labels |
已合并 |
working_directory_label |
metadata.ingestion_labels |
已合并 |
auditType |
metadata.product_event_type |
直接映射 |
event_datas.ActionId |
metadata.product_log_id |
直接映射 |
labels.related_item_id |
metadata.product_log_id |
直接映射 |
uniqueid |
metadata.product_log_id |
直接映射 |
masterdversion |
metadata.product_version |
直接映射 |
headers.http_version |
network.application_protocol_version |
直接映射 |
headers.request_method |
network.http.method |
直接映射 |
host.os.platform |
principal.administrative_domain |
直接映射 |
Processes.process |
principal.application |
直接映射 |
agent_ephemeral_id_label |
principal.asset.attribute.labels |
已合并 |
agent_id_label |
principal.asset.attribute.labels |
已合并 |
agent_version_label |
principal.asset.attribute.labels |
已合并 |
ecs_version_label |
principal.asset.attribute.labels |
已合并 |
_hardware |
principal.asset.hardware |
已合并 |
host.hostname |
principal.asset.hostname |
直接映射 |
submithost |
principal.asset.hostname |
直接映射 |
ip_address |
principal.asset.ip |
已合并 |
masterhostip |
principal.asset.ip |
已合并 |
submithostip |
principal.asset.ip |
已合并 |
file.path |
principal.file.full_path |
直接映射 |
lineinfile |
principal.file.full_path |
直接映射 |
host.hostname |
principal.hostname |
直接映射 |
submithost |
principal.hostname |
直接映射 |
ip_address |
principal.ip |
已合并 |
masterhostip |
principal.ip |
已合并 |
submithostip |
principal.ip |
已合并 |
mac |
principal.mac |
已合并 |
host.os.name |
principal.platform_version |
直接映射 |
host.os.version |
principal.platform_version |
直接映射 |
process.command_line |
principal.process.command_line |
直接映射 |
runcommand |
principal.process.command_line |
直接映射 |
process.executable |
principal.process.file.full_path |
直接映射 |
cmd |
principal.process.parent_process.command_line |
直接映射 |
Processes.process_path |
principal.process.parent_process.file.full_path |
直接映射 |
process.parent.executable |
principal.process.parent_process.file.full_path |
直接映射 |
Processes.parent_process_id |
principal.process.parent_process.pid |
直接映射 |
Processes.process_id |
principal.process.pid |
直接映射 |
logpid |
principal.process.pid |
直接映射 |
file.Owner.DomainName |
principal.user.company_name |
直接映射 |
file.Owner.Name |
principal.user.user_display_name |
直接映射 |
runuser |
principal.user.user_display_name |
直接映射 |
userName |
principal.user.user_display_name |
直接映射 |
Processes.user |
principal.user.userid |
直接映射 |
userId |
principal.user.userid |
直接映射 |
userid |
principal.user.userid |
直接映射 |
Processes.user_id |
principal.user.windows_sid |
直接映射 |
EPMWinMac.Configuration.Rule.Action |
security_result.action |
已合并 |
security_result_action |
security_result.action |
已合并 |
event_data.outcome |
security_result.category_details |
已合并 |
host.os.version |
security_result.category_details |
已合并 |
EPMWinMac.Configuration.Application.Description |
security_result.description |
直接映射 |
EPMWinMac.Configuration.Message.Description |
security_result.description |
直接映射 |
host.os.type |
src.administrative_domain |
直接映射 |
file.name |
src.file.names |
已合并 |
host.os.full |
src.platform_version |
直接映射 |
host.os.family |
target.administrative_domain |
直接映射 |
Processes.dest |
target.asset.hostname |
直接映射 |
file.extension |
target.file.mime_type |
直接映射 |
Processes.dest |
target.hostname |
直接映射 |
host.domain |
target.hostname |
直接映射 |
file_DriveType_label |
target.resource.attribute.labels |
已合并 |
file_drive_letter_label |
target.resource.attribute.labels |
已合并 |
owner_label |
target.resource.attribute.labels |
已合并 |
| 不适用 | metadata.event_type |
常量:USER_UNCATEGORIZED |
| 不适用 | metadata.product_name |
常量:Beyondtrust Privilege Management |
| 不适用 | metadata.vendor_name |
常量:Beyondtrust Privilege Management |
| 不适用 | network.application_protocol |
常量:HTTP |
| 不适用 | principal.platform |
常量:MAC |