Mengumpulkan log JSON Kolaborasi Box

Didukung di:

Dokumen ini menjelaskan cara menyerap log JSON Kolaborasi Box ke Google Security Operations menggunakan AWS S3 menggunakan jadwal Lambda dan EventBridge. Parser memproses log peristiwa Box dalam format JSON, memetakannya ke model data terpadu (UDM). Alat ini mengekstrak kolom yang relevan dari log mentah, melakukan transformasi data seperti mengganti nama dan menggabungkan, serta memperkaya data dengan informasi perantara sebelum menghasilkan data peristiwa terstruktur.

Sebelum memulai

  • Instance Google SecOps
  • Akses istimewa ke Box (Konsol Admin + Developer)
  • Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge) di Region yang sama dengan tempat Anda berencana menyimpan log

Mengonfigurasi Konsol Developer Box (Kredensial Klien)

  1. Login ke Box Developer Console.
  2. Buat Aplikasi Kustom dengan Autentikasi Server (Pemberian Kredensial Klien).
  3. Tetapkan Akses Aplikasi = Akses Aplikasi + Perusahaan.
  4. Di Application Scopes, aktifkan Manage enterprise properties.
  5. Di Konsol Admin > Aplikasi > Pengelola Aplikasi Kustom, Beri otorisasi aplikasi dengan ID Klien.
  6. Salin dan simpan Client ID dan * Client Secret di lokasi yang aman.
  7. Buka Konsol Admin > Akun & Penagihan > Informasi Akun.
  8. Salin dan simpan ID Perusahaan di lokasi yang aman.

Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps

  1. Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket
  2. Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya, box-collaboration-logs).
  3. Buat pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
  4. Pilih Pengguna yang dibuat.
  5. Pilih tab Kredensial keamanan.
  6. Klik Create Access Key di bagian Access Keys.
  7. Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
  8. Klik Berikutnya.
  9. Opsional: tambahkan tag deskripsi.
  10. Klik Create access key.
  11. Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk digunakan nanti.
  12. Klik Selesai.
  13. Pilih tab Izin.
  14. Klik Tambahkan izin di bagian Kebijakan izin.
  15. Pilih Tambahkan izin.
  16. Pilih Lampirkan kebijakan secara langsung
  17. Telusuri dan pilih kebijakan AmazonS3FullAccess.
  18. Klik Berikutnya.
  19. Klik Add permissions.

Mengonfigurasi kebijakan dan peran IAM untuk upload S3

  1. Di konsol AWS, buka IAM > Policies > Create policy > JSON tab.
  2. Masukkan kebijakan berikut:

    {
      "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"
        }
      ]
    }
    
    
    • Ganti box-collaboration-logs jika Anda memasukkan nama bucket yang berbeda.
  3. Klik Berikutnya > Buat kebijakan.

  4. Buka IAM > Roles > Create role > AWS service > Lambda.

  5. Lampirkan kebijakan yang baru dibuat.

  6. Beri nama peran WriteBoxToS3Role, lalu klik Buat peran.

Buat fungsi Lambda

  1. Di Konsol AWS, buka Lambda > Functions > Create function.
  2. Klik Buat dari awal.
  3. Berikan detail konfigurasi berikut:

    Setelan Nilai
    Nama box_collaboration_to_s3
    Runtime Python 3.13
    Arsitektur x86_64
    Peran eksekusi WriteBoxToS3Role
  4. Setelah fungsi dibuat, buka tab Code, hapus stub, lalu masukkan kode berikut (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}
    
    
  5. Buka Configuration > Environment variables > Edit > Add new environment variable.

  6. Masukkan variabel lingkungan berikut, ganti dengan nilai Anda:

    Kunci Contoh
    S3_BUCKET box-collaboration-logs
    S3_PREFIX box/collaboration/
    STATE_KEY box/collaboration/state.json
    BOX_CLIENT_ID Masukkan ID Klien Box
    BOX_CLIENT_SECRET Masukkan Rahasia Klien Box
    BOX_ENTERPRISE_ID Masukkan ID Box Enterprise
    STREAM_TYPE admin_logs_streaming
    LIMIT 500
  7. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).

  8. Pilih tab Configuration

  9. Di panel Konfigurasi umum, klik Edit.

  10. Ubah Waktu tunggu menjadi 10 menit (600 detik), lalu klik Simpan.

Jadwalkan fungsi Lambda (EventBridge Scheduler)

  1. Buka Amazon EventBridge > Scheduler > Create schedule.
  2. Berikan detail konfigurasi berikut:
    • Jadwal berulang: Tarif (15 min).
    • Target: fungsi Lambda Anda.
    • Name: box-collaboration-schedule-15min.
  3. Klik Buat jadwal.

Mengonfigurasi feed di Google SecOps untuk menyerap log Box

  1. Buka Setelan SIEM > Feed.
  2. Klik Tambahkan Feed Baru.
  3. Di kolom Nama feed, masukkan nama untuk feed (misalnya, Box Collaboration).
  4. Pilih Amazon S3 V2 sebagai Jenis sumber.
  5. Pilih Box sebagai Jenis log.
  6. Klik Berikutnya.
  7. Tentukan nilai untuk parameter input berikut:
    • URI S3: URI bucket (formatnya harus: s3://box-collaboration-logs/box/collaboration/). Ganti box-collaboration-logs: Gunakan nama bucket yang sebenarnya.
    • Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
    • Usia File Maksimum: Menyertakan file yang diubah dalam beberapa hari terakhir. Defaultnya adalah 180 Hari.
    • ID Kunci Akses: Kunci akses pengguna dengan akses ke bucket S3.
    • Kunci Akses Rahasia: Kunci rahasia pengguna dengan akses ke bucket S3.
    • Namespace aset: Namespace aset.
    • Label penyerapan: Label yang akan diterapkan ke peristiwa dari feed ini.
  8. Klik Berikutnya.
  9. Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.

Tabel Pemetaan UDM

Kolom log Pemetaan UDM Logika
additional_details.ekm_id additional.fields Nilai yang diambil dari additional_details.ekm_id
additional_details.service_id additional.fields Nilai diambil dari additional_details.service_id
additional_details.service_name additional.fields Nilai diambil dari additional_details.service_name
additional_details.shared_link_id additional.fields Nilai diambil dari additional_details.shared_link_id
additional_details.size target.file.size Nilai diambil dari additional_details.size
additional_details.version_id additional.fields Nilai diambil dari additional_details.version_id
created_at metadata.event_timestamp Nilai diambil dari created_at
created_by.id principal.user.userid Nilai diambil dari created_by.id
created_by.login principal.user.email_addresses Nilai diambil dari created_by.login
created_by.name principal.user.user_display_name Nilai diambil dari created_by.name
event_id metadata.product_log_id Nilai diambil dari event_id
event_type metadata.product_event_type Nilai diambil dari event_type
ip_address principal.ip Nilai diambil dari ip_address
source.item_id target.file.product_object_id Nilai yang diambil dari source.item_id
source.item_name target.file.full_path Nilai yang diambil dari source.item_name
source.item_type Tidak dipetakan
source.login target.user.email_addresses Nilai yang diambil dari source.login
source.name target.user.user_display_name Nilai diambil dari source.name
source.owned_by.id target.user.userid Nilai diambil dari source.owned_by.id
source.owned_by.login target.user.email_addresses Nilai yang diambil dari source.owned_by.login
source.owned_by.name target.user.user_display_name Nilai diambil dari source.owned_by.name
source.parent.id Tidak dipetakan
source.parent.name Tidak dipetakan
source.parent.type Tidak dipetakan
source.type Tidak dipetakan
jenis metadata.log_type Nilai diambil dari jenis
metadata.vendor_name Nilai yang di-hardcode
metadata.product_name Nilai yang di-hardcode
security_result.action Diperoleh dari event_type. Jika event_type adalah FAILED_LOGIN, maka BLOCK, jika event_type adalah USER_LOGIN, maka ALLOW, jika tidak, UNSPECIFIED.
extensions.auth.type Diperoleh dari event_type. Jika event_type adalah USER_LOGIN atau ADMIN_LOGIN, maka MACHINE, jika tidak, UNSPECIFIED.
extensions.auth.mechanism Diperoleh dari event_type. Jika event_type adalah USER_LOGIN atau ADMIN_LOGIN, maka USERNAME_PASSWORD, jika tidak, UNSPECIFIED.

Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.