Raccogliere i log del contesto dell'entità Duo
Questo documento spiega come importare i dati di contesto delle entità Duo in Google Security Operations utilizzando Amazon S3. Il parser trasforma i log JSON in un modello UDM (Unified Data Model) estraendo prima i campi dal JSON non elaborato e poi mappandoli agli attributi UDM. Gestisce vari scenari di dati, tra cui informazioni su utenti e asset, dettagli del software ed etichette di sicurezza, garantendo una rappresentazione completa nello schema UDM.
Prima di iniziare
- Istanza Google SecOps
- Accesso privilegiato al tenant Duo (applicazione API Admin)
- Accesso privilegiato ad AWS (S3, IAM, Lambda, EventBridge)
Configurare l'applicazione API Duo Admin
- Accedi al pannello di amministrazione di Duo.
- Vai ad Applicazioni > Catalogo applicazioni.
- Aggiungi l'applicazione API Admin.
- Registra i seguenti valori:
- Chiave di integrazione (ikey)
- Chiave segreta (skey)
- Nome host API (ad esempio,
api-XXXXXXXX.duosecurity.com
)
- In Autorizzazioni, attiva Concedi risorsa - Lettura (per leggere utenti, gruppi, dispositivi/endpoint).
- Salva l'applicazione.
Configura il bucket AWS S3 e IAM per Google SecOps
- Crea un bucket Amazon S3 seguendo questa guida utente: Creazione di un bucket
- Salva il nome e la regione del bucket per riferimento futuro (ad esempio,
duo-context
). - Crea un utente seguendo questa guida: Creazione di un utente IAM.
- Seleziona l'utente creato.
- Seleziona la scheda Credenziali di sicurezza.
- Fai clic su Crea chiave di accesso nella sezione Chiavi di accesso.
- Seleziona Servizio di terze parti come Caso d'uso.
- Fai clic su Avanti.
- (Facoltativo) Aggiungi un tag di descrizione.
- Fai clic su Crea chiave di accesso.
- Fai clic su Scarica file CSV per salvare la chiave di accesso e la chiave di accesso segreta per un utilizzo successivo.
- Fai clic su Fine.
- Seleziona la scheda Autorizzazioni.
- Fai clic su Aggiungi autorizzazioni nella sezione Criteri per le autorizzazioni.
- Seleziona Aggiungi autorizzazioni.
- Seleziona Allega direttamente i criteri.
- Cerca e seleziona il criterio AmazonS3FullAccess.
- Fai clic su Avanti.
- Fai clic su Aggiungi autorizzazioni.
Configura il ruolo e il criterio IAM per i caricamenti S3
- Vai alla console AWS > IAM > Policy > Crea policy > scheda JSON.
Inserisci la seguente policy:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutDuoObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::duo-context/*" } ] }
- Sostituisci
duo-context
se hai inserito un nome bucket diverso:
- Sostituisci
Fai clic su Avanti > Crea policy.
Vai a IAM > Ruoli > Crea ruolo > Servizio AWS > Lambda.
Allega il criterio appena creato.
Assegna al ruolo il nome
WriteDuoToS3Role
e fai clic su Crea ruolo.
Crea la funzione Lambda
- Nella console AWS, vai a Lambda > Funzioni > Crea funzione.
- Fai clic su Crea autore da zero.
Fornisci i seguenti dettagli di configurazione:
Impostazione Valore Nome duo_entity_context_to_s3
Tempo di esecuzione Python 3.13 Architettura x86_64 Ruolo di esecuzione WriteDuoToS3Role
Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e inserisci il seguente codice (
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())
Vai a Configurazione > Variabili di ambiente > Modifica > Aggiungi nuova variabile di ambiente.
Inserisci le seguenti variabili di ambiente, sostituendole con i tuoi valori.
Chiave Esempio 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
Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).
Seleziona la scheda Configurazione.
Nel riquadro Configurazione generale, fai clic su Modifica.
Modifica Timeout impostandolo su 5 minuti (300 secondi) e fai clic su Salva.
Creare una pianificazione EventBridge
- Vai a Amazon EventBridge > Scheduler > Crea pianificazione.
- Fornisci i seguenti dettagli di configurazione:
- Programma ricorrente: Tariffa (
1 hour
). - Destinazione: la tua funzione Lambda.
- Nome:
duo-entity-context-1h
- Programma ricorrente: Tariffa (
- Fai clic su Crea pianificazione.
(Facoltativo) Crea chiavi e utente IAM di sola lettura per Google SecOps
- Nella console AWS, vai a IAM > Utenti, poi fai clic su Aggiungi utenti.
- Fornisci i seguenti dettagli di configurazione:
- Utente: inserisci un nome univoco (ad esempio
secops-reader
) - Tipo di accesso: seleziona Chiave di accesso - Accesso programmatico
- Fai clic su Crea utente.
- Utente: inserisci un nome univoco (ad esempio
- Allega criterio per la lettura minimi (personalizzati): Utenti > seleziona
secops-reader
> Autorizzazioni > Aggiungi autorizzazioni > Allega criteri direttamente > Crea criteri Nell'editor JSON, inserisci la seguente policy:
{ "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>" } ] }
Imposta il nome su
secops-reader-policy
.Vai a Crea criterio > cerca/seleziona > Avanti > Aggiungi autorizzazioni.
Vai a Credenziali di sicurezza > Chiavi di accesso > Crea chiave di accesso.
Scarica il file CSV (questi valori vengono inseriti nel feed).
Configura un feed in Google SecOps per importare i dati del contesto delle entità di Duo
- Vai a Impostazioni SIEM > Feed.
- Fai clic su + Aggiungi nuovo feed.
- Nel campo Nome feed, inserisci un nome per il feed (ad esempio,
Duo Entity Context
). - Seleziona Amazon S3 V2 come Tipo di origine.
- Seleziona Dati contestuali dell'entità Duo come Tipo di log.
- Fai clic su Avanti.
- Specifica i valori per i seguenti parametri di input:
- URI S3:
s3://duo-context/duo/context/
- Opzioni di eliminazione dell'origine: seleziona l'opzione di eliminazione in base alle tue preferenze.
- Durata massima del file: 180 giorni per impostazione predefinita.
- ID chiave di accesso: chiave di accesso utente con accesso al bucket S3.
- Chiave di accesso segreta: chiave segreta dell'utente con accesso al bucket S3.
- Spazio dei nomi dell'asset: lo spazio dei nomi dell'asset.
- Etichette di importazione: l'etichetta applicata agli eventi di questo feed.
- URI S3:
- Fai clic su Avanti.
- Controlla la nuova configurazione del feed nella schermata Finalizza e poi fai clic su Invia.
Tabella di mappatura UDM
Campo log | Mappatura UDM | Logic |
---|---|---|
attivato | entity.asset.deployment_status | Se "activated" è false, imposta "DECOMISSIONED", altrimenti "ACTIVE". |
browsers.browser_family | entity.asset.software.name | Estratto dall'array "browser" nel log non elaborato. |
browsers.browser_version | entity.asset.software.version | Estratto dall'array "browser" nel log non elaborato. |
device_name | entity.asset.hostname | Mappato direttamente dal log non elaborato. |
disk_encryption_status | entity.asset.attribute.labels.key: "disk_encryption_status", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
entity.user.email_addresses | Mappato direttamente dal log non elaborato se contiene "@", altrimenti utilizza "username" o "username1" se contengono "@". | |
criptato | entity.asset.attribute.labels.key: "Encrypted", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
epkey | entity.asset.product_object_id | Utilizzato come "product_object_id" se presente, altrimenti utilizza "phone_id" o "token_id". |
impronta | entity.asset.attribute.labels.key: "Finger Print", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
firewall_status | entity.asset.attribute.labels.key: "firewall_status", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
hardware_uuid | entity.asset.asset_id | Utilizzato come "asset_id" se presente, altrimenti utilizza "user_id". |
last_seen | entity.asset.last_discover_time | Analizzato come timestamp ISO8601 e mappato. |
modello | entity.asset.hardware.model | Mappato direttamente dal log non elaborato. |
numero | entity.user.phone_numbers | Mappato direttamente dal log non elaborato. |
os_family | entity.asset.platform_software.platform | Mappato su "WINDOWS", "LINUX" o "MAC" in base al valore, senza distinzione tra maiuscole e minuscole. |
os_version | entity.asset.platform_software.platform_version | Mappato direttamente dal log non elaborato. |
password_status | entity.asset.attribute.labels.key: "password_status", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
phone_id | entity.asset.product_object_id | Utilizzato come "product_object_id" se "epkey" non è presente, altrimenti utilizza "token_id". |
security_agents.security_agent | entity.asset.software.name | Estratto dall'array "security_agents" nel log non elaborato. |
security_agents.version | entity.asset.software.version | Estratto dall'array "security_agents" nel log non elaborato. |
timestamp | entity.metadata.collected_timestamp | Compila il campo "collected_timestamp" all'interno dell'oggetto "metadata". |
token_id | entity.asset.product_object_id | Utilizzato come "product_object_id" se "epkey" e "phone_id" non sono presenti. |
trusted_endpoint | entity.asset.attribute.labels.key: "trusted_endpoint", entity.asset.attribute.labels.value: |
Mappato direttamente dal log non elaborato, convertito in minuscolo. |
tipo | entity.asset.type | Se il "tipo" del log grezzo contiene "mobile" (senza distinzione tra maiuscole e minuscole), impostalo su "MOBILE", altrimenti su "LAPTOP". |
user_id | entity.asset.asset_id | Utilizzato come "asset_id" se "hardware_uuid" non è presente. |
users.email | entity.user.email_addresses | Utilizzato come "email_addresses" se è il primo utente nell'array "users" e contiene "@". |
users.username | entity.user.userid | Nome utente estratto prima di "@" e utilizzato come "userid" se è il primo utente nell'array "users". |
entity.metadata.vendor_name | "Duo" | |
entity.metadata.product_name | "Duo Entity Context Data" | |
entity.metadata.entity_type | RISORSA | |
entity.relations.entity_type | UTENTE | |
entity.relations.relationship | OWNS |
Hai bisogno di ulteriore assistenza? Ricevi risposte dai membri della community e dai professionisti di Google SecOps.