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 functions サービス、Pub/Sub トピック、Cloud Scheduler ジョブを作成する権限
- BeyondTrust Endpoint Privilege Management テナントまたは API への特権アクセス
Cloud Storage バケットを作成する
- Google Cloud コンソールに移動します。
- プロジェクトを選択するか、新しいプロジェクトを作成します。
- ナビゲーション メニューで、[Cloud Storage > バケット] に移動します。
- [バケットを作成] をクリックします。
次の構成情報を提供してください。
設定 値 バケットに名前を付ける グローバルに一意の名前( beyondtrust-epm-logsなど)を入力します。ロケーション タイプ ニーズに応じて選択します(リージョン、デュアルリージョン、マルチリージョン)。 ロケーション 場所を選択します(例: us-central1)。ストレージ クラス Standard(アクセス頻度の高いログにおすすめ) アクセス制御 均一(推奨) 保護ツール 省略可: オブジェクトのバージョニングまたは保持ポリシーを有効にする [作成] をクリックします。
BeyondTrust EPM API 認証情報を収集する
- 管理者として BeyondTrust Privilege Management ウェブ コンソールにログインします。
- [構成] > [設定] > [API 設定] に移動します。
- [Create an API Account] をクリックします。
- 次の構成情報を指定します。
- 名前: 「
Google SecOps Collector」と入力します。 - API アクセス: [レポート(読み取り専用)] を有効にします。このコレクタで使用される
/management-api/v3/Events/FromStartDateエンドポイントは、レポート権限カテゴリに属します。ウェブ コンソール アクティビティの監査も収集する場合は、[監査(読み取り専用)] も有効にします。
- 名前: 「
- クライアント ID とクライアント シークレットをコピーして保存します。
[API 設定] ページの上部に表示されている API ベース URL をコピーします。通常は
https://<your-tenant>-services.pm.beyondtrustcloud.comです。これはBPT_API_URLとして使用します。
Cloud Run functions のサービス アカウントを作成する
Cloud Run functions には、Cloud Storage バケットへの書き込み権限と Pub/Sub からの呼び出し権限を持つサービス アカウントが必要です。
サービス アカウントを作成する
- Google Cloud コンソールで、[IAM と管理] > [サービス アカウント] に移動します。
- [サービス アカウントを作成] をクリックします。
- 次の構成情報を指定します。
- サービス アカウント名: 「
beyondtrust-epm-collector-sa」と入力します。 - サービス アカウントの説明: 「
Service account for Cloud Run function to collect BeyondTrust EPM logs」と入力します。
- サービス アカウント名: 「
- [作成して続行] をクリックします。
- [このサービス アカウントにプロジェクトへのアクセスを許可する] セクションで、次のロールを追加します。
- [ロールを選択] をクリックします。
- [ストレージ オブジェクト管理者] を検索して選択します。
- [+ 別のロールを追加] をクリックします。
- [Cloud Run 起動元] を検索して選択します。
- [+ 別のロールを追加] をクリックします。
- [Cloud Functions 起動元] を検索して選択します。
- [続行] をクリックします。
- [完了] をクリックします。
これらのロールは、次の操作に必要です。
- Storage オブジェクト管理者: ログを Cloud Storage バケットに書き込み、状態ファイルを管理します。
- Cloud Run 起動元: Pub/Sub が関数を呼び出すことを許可します。
- Cloud Functions 起動元: 関数の呼び出しを許可します
Cloud Storage バケットに対する IAM 権限を付与する
Cloud Storage バケットに対する書き込み権限をサービス アカウントに付与します。
- [Cloud Storage] > [バケット] に移動します。
- バケット名をクリックします。
- [権限] タブに移動します。
- [アクセス権を付与] をクリックします。
- 次の構成情報を指定します。
- プリンシパルを追加する: サービス アカウントのメールアドレスを入力します。例:
beyondtrust-epm-collector-sa@PROJECT_ID.iam.gserviceaccount.com - ロールを割り当てる: [ストレージ オブジェクト管理者] を選択します。
- プリンシパルを追加する: サービス アカウントのメールアドレスを入力します。例:
- [保存] をクリックします。
Pub/Sub トピックの作成
Cloud Scheduler がパブリッシュし、Cloud Run 関数がサブスクライブする Pub/Sub トピックを作成します。
- 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 に移動します。
- [サービスを作成] をクリックします。
- [関数] を選択します。インライン エディタで関数を作成します。
[構成] セクションで、次の構成の詳細を指定します。
設定 値 サービス名 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 ~ 1,000 の値を受け入れます。これより大きい値は関数によってクランプされます。MAX_BATCHES: 実行あたりのリクエスト数のセーフティ リミット。ランがヒットした場合でも、カーソルは記述された内容に進むため、次のランは作業を繰り返すのではなく、そこから続行されます。LOOKBACK_HOURS: 最初の実行が遡る期間。BeyondTrust は、パッシブ イベント(コード 106、107、603、706)を 30 日間、その他のすべてのイベントを 90 日間保持するため、ルックバックを長くしても、それより古いものは返されません。LOOKBACK_HOURS: 最初の実行時に、カーソルが存在する前にどこまで遡るか。
[変数とシークレット] タブで、[リクエスト] に移動します。
- リクエストのタイムアウト:
600秒(10 分)を入力します。
- リクエストのタイムアウト:
[コンテナ] の [設定] タブに移動します。
- [リソース] セクションで次の操作を行います。
- メモリ: 512 MiB 以上を選択します。
- CPU: [1] を選択します。
- [完了] をクリックします。
- [リソース] セクションで次の操作を行います。
[実行環境] までスクロールします。
- [デフォルト](推奨)を選択します。
[リビジョン スケーリング] セクションで、次の操作を行います。
- [インスタンスの最小数] に「
0」と入力します。 - インスタンスの最大数:
100と入力します(または、想定される負荷に基づいて調整します)。
- [インスタンスの最小数] に「
[作成] をクリックします。
サービスが作成されるまで待ちます(1 ~ 2 分)。
サービスが作成されると、インライン コードエディタが自動的に開きます。
関数コードを追加する
- [関数のエントリ ポイント] に「main」と入力します。
インライン コードエディタで、次の 2 つのファイルを作成します。
- 最初のファイル - 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')}")- 2 つ目のファイル: requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0[デプロイ] をクリックして、関数を保存してデプロイします。
デプロイが完了するまで待ちます(2 ~ 3 分)。
Cloud Scheduler ジョブを作成する
Cloud Scheduler は、定期的に Pub/Sub トピックにメッセージをパブリッシュし、Cloud Run functions の関数をトリガーします。
- Google Cloud コンソールで、[Cloud Scheduler] に移動します。
- [ジョブを作成] をクリックします。
次の構成情報を提供してください。
設定 値 名前 beyondtrust-epm-collector-hourlyリージョン Cloud Run functions と同じリージョンを選択する 周波数 0 * * * *(1 時間ごと、毎正時)タイムゾーン タイムゾーンを選択(UTC を推奨) ターゲット タイプ Pub/Sub トピック トピック「 beyondtrust-epm-trigger」を選択しますメッセージ本文 {}(空の JSON オブジェクト)[作成] をクリックします。
スケジュールの頻度オプション
ログの量とレイテンシの要件に基づいて頻度を選択します。
| 頻度 | CRON 式 | ユースケース |
|---|---|---|
| 5 分おき | */5 * * * * |
大容量、低レイテンシ |
| 15 分ごと | */15 * * * * |
検索量が普通 |
| 1 時間ごと | 0 * * * * |
標準(推奨) |
| 6 時間ごと | 0 */6 * * * |
少量、バッチ処理 |
| 毎日 | 0 0 * * * |
過去のデータの収集 |
スケジューラ ジョブをテストする
- Cloud Scheduler コンソールで、ジョブを見つけます。
- [強制実行] をクリックして、手動でトリガーします。
- 数秒待ってから、[Cloud Run] > [サービス] > [beyondtrust-epm-collector] > [ログ] に移動します。
- 関数が正常に実行されたことを確認します。
- Cloud Storage バケットをチェックして、ログが書き込まれていることを確認します。
Google SecOps サービス アカウントを取得する
Google SecOps は、一意のサービス アカウントを使用して Cloud Storage バケットからデータを読み取ります。このサービス アカウントにバケットへのアクセス権を付与する必要があります。
サービス アカウントのメールアドレスを取得する
- [SIEM 設定] > [フィード] に移動します。
- [Add New 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 バケットに対する 2 つのロールが必要です。ログ オブジェクトを読み取るための Storage オブジェクト閲覧者と、バケット メタデータを読み取るためのバケットレベルのロールです。
- [Cloud Storage] > [バケット] に移動します。
- バケット名をクリックします。
- [権限] タブに移動します。
- [アクセス権を付与] をクリックします。
- 次の構成情報を指定します。
- プリンシパルを追加: Google SecOps サービス アカウントのメールアドレスを貼り付けます。
- ロールを割り当てる: 次の両方を選択します。
- Storage オブジェクト閲覧者: ログオブジェクトを読み取ります。
- Storage レガシー バケット読み取り: バケット メタデータを読み取ります。[転送されたファイルと空のディレクトリを削除する] 削除オプションを選択した場合は、代わりに [Storage Legacy Bucket Writer] を選択します。これにより、削除権限も付与されます。
[保存] をクリックします。
BeyondTrust EPM のログを取り込むように Google SecOps でフィードを構成する
- [SIEM 設定] > [フィード] に移動します。
- [Add New Feed] をクリックします。
- [単一フィードを設定] をクリックします。
- [フィード名] フィールドに、フィードの名前を入力します(例:
BeyondTrust EPM logs)。 - [ソースタイプ] として [Google Cloud Storage V2] を選択します。
- [ログタイプ] として [BeyondTrust Endpoint Privilege Management] を選択します。
- [次へ] をクリックします。
次の入力パラメータの値を指定します。
ストレージ バケット URL: 接頭辞パスを含む 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/
- ルートバケット:
Source deletion option: 必要に応じて削除オプションを選択します。
- ファイルを削除しない: ソースからファイルを削除しません。これはテスト目的での使用をおすすめします。
転送されたファイルと空のディレクトリを削除する: フェッチが正常に完了した後、ソースからファイルと空のディレクトリを削除します。
ファイルの最大経過日数: 指定した日数以内に変更されたファイルを含めます。デフォルトは 180 日です。
アセットの名前空間: アセットの名前空間。
Ingestion labels: このフィードのイベントに適用されるラベル。
[次へ] をクリックします。
[Finalize] 画面で新しいフィードの設定を確認し、[送信] をクリックします。
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 |
変更ログ
さらにサポートが必要な場合 コミュニティ メンバーや Google SecOps のプロフェッショナルから回答を得ることができます。