Duo-Entitätskontext-Logs erfassen

Unterstützt in:

In diesem Dokument wird beschrieben, wie Sie Kontextdaten von Duo-Entitäten mithilfe von Amazon S3 in Google Security Operations aufnehmen. Der Parser transformiert die JSON-Logs in ein einheitliches Datenmodell (Unified Data Model, UDM), indem er zuerst Felder aus dem Roh-JSON extrahiert und diese Felder dann UDM-Attributen zuordnet. Es verarbeitet verschiedene Datenszenarien, darunter Nutzer- und Asset-Informationen, Software-Details und Sicherheitslabels, um eine umfassende Darstellung im UDM-Schema zu gewährleisten.

Hinweise

  • Google SecOps-Instanz
  • Privilegierter Zugriff auf den Duo-Mandanten (Admin API-Anwendung)
  • Privilegierter Zugriff auf AWS (S3, IAM, Lambda, EventBridge)

Duo Admin API-Anwendung konfigurieren

  1. Melden Sie sich im Duo Admin Panel an.
  2. Rufen Sie Anwendungen > Anwendungskatalog auf.
  3. Fügen Sie eine Admin API-Anwendung hinzu.
  4. Notieren Sie die folgenden Werte:
    • Integrationsschlüssel (ikey)
    • Geheimer Schlüssel (skey)
    • API-Hostname (z. B. api-XXXXXXXX.duosecurity.com)
  5. Aktivieren Sie unter Berechtigungen die Option Ressource gewähren – Lesen, um Nutzer, Gruppen, Geräte/Endpunkte lesen zu können.
  6. Speichern Sie die Anwendung.

AWS S3-Bucket und IAM für Google SecOps konfigurieren

  1. Erstellen Sie einen Amazon S3-Bucket. Folgen Sie dazu dieser Anleitung: Bucket erstellen.
  2. Speichern Sie den Namen und die Region des Buckets zur späteren Verwendung (z. B. duo-context).
  3. Erstellen Sie einen Nutzer gemäß dieser Anleitung: IAM-Nutzer erstellen.
  4. Wählen Sie den erstellten Nutzer aus.
  5. Wählen Sie den Tab Sicherheitsanmeldedaten aus.
  6. Klicken Sie im Abschnitt Zugriffsschlüssel auf Zugriffsschlüssel erstellen.
  7. Wählen Sie als Anwendungsfall Drittanbieterdienst aus.
  8. Klicken Sie auf Weiter.
  9. Optional: Fügen Sie ein Beschreibungstag hinzu.
  10. Klicken Sie auf Zugriffsschlüssel erstellen.
  11. Klicken Sie auf CSV-Datei herunterladen, um den Zugriffsschlüssel und den geheimen Zugriffsschlüssel zur späteren Verwendung zu speichern.
  12. Klicken Sie auf Fertig.
  13. Wählen Sie den Tab Berechtigungen aus.
  14. Klicken Sie im Bereich Berechtigungsrichtlinien auf Berechtigungen hinzufügen.
  15. Wählen Sie Berechtigungen hinzufügen aus.
  16. Wählen Sie Richtlinien direkt anhängen aus.
  17. Suchen Sie nach der Richtlinie AmazonS3FullAccess und wählen Sie sie aus.
  18. Klicken Sie auf Weiter.
  19. Klicken Sie auf Berechtigungen hinzufügen.

IAM-Richtlinie und -Rolle für S3-Uploads konfigurieren

  1. Rufen Sie die AWS-Konsole > IAM > Richtlinien > Richtlinie erstellen > JSON-Tab auf.
  2. Geben Sie die folgende Richtlinie ein:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutDuoObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::duo-context/*"
        }
      ]
    }
    
    • Ersetzen Sie duo-context, wenn Sie einen anderen Bucket-Namen eingegeben haben:
  3. Klicken Sie auf Weiter > Richtlinie erstellen.

  4. Rufen Sie IAM > Rollen > Rolle erstellen > AWS-Service > Lambda auf.

  5. Hängen Sie die neu erstellte Richtlinie an.

  6. Geben Sie der Rolle den Namen WriteDuoToS3Role und klicken Sie auf Rolle erstellen.

Lambda-Funktion erstellen

  1. Rufen Sie in der AWS Console Lambda > Funktionen > Funktion erstellen auf.
  2. Klicken Sie auf Von Grund auf erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Name duo_entity_context_to_s3
    Laufzeit Python 3.13
    Architektur x86_64
    Ausführungsrolle WriteDuoToS3Role
  4. Nachdem die Funktion erstellt wurde, öffnen Sie den Tab Code, löschen Sie den Stub und geben Sie den folgenden Code ein (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. Klicken Sie auf Konfiguration> Umgebungsvariablen> Bearbeiten> Neue Umgebungsvariable hinzufügen.

  6. Geben Sie die folgenden Umgebungsvariablen ein und ersetzen Sie die Platzhalter durch Ihre Werte.

    Schlüssel Beispiel
    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. Bleiben Sie nach dem Erstellen der Funktion auf der zugehörigen Seite oder öffnen Sie Lambda > Funktionen > Ihre Funktion.

  8. Wählen Sie den Tab Konfiguration aus.

  9. Klicken Sie im Bereich Allgemeine Konfiguration auf Bearbeiten.

  10. Ändern Sie Zeitlimit in 5 Minuten (300 Sekunden) und klicken Sie auf Speichern.

EventBridge-Zeitplan erstellen

  1. Gehen Sie zu Amazon EventBridge > Scheduler > Create schedule (Amazon EventBridge > Scheduler > Zeitplan erstellen).
  2. Geben Sie die folgenden Konfigurationsdetails an:
    • Wiederkehrender Zeitplan: Preis (1 hour).
    • Ziel: Ihre Lambda-Funktion.
    • Name: duo-entity-context-1h.
  3. Klicken Sie auf Zeitplan erstellen.

Optional: IAM-Nutzer mit Lesezugriff und Schlüssel für Google SecOps erstellen

  1. Rufen Sie in der AWS-Konsole IAM > Nutzer auf und klicken Sie auf Nutzer hinzufügen.
  2. Geben Sie die folgenden Konfigurationsdetails an:
    • Nutzer: Geben Sie einen eindeutigen Namen ein, z. B. secops-reader.
    • Zugriffstyp: Wählen Sie Zugriffsschlüssel – programmatischer Zugriff aus.
    • Klicken Sie auf Nutzer erstellen.
  3. Richtlinie mit minimalen Leseberechtigungen anhängen (benutzerdefiniert): Nutzer > secops-reader auswählen > Berechtigungen > Berechtigungen hinzufügen > Richtlinien direkt anhängen > Richtlinie erstellen
  4. Geben Sie im JSON-Editor die folgende Richtlinie ein:

    {
      "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. Legen Sie secops-reader-policy als Name fest.

  6. Gehen Sie zu Richtlinie erstellen> suchen/auswählen > Weiter > Berechtigungen hinzufügen.

  7. Rufen Sie Sicherheitsanmeldedaten > Zugriffsschlüssel > Zugriffsschlüssel erstellen auf.

  8. Laden Sie die CSV herunter (diese Werte werden in den Feed eingegeben).

Feed in Google SecOps konfigurieren, um Duo Entity Context-Daten aufzunehmen

  1. Rufen Sie die SIEM-Einstellungen > Feeds auf.
  2. Klicken Sie auf + Neuen Feed hinzufügen.
  3. Geben Sie im Feld Feed name (Feedname) einen Namen für den Feed ein, z. B. Duo Entity Context.
  4. Wählen Sie Amazon S3 V2 als Quelltyp aus.
  5. Wählen Sie Duo Entity context data als Log type (Logtyp) aus.
  6. Klicken Sie auf Weiter.
  7. Geben Sie Werte für die folgenden Eingabeparameter an:
    • S3-URI: s3://duo-context/duo/context/
    • Optionen zum Löschen der Quelle: Wählen Sie die gewünschte Option zum Löschen aus.
    • Maximales Dateialter: Standardmäßig 180 Tage.
    • Zugriffsschlüssel-ID: Zugriffsschlüssel des Nutzers mit Zugriff auf den S3-Bucket.
    • Secret Access Key (Geheimer Zugriffsschlüssel): Geheimer Nutzersicherheitsschlüssel mit Zugriff auf den S3-Bucket.
    • Asset-Namespace: der Asset-Namespace.
    • Aufnahmelabels: Das Label, das auf die Ereignisse aus diesem Feed angewendet wird.
  8. Klicken Sie auf Weiter.
  9. Prüfen Sie die neue Feedkonfiguration auf dem Bildschirm Abschließen und klicken Sie dann auf Senden.

UDM-Zuordnungstabelle

Logfeld UDM-Zuordnung Logik
Aktiviert entity.asset.deployment_status Wenn „activated“ auf „false“ gesetzt ist, wird „DECOMISSIONED“ festgelegt, andernfalls „ACTIVE“.
browsers.browser_family entity.asset.software.name Aus dem Array „browsers“ im Rohlog extrahiert.
browsers.browser_version entity.asset.software.version Aus dem Array „browsers“ im Rohlog extrahiert.
device_name entity.asset.hostname Direkt aus dem Rohlog abgeleitet.
disk_encryption_status entity.asset.attribute.labels.key: "disk_encryption_status", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
E-Mail entity.user.email_addresses Direkt aus dem Rohlog zugeordnet, wenn er „@“ enthält. Andernfalls wird „username“ oder „username1“ verwendet, wenn diese „@“ enthalten.
verschlüsselt entity.asset.attribute.labels.key: "Encrypted", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
epkey entity.asset.product_object_id Wird als „product_object_id“ verwendet, sofern vorhanden. Andernfalls wird „phone_id“ oder „token_id“ verwendet.
Fingerprint entity.asset.attribute.labels.key: "Finger Print", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
firewall_status entity.asset.attribute.labels.key: "firewall_status", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
hardware_uuid entity.asset.asset_id Wird als „asset_id“ verwendet, falls vorhanden. Andernfalls wird „user_id“ verwendet.
last_seen entity.asset.last_discover_time Wird als ISO8601-Zeitstempel geparst und zugeordnet.
Modell entity.asset.hardware.model Direkt aus dem Rohlog abgeleitet.
Zahl entity.user.phone_numbers Direkt aus dem Rohlog abgeleitet.
os_family entity.asset.platform_software.platform Wird je nach Wert (ohne Berücksichtigung der Groß-/Kleinschreibung) „WINDOWS“, „LINUX“ oder „MAC“ zugeordnet.
os_version entity.asset.platform_software.platform_version Direkt aus dem Rohlog abgeleitet.
password_status entity.asset.attribute.labels.key: "password_status", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
phone_id entity.asset.product_object_id Wird als „product_object_id“ verwendet, wenn „epkey“ nicht vorhanden ist. Andernfalls wird „token_id“ verwendet.
security_agents.security_agent entity.asset.software.name Aus dem Array „security_agents“ im Rohlog extrahiert.
security_agents.version entity.asset.software.version Aus dem Array „security_agents“ im Rohlog extrahiert.
timestamp entity.metadata.collected_timestamp Füllt das Feld „collected_timestamp“ im Objekt „metadata“ aus.
token_id entity.asset.product_object_id Wird als „product_object_id“ verwendet, wenn „epkey“ und „phone_id“ nicht vorhanden sind.
trusted_endpoint entity.asset.attribute.labels.key: "trusted_endpoint", entity.asset.attribute.labels.value: Direkt aus dem Rohlog zugeordnet, in Kleinbuchstaben umgewandelt.
Typ entity.asset.type Wenn der Rohlog-„type“ „mobile“ (unabhängig von der Groß-/Kleinschreibung) enthält, legen Sie „MOBILE“ fest, andernfalls „LAPTOP“.
user_id entity.asset.asset_id Wird als „asset_id“ verwendet, wenn „hardware_uuid“ nicht vorhanden ist.
users.email entity.user.email_addresses Wird als „email_addresses“ verwendet, wenn es sich um den ersten Nutzer im Array „users“ handelt und „@“ enthält.
users.username entity.user.userid Der Nutzername, der vor „@“ extrahiert und als „userid“ verwendet wird, wenn er der erste Nutzer im Array „users“ ist.
entity.metadata.vendor_name „Duo“
entity.metadata.product_name „Duo Entity Context Data“
entity.metadata.entity_type ASSET
entity.relations.entity_type NUTZER
entity.relations.relationship OWNS

Benötigen Sie weitere Hilfe? Antworten von Community-Mitgliedern und Google SecOps-Experten erhalten