Box Collaboration の JSON ログを収集する
このドキュメントでは、Lambda と EventBridge スケジュールを使用して AWS S3 を使用して Box Collaboration JSON ログを Google Security Operations に取り込む方法について説明します。パーサーは、JSON 形式の Box イベントログを処理し、統合データモデル(UDM)にマッピングします。未加工のログから関連するフィールドを抽出し、名前の変更や統合などのデータ変換を行い、中間情報でデータを拡充してから、構造化されたイベントデータを出力します。
始める前に
- Google SecOps インスタンス
- Box への特権アクセス(管理コンソールとデベロッパー コンソール)
- ログの保存を予定している同じリージョンの AWS(S3、IAM、Lambda、EventBridge)への特権アクセス
Box Developer Console(クライアント認証情報)を構成する
- Box Developer Console にログインします。
- サーバー認証(クライアント認証情報の権限付与)を使用してカスタムアプリを作成します。
- [Application Access] を [App + Enterprise Access] に設定します。
- [Application Scopes] で、[Manage enterprise properties] を有効にします。
- 管理コンソール > [アプリ] > [カスタム アプリ マネージャー] で、クライアント ID でアプリを承認します。
- [クライアント ID] と [クライアント シークレット] をコピーして、安全な場所に保存します。
- 管理コンソール > [アカウントと請求] > [アカウント情報] に移動します。
- エンタープライズ ID をコピーして安全な場所に保存します。
Google SecOps 用に AWS S3 バケットと IAM を構成する
- バケットの作成のユーザーガイドに沿って、Amazon S3 バケットを作成します。
- 後で参照できるように、バケットの名前とリージョンを保存します(例:
box-collaboration-logs
)。 - IAM ユーザーの作成のユーザーガイドに沿って、ユーザーを作成します。
- 作成したユーザーを選択します。
- [セキュリティ認証情報] タブを選択します。
- [アクセスキー] セクションで [アクセスキーを作成] をクリックします。
- [ユースケース] として [サードパーティ サービス] を選択します。
- [次へ] をクリックします。
- 省略可: 説明タグを追加します。
- [アクセスキーを作成] をクリックします。
- [CSV ファイルをダウンロード] をクリックして、[アクセスキー] と [シークレット アクセスキー] を保存し、後で使用できるようにします。
- [完了] をクリックします。
- [権限] タブを選択します。
- [権限ポリシー] セクションで、[権限を追加] をクリックします。
- [権限を追加] を選択します。
- [ポリシーを直接アタッチする] を選択します。
- AmazonS3FullAccess ポリシーを検索して選択します。
- [次へ] をクリックします。
- [権限を追加] をクリックします。
S3 アップロードの IAM ポリシーとロールを構成する
- AWS コンソールで、[IAM] > [ポリシー] > [ポリシーの作成] > [JSON] タブに移動します。
次のポリシーを入力します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutBoxObjects", "Effect": "Allow", "Action": ["s3:PutObject"], "Resource": "arn:aws:s3:::box-collaboration-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::box-collaboration-logs/box/collaboration/state.json" } ] }
- 別のバケット名を入力した場合は、
box-collaboration-logs
を置き換えます。
- 別のバケット名を入力した場合は、
[次へ] > [ポリシーを作成] をクリックします。
[IAM] > [ロール] > [ロールの作成] > [AWS サービス] > [Lambda] に移動します。
新しく作成したポリシーを関連付けます。
ロールに「
WriteBoxToS3Role
」という名前を付けて、[ロールを作成] をクリックします。
Lambda 関数を作成する
- AWS コンソールで、[Lambda] > [Functions] > [Create function] に移動します。
- [Author from scratch] をクリックします。
次の構成情報を提供してください。
設定 値 名前 box_collaboration_to_s3
ランタイム Python 3.13 アーキテクチャ x86_64 実行ロール WriteBoxToS3Role
関数を作成したら、[コード] タブを開き、スタブを削除して次のコード(
box_collaboration_to_s3.py
)を入力します。#!/usr/bin/env python3 # Lambda: Pull Box Enterprise Events to S3 (no transform) import os, json, time, urllib.parse from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError import boto3 TOKEN_URL = "https://api.box.com/oauth2/token" EVENTS_URL = "https://api.box.com/2.0/events" CID = os.environ["BOX_CLIENT_ID"] CSECRET = os.environ["BOX_CLIENT_SECRET"] ENT_ID = os.environ["BOX_ENTERPRISE_ID"] STREAM_TYPE = os.environ.get("STREAM_TYPE", "admin_logs_streaming") LIMIT = int(os.environ.get("LIMIT", "500")) BUCKET = os.environ["S3_BUCKET"] PREFIX = os.environ.get("S3_PREFIX", "box/collaboration/") STATE_KEY = os.environ.get("STATE_KEY", "box/collaboration/state.json") s3 = boto3.client("s3") def get_state(): try: obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY) data = json.loads(obj["Body"].read()) return data.get("stream_position") except Exception: return None def put_state(pos): body = json.dumps({"stream_position": pos}, separators=(",", ":")).encode("utf-8") s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json") def get_token(): body = urllib.parse.urlencode({ "grant_type": "client_credentials", "client_id": CID, "client_secret": CSECRET, "box_subject_type": "enterprise", "box_subject_id": ENT_ID, }).encode() req = Request(TOKEN_URL, data=body, method="POST") req.add_header("Content-Type", "application/x-www-form-urlencoded") with urlopen(req, timeout=30) as r: tok = json.loads(r.read().decode()) return tok["access_token"] def fetch_events(token, stream_position=None, timeout=60, max_retries=5): params = {"stream_type": STREAM_TYPE, "limit": LIMIT, "stream_position": stream_position or "now"} qs = urllib.parse.urlencode(params) attempt, backoff = 0, 1.0 while True: try: req = Request(f"{EVENTS_URL}?{qs}", method="GET") req.add_header("Authorization", f"Bearer {token}") with urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode()) except HTTPError as e: if e.code == 429 and attempt < max_retries: ra = e.headers.get("Retry-After") delay = int(ra) if (ra and ra.isdigit()) else int(backoff) time.sleep(max(1, delay)); attempt += 1; backoff *= 2; continue if 500 <= e.code <= 599 and attempt < max_retries: time.sleep(backoff); attempt += 1; backoff *= 2; continue raise except URLError: if attempt < max_retries: time.sleep(backoff); attempt += 1; backoff *= 2; continue raise def write_chunk(data): ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime()) key = f"{PREFIX}/{ts}-box-events.json" s3.put_object(Bucket=BUCKET, Key=key, Body=json.dumps(data, separators=(",", ":")).encode("utf-8"), ContentType="application/json") return key def lambda_handler(event=None, context=None): token = get_token() pos = get_state() total, idx = 0, 0 while True: page = fetch_events(token, pos) entries = page.get("entries") or [] if not entries: next_pos = page.get("next_stream_position") or pos if next_pos and next_pos != pos: put_state(next_pos) break # уникальный ключ ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime()) key = f"{PREFIX}/{ts}-box-events-{idx:03d}.json" s3.put_object(Bucket=BUCKET, Key=key, Body=json.dumps(page, separators=(",", ":")).encode("utf-8"), ContentType="application/json") idx += 1 total += len(entries) pos = page.get("next_stream_position") or pos if pos: put_state(pos) if len(entries) < LIMIT: break return {"ok": True, "written": total, "next_stream_position": pos}
[構成> 環境変数 > 編集 > 新しい環境変数を追加] に移動します。
次の環境変数を入力し、実際の値に置き換えます。
キー 例 S3_BUCKET
box-collaboration-logs
S3_PREFIX
box/collaboration/
STATE_KEY
box/collaboration/state.json
BOX_CLIENT_ID
Box クライアント ID を入力する BOX_CLIENT_SECRET
Box クライアント シークレットを入力 BOX_ENTERPRISE_ID
Box Enterprise ID を入力する STREAM_TYPE
admin_logs_streaming
LIMIT
500
関数が作成されたら、そのページにとどまるか、[Lambda] > [関数] > [your-function] を開きます。
[CONFIGURATION] タブを選択します。
[全般的な構成] パネルで、[編集] をクリックします。
[タイムアウト] を [10 分(600 秒)] に変更し、[保存] をクリックします。
Lambda 関数をスケジュールする(EventBridge Scheduler)
- Amazon EventBridge > Scheduler > スケジュールの作成に移動します。
- 次の構成の詳細を入力します。
- 定期的なスケジュール: レート(
15 min
)。 - ターゲット: Lambda 関数。
- 名前:
box-collaboration-schedule-15min
- 定期的なスケジュール: レート(
- [スケジュールを作成] をクリックします。
Box のログを取り込むように Google SecOps でフィードを構成する
- [SIEM 設定] > [フィード] に移動します。
- [Add New Feed] をクリックします。
- [フィード名] フィールドに、フィードの名前を入力します(例:
Box Collaboration
)。 - [ソースタイプ] として [Amazon S3 V2] を選択します。
- [ログタイプ] として [Box] を選択します。
- [次へ] をクリックします。
- 次の入力パラメータの値を指定します。
- S3 URI: バケット URI(形式は
s3://box-collaboration-logs/box/collaboration/
にする必要があります)。box-collaboration-logs
を置き換えます。バケットの実際の名前を使用します。 - Source deletion options: 必要に応じて削除オプションを選択します。
- ファイルの最大経過日数: 指定した日数以内に変更されたファイルを含めます。デフォルトは 180 日です。
- アクセスキー ID: S3 バケットにアクセスできるユーザー アクセスキー。
- シークレット アクセスキー: S3 バケットにアクセスできるユーザーのシークレット キー。
- アセットの名前空間: アセットの名前空間。
- Ingestion labels: このフィードのイベントに適用されるラベル。
- S3 URI: バケット URI(形式は
- [次へ] をクリックします。
- [Finalize] 画面で新しいフィードの設定を確認し、[送信] をクリックします。
UDM マッピング テーブル
ログフィールド | UDM マッピング | ロジック |
---|---|---|
additional_details.ekm_id | additional.fields | additional_details.ekm_id から取得した値 |
additional_details.service_id | additional.fields | additional_details.service_id から取得された値 |
additional_details.service_name | additional.fields | additional_details.service_name から取得した値 |
additional_details.shared_link_id | additional.fields | additional_details.shared_link_id から取得した値 |
additional_details.size | target.file.size | additional_details.size から取得された値 |
additional_details.version_id | additional.fields | additional_details.version_id から取得した値 |
created_at | metadata.event_timestamp | created_at から取得された値 |
created_by.id | principal.user.userid | created_by.id から取得した値 |
created_by.login | principal.user.email_addresses | created_by.login から取得された値 |
created_by.name | principal.user.user_display_name | created_by.name から取得された値 |
event_id | metadata.product_log_id | event_id から取得した値 |
event_type | metadata.product_event_type | event_type から取得された値 |
ip_address | principal.ip | ip_address から取得した値 |
source.item_id | target.file.product_object_id | source.item_id から取得された値 |
source.item_name | target.file.full_path | source.item_name から取得した値 |
source.item_type | マッピングされていません | |
source.login | target.user.email_addresses | source.login から取得された値 |
source.name | target.user.user_display_name | source.name から取得された値 |
source.owned_by.id | target.user.userid | source.owned_by.id から取得した値 |
source.owned_by.login | target.user.email_addresses | source.owned_by.login から取得された値 |
source.owned_by.name | target.user.user_display_name | source.owned_by.name から取得した値 |
source.parent.id | マッピングされていません | |
source.parent.name | マッピングされていません | |
source.parent.type | マッピングされていません | |
source.type | マッピングされていません | |
type | metadata.log_type | 型から取得された値 |
metadata.vendor_name | ハードコードされた値 | |
metadata.product_name | ハードコードされた値 | |
security_result.action | event_type から取得されます。event_type が FAILED_LOGIN の場合は BLOCK、event_type が USER_LOGIN の場合は ALLOW、それ以外の場合は UNSPECIFIED。 | |
extensions.auth.type | event_type から取得されます。event_type が USER_LOGIN または ADMIN_LOGIN の場合は MACHINE、それ以外の場合は UNSPECIFIED。 | |
extensions.auth.mechanism | event_type から取得されます。event_type が USER_LOGIN または ADMIN_LOGIN の場合は USERNAME_PASSWORD、それ以外の場合は UNSPECIFIED。 |
さらにサポートが必要な場合 コミュニティ メンバーや Google SecOps のプロフェッショナルから回答を得ることができます。