Raccogliere i log del contesto dell'entità Duo

Supportato in:

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

  1. Accedi al pannello di amministrazione di Duo.
  2. Vai ad Applicazioni > Catalogo applicazioni.
  3. Aggiungi l'applicazione API Admin.
  4. Registra i seguenti valori:
    • Chiave di integrazione (ikey)
    • Chiave segreta (skey)
    • Nome host API (ad esempio, api-XXXXXXXX.duosecurity.com)
  5. In Autorizzazioni, attiva Concedi risorsa - Lettura (per leggere utenti, gruppi, dispositivi/endpoint).
  6. Salva l'applicazione.

Configura il bucket AWS S3 e IAM per Google SecOps

  1. Crea un bucket Amazon S3 seguendo questa guida utente: Creazione di un bucket
  2. Salva il nome e la regione del bucket per riferimento futuro (ad esempio, duo-context).
  3. Crea un utente seguendo questa guida: Creazione di un utente IAM.
  4. Seleziona l'utente creato.
  5. Seleziona la scheda Credenziali di sicurezza.
  6. Fai clic su Crea chiave di accesso nella sezione Chiavi di accesso.
  7. Seleziona Servizio di terze parti come Caso d'uso.
  8. Fai clic su Avanti.
  9. (Facoltativo) Aggiungi un tag di descrizione.
  10. Fai clic su Crea chiave di accesso.
  11. Fai clic su Scarica file CSV per salvare la chiave di accesso e la chiave di accesso segreta per un utilizzo successivo.
  12. Fai clic su Fine.
  13. Seleziona la scheda Autorizzazioni.
  14. Fai clic su Aggiungi autorizzazioni nella sezione Criteri per le autorizzazioni.
  15. Seleziona Aggiungi autorizzazioni.
  16. Seleziona Allega direttamente i criteri.
  17. Cerca e seleziona il criterio AmazonS3FullAccess.
  18. Fai clic su Avanti.
  19. Fai clic su Aggiungi autorizzazioni.

Configura il ruolo e il criterio IAM per i caricamenti S3

  1. Vai alla console AWS > IAM > Policy > Crea policy > scheda JSON.
  2. 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:
  3. Fai clic su Avanti > Crea policy.

  4. Vai a IAM > Ruoli > Crea ruolo > Servizio AWS > Lambda.

  5. Allega il criterio appena creato.

  6. Assegna al ruolo il nome WriteDuoToS3Role e fai clic su Crea ruolo.

Crea la funzione Lambda

  1. Nella console AWS, vai a Lambda > Funzioni > Crea funzione.
  2. Fai clic su Crea autore da zero.
  3. 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
  4. 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())
    
    
  5. Vai a Configurazione > Variabili di ambiente > Modifica > Aggiungi nuova variabile di ambiente.

  6. 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
  7. Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).

  8. Seleziona la scheda Configurazione.

  9. Nel riquadro Configurazione generale, fai clic su Modifica.

  10. Modifica Timeout impostandolo su 5 minuti (300 secondi) e fai clic su Salva.

Creare una pianificazione EventBridge

  1. Vai a Amazon EventBridge > Scheduler > Crea pianificazione.
  2. Fornisci i seguenti dettagli di configurazione:
    • Programma ricorrente: Tariffa (1 hour).
    • Destinazione: la tua funzione Lambda.
    • Nome: duo-entity-context-1h
  3. Fai clic su Crea pianificazione.

(Facoltativo) Crea chiavi e utente IAM di sola lettura per Google SecOps

  1. Nella console AWS, vai a IAM > Utenti, poi fai clic su Aggiungi utenti.
  2. 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.
  3. Allega criterio per la lettura minimi (personalizzati): Utenti > seleziona secops-reader > Autorizzazioni > Aggiungi autorizzazioni > Allega criteri direttamente > Crea criteri
  4. 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>"
        }
      ]
    }
    
  5. Imposta il nome su secops-reader-policy.

  6. Vai a Crea criterio > cerca/seleziona > Avanti > Aggiungi autorizzazioni.

  7. Vai a Credenziali di sicurezza > Chiavi di accesso > Crea chiave di accesso.

  8. 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

  1. Vai a Impostazioni SIEM > Feed.
  2. Fai clic su + Aggiungi nuovo feed.
  3. Nel campo Nome feed, inserisci un nome per il feed (ad esempio, Duo Entity Context).
  4. Seleziona Amazon S3 V2 come Tipo di origine.
  5. Seleziona Dati contestuali dell'entità Duo come Tipo di log.
  6. Fai clic su Avanti.
  7. 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.
  8. Fai clic su Avanti.
  9. 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.
email 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.