Duo-Entitätskontext-Logs erfassen
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
- Melden Sie sich im Duo Admin Panel an.
- Rufen Sie Anwendungen > Anwendungskatalog auf.
- Fügen Sie eine Admin API-Anwendung hinzu.
- Notieren Sie die folgenden Werte:
- Integrationsschlüssel (ikey)
- Geheimer Schlüssel (skey)
- API-Hostname (z. B.
api-XXXXXXXX.duosecurity.com
)
- Aktivieren Sie unter Berechtigungen die Option Ressource gewähren – Lesen, um Nutzer, Gruppen, Geräte/Endpunkte lesen zu können.
- Speichern Sie die Anwendung.
AWS S3-Bucket und IAM für Google SecOps konfigurieren
- Erstellen Sie einen Amazon S3-Bucket. Folgen Sie dazu dieser Anleitung: Bucket erstellen.
- Speichern Sie den Namen und die Region des Buckets zur späteren Verwendung (z. B.
duo-context
). - Erstellen Sie einen Nutzer gemäß dieser Anleitung: IAM-Nutzer erstellen.
- Wählen Sie den erstellten Nutzer aus.
- Wählen Sie den Tab Sicherheitsanmeldedaten aus.
- Klicken Sie im Abschnitt Zugriffsschlüssel auf Zugriffsschlüssel erstellen.
- Wählen Sie als Anwendungsfall Drittanbieterdienst aus.
- Klicken Sie auf Weiter.
- Optional: Fügen Sie ein Beschreibungstag hinzu.
- Klicken Sie auf Zugriffsschlüssel erstellen.
- Klicken Sie auf CSV-Datei herunterladen, um den Zugriffsschlüssel und den geheimen Zugriffsschlüssel zur späteren Verwendung zu speichern.
- Klicken Sie auf Fertig.
- Wählen Sie den Tab Berechtigungen aus.
- Klicken Sie im Bereich Berechtigungsrichtlinien auf Berechtigungen hinzufügen.
- Wählen Sie Berechtigungen hinzufügen aus.
- Wählen Sie Richtlinien direkt anhängen aus.
- Suchen Sie nach der Richtlinie AmazonS3FullAccess und wählen Sie sie aus.
- Klicken Sie auf Weiter.
- Klicken Sie auf Berechtigungen hinzufügen.
IAM-Richtlinie und -Rolle für S3-Uploads konfigurieren
- Rufen Sie die AWS-Konsole > IAM > Richtlinien > Richtlinie erstellen > JSON-Tab auf.
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:
- Ersetzen Sie
Klicken Sie auf Weiter > Richtlinie erstellen.
Rufen Sie IAM > Rollen > Rolle erstellen > AWS-Service > Lambda auf.
Hängen Sie die neu erstellte Richtlinie an.
Geben Sie der Rolle den Namen
WriteDuoToS3Role
und klicken Sie auf Rolle erstellen.
Lambda-Funktion erstellen
- Rufen Sie in der AWS Console Lambda > Funktionen > Funktion erstellen auf.
- Klicken Sie auf Von Grund auf erstellen.
Geben Sie die folgenden Konfigurationsdetails an:
Einstellung Wert Name duo_entity_context_to_s3
Laufzeit Python 3.13 Architektur x86_64 Ausführungsrolle WriteDuoToS3Role
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())
Klicken Sie auf Konfiguration> Umgebungsvariablen> Bearbeiten> Neue Umgebungsvariable hinzufügen.
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
Bleiben Sie nach dem Erstellen der Funktion auf der zugehörigen Seite oder öffnen Sie Lambda > Funktionen > Ihre Funktion.
Wählen Sie den Tab Konfiguration aus.
Klicken Sie im Bereich Allgemeine Konfiguration auf Bearbeiten.
Ändern Sie Zeitlimit in 5 Minuten (300 Sekunden) und klicken Sie auf Speichern.
EventBridge-Zeitplan erstellen
- Gehen Sie zu Amazon EventBridge > Scheduler > Create schedule (Amazon EventBridge > Scheduler > Zeitplan erstellen).
- Geben Sie die folgenden Konfigurationsdetails an:
- Wiederkehrender Zeitplan: Preis (
1 hour
). - Ziel: Ihre Lambda-Funktion.
- Name:
duo-entity-context-1h
.
- Wiederkehrender Zeitplan: Preis (
- Klicken Sie auf Zeitplan erstellen.
Optional: IAM-Nutzer mit Lesezugriff und Schlüssel für Google SecOps erstellen
- Rufen Sie in der AWS-Konsole IAM > Nutzer auf und klicken Sie auf Nutzer hinzufügen.
- 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.
- Nutzer: Geben Sie einen eindeutigen Namen ein, z. B.
- Richtlinie mit minimalen Leseberechtigungen anhängen (benutzerdefiniert): Nutzer >
secops-reader
auswählen > Berechtigungen > Berechtigungen hinzufügen > Richtlinien direkt anhängen > Richtlinie erstellen 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>" } ] }
Legen Sie
secops-reader-policy
als Name fest.Gehen Sie zu Richtlinie erstellen> suchen/auswählen > Weiter > Berechtigungen hinzufügen.
Rufen Sie Sicherheitsanmeldedaten > Zugriffsschlüssel > Zugriffsschlüssel erstellen auf.
Laden Sie die CSV herunter (diese Werte werden in den Feed eingegeben).
Feed in Google SecOps konfigurieren, um Duo Entity Context-Daten aufzunehmen
- Rufen Sie die SIEM-Einstellungen > Feeds auf.
- Klicken Sie auf + Neuen Feed hinzufügen.
- Geben Sie im Feld Feed name (Feedname) einen Namen für den Feed ein, z. B.
Duo Entity Context
. - Wählen Sie Amazon S3 V2 als Quelltyp aus.
- Wählen Sie Duo Entity context data als Log type (Logtyp) aus.
- Klicken Sie auf Weiter.
- 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.
- S3-URI:
- Klicken Sie auf Weiter.
- 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. |
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