Mengumpulkan log konteks entitas Duo

Didukung di:

Dokumen ini menjelaskan cara menyerap data konteks entitas Duo ke Google Security Operations menggunakan Amazon S3. Parser mengubah log JSON menjadi model data terpadu (UDM) dengan terlebih dahulu mengekstrak kolom dari JSON mentah, lalu memetakan kolom tersebut ke atribut UDM. UDM menangani berbagai skenario data, termasuk informasi pengguna dan aset, detail software, dan label keamanan, sehingga memastikan representasi yang komprehensif dalam skema UDM.

Sebelum memulai

  • Instance Google SecOps
  • Akses istimewa ke tenant Duo (aplikasi Admin API)
  • Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge)

Mengonfigurasi aplikasi Duo Admin API

  1. Login ke Panel Admin Duo.
  2. Buka Applications > Application Catalog.
  3. Tambahkan aplikasi Admin API.
  4. Catat nilai berikut:
    • Kunci integrasi (ikey)
    • Kunci rahasia (skey)
    • Nama host API (misalnya, api-XXXXXXXX.duosecurity.com)
  5. Di Permissions, aktifkan Grant resource – Read (untuk membaca pengguna, grup, perangkat/endpoint).
  6. Simpan aplikasi.

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, duo-context).
  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. Buka konsol AWS > IAM > Policies > Create policy > tab JSON.
  2. Masukkan kebijakan berikut:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutDuoObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::duo-context/*"
        }
      ]
    }
    
    • Ganti duo-context 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 WriteDuoToS3Role, 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 duo_entity_context_to_s3
    Runtime Python 3.13
    Arsitektur x86_64
    Peran eksekusi WriteDuoToS3Role
  4. Setelah fungsi dibuat, buka tab Code, hapus stub, lalu masukkan kode berikut (duo_entity_context_to_s3.py):

    #!/usr/bin/env python3
    
    import os, json, time, hmac, hashlib, base64, email.utils, urllib.parse
    from urllib.request import Request, urlopen
    import boto3
    
    # Env
    DUO_IKEY = os.environ["DUO_IKEY"]
    DUO_SKEY = os.environ["DUO_SKEY"]
    DUO_API_HOSTNAME = os.environ["DUO_API_HOSTNAME"].strip()
    S3_BUCKET = os.environ["S3_BUCKET"]
    S3_PREFIX = os.environ.get("S3_PREFIX", "duo/context/")
    # Default set can be adjusted via ENV
    RESOURCES = [r.strip() for r in os.environ.get(
        "RESOURCES",
        "users,groups,phones,endpoints,tokens,webauthncredentials,desktop_authenticators"
    ).split(",") if r.strip()]
    # Duo paging: default 100; max 500 for these endpoints
    LIMIT = int(os.environ.get("LIMIT", "500"))
    
    s3 = boto3.client("s3")
    
    def _canon_params(params: dict) -> str:
        """RFC3986 encoding with '~' unescaped, keys sorted lexicographically."""
        if not params:
            return ""
        parts = []
        for k in sorted(params.keys()):
            v = params[k]
            if v is None:
                continue
            ks = urllib.parse.quote(str(k), safe="~")
            vs = urllib.parse.quote(str(v), safe="~")
            parts.append(f"{ks}={vs}")
        return "&".join(parts)
    
    def _sign(method: str, host: str, path: str, params: dict) -> dict:
        """Construct Duo Admin API Authorization + Date headers (HMAC-SHA1)."""
        now = email.utils.formatdate()
        canon = "\n".join([now, method.upper(), host.lower(), path, _canon_params(params)])
        sig = hmac.new(DUO_SKEY.encode("utf-8"), canon.encode("utf-8"), hashlib.sha1).hexdigest()
        auth = base64.b64encode(f"{DUO_IKEY}:{sig}".encode("utf-8")).decode("utf-8")
        return {"Date": now, "Authorization": f"Basic {auth}"}
    
    def _call(method: str, path: str, params: dict) -> dict:
        host = DUO_API_HOSTNAME
        assert host.startswith("api-") and host.endswith(".duosecurity.com"), \
            "DUO_API_HOSTNAME must be e.g. api-XXXXXXXX.duosecurity.com"
        qs = _canon_params(params)
        url = f"https://{host}{path}" + (f"?{qs}" if method.upper() == "GET" and qs else "")
        req = Request(url, method=method.upper())
        for k, v in _sign(method, host, path, params).items():
            req.add_header(k, v)
        with urlopen(req, timeout=60) as r:
            return json.loads(r.read().decode("utf-8"))
    
    def _write_json(obj: dict, when: float, resource: str, page: int) -> str:
        prefix = S3_PREFIX.strip("/") + "/" if S3_PREFIX else ""
        key = f"{prefix}{time.strftime('%Y/%m/%d', time.gmtime(when))}/duo-{resource}-{page:05d}.json"
        s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(obj, separators=(",", ":")).encode("utf-8"))
        return key
    
    def _fetch_resource(resource: str) -> dict:
        """Fetch all pages for a list endpoint using limit/offset + metadata.next_offset."""
        path = f"/admin/v1/{resource}"
        offset = 0
        page = 0
        now = time.time()
        total_items = 0
    
        while True:
            params = {"limit": LIMIT, "offset": offset}
            data = _call("GET", path, params)
            _write_json(data, now, resource, page)
            page += 1
    
            resp = data.get("response")
            # most endpoints return a list; if not a list, count as 1 object page
            if isinstance(resp, list):
                total_items += len(resp)
            elif resp is not None:
                total_items += 1
    
            meta = data.get("metadata") or {}
            next_offset = meta.get("next_offset")
            if next_offset is None:
                break
    
            # Duo returns next_offset as int
            try:
                offset = int(next_offset)
            except Exception:
                break
    
        return {"resource": resource, "pages": page, "objects": total_items}
    
    def lambda_handler(event=None, context=None):
        results = []
        for res in RESOURCES:
            results.append(_fetch_resource(res))
        return {"ok": True, "results": results}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
    
  5. Buka Configuration > Environment variables > Edit > Add new environment variable.

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

    Kunci Contoh
    S3_BUCKET duo-context
    S3_PREFIX duo/context/
    DUO_IKEY DIXYZ...
    DUO_SKEY ****************
    DUO_API_HOSTNAME api-XXXXXXXX.duosecurity.com
    LIMIT 200
    RESOURCES users,groups,phones,endpoints,tokens,webauthncredentials
  7. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).

  8. Pilih tab Configuration

  9. Di panel General configuration, klik Edit.

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

Membuat jadwal EventBridge

  1. Buka Amazon EventBridge > Scheduler > Create schedule.
  2. Berikan detail konfigurasi berikut:
    • Jadwal berulang: Tarif (1 hour).
    • Target: fungsi Lambda Anda.
    • Name: duo-entity-context-1h.
  3. Klik Buat jadwal.

Opsional: Buat pengguna & kunci IAM hanya baca untuk Google SecOps

  1. Di Konsol AWS, buka IAM > Users, lalu klik Add users.
  2. Berikan detail konfigurasi berikut:
    • Pengguna: Masukkan nama unik (misalnya, secops-reader)
    • Jenis akses: Pilih Kunci akses - Akses terprogram
    • Klik Buat pengguna.
  3. Lampirkan kebijakan baca minimal (kustom): Pengguna > pilih secops-reader > Izin > Tambahkan izin > Lampirkan kebijakan secara langsung > Buat kebijakan
  4. Di editor JSON, masukkan kebijakan berikut:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::<your-bucket>/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::<your-bucket>"
        }
      ]
    }
    
  5. Tetapkan nama ke secops-reader-policy.

  6. Buka Buat kebijakan > telusuri/pilih > Berikutnya > Tambahkan izin.

  7. Buka Kredensial keamanan > Kunci akses > Buat kunci akses.

  8. Download CSV (nilai ini dimasukkan ke dalam feed).

Mengonfigurasi feed di Google SecOps untuk menyerap data Konteks Entitas Duo

  1. Buka Setelan SIEM > Feed.
  2. Klik + Tambahkan Feed Baru.
  3. Di kolom Nama feed, masukkan nama untuk feed (misalnya, Duo Entity Context).
  4. Pilih Amazon S3 V2 sebagai Jenis sumber.
  5. Pilih Data konteks Entitas Duo sebagai Jenis log.
  6. Klik Berikutnya.
  7. Tentukan nilai untuk parameter input berikut:
    • URI S3: s3://duo-context/duo/context/
    • Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
    • Usia File Maksimum: Default 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 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
diaktifkan entity.asset.deployment_status Jika 'activated' adalah false, setel ke "DECOMISSIONED", jika tidak, setel ke "ACTIVE".
browsers.browser_family entity.asset.software.name Diekstrak dari array 'browser' dalam log mentah.
browsers.browser_version entity.asset.software.version Diekstrak dari array 'browser' dalam log mentah.
device_name entity.asset.hostname Dipetakan langsung dari log mentah.
disk_encryption_status entity.asset.attribute.labels.key: "disk_encryption_status", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
email entity.user.email_addresses Dipetakan langsung dari log mentah jika berisi "@", atau menggunakan 'username' atau 'username1' jika berisi "@".
dienkripsi entity.asset.attribute.labels.key: "Encrypted", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
epkey entity.asset.product_object_id Digunakan sebagai 'product_object_id' jika ada, atau menggunakan 'phone_id' atau 'token_id'.
sidik jari entity.asset.attribute.labels.key: "Finger Print", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
firewall_status entity.asset.attribute.labels.key: "firewall_status", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
hardware_uuid entity.asset.asset_id Digunakan sebagai 'asset_id' jika ada, jika tidak, 'user_id' akan digunakan.
last_seen entity.asset.last_discover_time Diuraikan sebagai stempel waktu ISO8601 dan dipetakan.
model entity.asset.hardware.model Dipetakan langsung dari log mentah.
angka entity.user.phone_numbers Dipetakan langsung dari log mentah.
os_family entity.asset.platform_software.platform Dipetakan ke "WINDOWS", "LINUX", atau "MAC" berdasarkan nilai, tidak peka huruf besar/kecil.
os_version entity.asset.platform_software.platform_version Dipetakan langsung dari log mentah.
password_status entity.asset.attribute.labels.key: "password_status", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
phone_id entity.asset.product_object_id Digunakan sebagai 'product_object_id' jika 'epkey' tidak ada, atau menggunakan 'token_id'.
security_agents.security_agent entity.asset.software.name Diekstrak dari array 'security_agents' dalam log mentah.
security_agents.version entity.asset.software.version Diekstrak dari array 'security_agents' dalam log mentah.
timestamp entity.metadata.collected_timestamp Mengisi kolom 'collected_timestamp' dalam objek 'metadata'.
token_id entity.asset.product_object_id Digunakan sebagai 'product_object_id' jika 'epkey' dan 'phone_id' tidak ada.
trusted_endpoint entity.asset.attribute.labels.key: "trusted_endpoint", entity.asset.attribute.labels.value: Dipetakan langsung dari log mentah, dikonversi menjadi huruf kecil.
jenis entity.asset.type Jika 'type' log mentah berisi "mobile" (tidak peka huruf besar/kecil), tetapkan ke "MOBILE", jika tidak, tetapkan ke "LAPTOP".
user_id entity.asset.asset_id Digunakan sebagai 'asset_id' jika 'hardware_uuid' tidak ada.
users.email entity.user.email_addresses Digunakan sebagai 'email_addresses' jika merupakan pengguna pertama dalam array 'users' dan berisi "@".
users.username entity.user.userid Mengekstrak nama pengguna sebelum "@" dan digunakan sebagai 'userid' jika merupakan pengguna pertama dalam array 'users'.
entity.metadata.vendor_name "Duo"
entity.metadata.product_name "Data Konteks Entitas Duo"
entity.metadata.entity_type ASET
entity.relations.entity_type PENGGUNA
entity.relations.relationship MEMILIKI

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