Raccogliere i log delle operazioni di Zoom

Supportato in:

Questo documento spiega come importare i log delle operazioni di Zoom in Google Security Operations utilizzando Amazon S3. Il parser trasforma i log non elaborati in un modello dei dati unificato (UDM). Estrae i campi dal messaggio di log non elaborato, esegue la pulizia e la normalizzazione dei dati e mappa le informazioni estratte nei campi UDM corrispondenti, arricchendo infine i dati per l'analisi e la correlazione all'interno di un sistema SIEM.

Prima di iniziare

Assicurati di soddisfare i seguenti prerequisiti:

  • Istanza Google SecOps
  • Accesso privilegiato a Zoom
  • Accesso con privilegi ad AWS (S3, IAM, Lambda, EventBridge)

Raccogli i prerequisiti per i log delle operazioni di Zoom (ID, chiavi API, ID organizzazione, token)

  1. Accedi a Zoom App Marketplace.
  2. Vai a Sviluppa > Crea app > OAuth da server a server.
  3. Crea l'app e aggiungi il seguente ambito: report:read:operation_logs:admin (o report:read:admin).
  4. In Credenziali app, copia e salva i seguenti dettagli in una posizione sicura:
    • ID account.
    • ID client.
    • Client secret.

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, zoom-operation-logs).
  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. Nella console AWS, vai a IAM > Policy > Crea policy > scheda JSON.
  2. Inserisci la seguente policy:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutZoomOperationLogs",
          "Effect": "Allow",
          "Action": ["s3:PutObject"],
          "Resource": "arn:aws:s3:::zoom-operation-logs/zoom/operationlogs/*"
        },
        {
          "Sid": "AllowStateReadWrite",
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:PutObject"],
          "Resource": "arn:aws:s3:::zoom-operation-logs/zoom/operationlogs/state.json"
        }
      ]
    }
    
    • Sostituisci zoom-operation-logs se hai inserito un nome bucket diverso.
  3. Fai clic su Avanti > Crea criterio.

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

  5. Allega il criterio appena creato.

  6. Assegna al ruolo il nome WriteZoomOperationLogsToS3Role 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 zoom_operationlogs_to_s3
Tempo di esecuzione Python 3.13
Architettura x86_64
Ruolo di esecuzione WriteZoomOperationLogsToS3Role
  1. Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e inserisci il seguente codice(zoom_operationlogs_to_s3.py):

    #!/usr/bin/env python3
    import os, json, gzip, io, uuid, datetime as dt, base64, urllib.parse, urllib.request
    import boto3
    
    # ---- Environment ----
    S3_BUCKET = os.environ["S3_BUCKET"]
    S3_PREFIX = os.environ.get("S3_PREFIX", "zoom/operationlogs/")
    STATE_KEY = os.environ.get("STATE_KEY", S3_PREFIX + "state.json")
    ZOOM_ACCOUNT_ID = os.environ["ZOOM_ACCOUNT_ID"]
    ZOOM_CLIENT_ID = os.environ["ZOOM_CLIENT_ID"]
    ZOOM_CLIENT_SECRET = os.environ["ZOOM_CLIENT_SECRET"]
    PAGE_SIZE = int(os.environ.get("PAGE_SIZE", "300"))  # API default 30; max may vary
    TIMEOUT = int(os.environ.get("TIMEOUT", "30"))
    
    TOKEN_URL = "https://zoom.us/oauth/token"
    REPORT_URL = "https://api.zoom.us/v2/report/operationlogs"
    
    s3 = boto3.client("s3")
    
    # ---- Helpers ----
    
    def _http(req: urllib.request.Request):
        return urllib.request.urlopen(req, timeout=TIMEOUT)
    
    def get_token() -> str:
        params = urllib.parse.urlencode({
            "grant_type": "account_credentials",
            "account_id": ZOOM_ACCOUNT_ID,
        }).encode()
        basic = base64.b64encode(f"{ZOOM_CLIENT_ID}:{ZOOM_CLIENT_SECRET}".encode()).decode()
        req = urllib.request.Request(
            TOKEN_URL,
            data=params,
            headers={
                "Authorization": f"Basic {basic}",
                "Content-Type": "application/x-www-form-urlencoded",
                "Accept": "application/json",
                "Host": "zoom.us",
            },
            method="POST",
        )
        with _http(req) as r:
            body = json.loads(r.read())
            return body["access_token"]
    
    def get_state() -> dict:
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(obj["Body"].read())
        except Exception:
            # initial state: start today
            today = dt.date.today().isoformat()
            return {"cursor_date": today, "next_page_token": None}
    
    def put_state(state: dict):
        state["updated_at"] = dt.datetime.utcnow().isoformat() + "Z"
        s3.put_object(Bucket=S3_BUCKET, Key=STATE_KEY, Body=json.dumps(state).encode())
    
    def write_chunk(items: list[dict], ts: dt.datetime) -> str:
        key = f"{S3_PREFIX}{ts:%Y/%m/%d}/zoom-operationlogs-{uuid.uuid4()}.json.gz"
        buf = io.BytesIO()
        with gzip.GzipFile(fileobj=buf, mode="w") as gz:
            for rec in items:
                gz.write((json.dumps(rec) + "n").encode())
        buf.seek(0)
        s3.upload_fileobj(buf, S3_BUCKET, key)
        return key
    
    def fetch_page(token: str, from_date: str, to_date: str, next_page_token: str | None) -> dict:
        q = {
            "from": from_date,
            "to": to_date,
            "page_size": str(PAGE_SIZE),
        }
        if next_page_token:
            q["next_page_token"] = next_page_token
        url = REPORT_URL + "?" + urllib.parse.urlencode(q)
        req = urllib.request.Request(url, headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json",
        })
        with _http(req) as r:
            return json.loads(r.read())
    
    def lambda_handler(event=None, context=None):
        token = get_token()
        state = get_state()
    
        cursor_date = state.get("cursor_date")  # YYYY-MM-DD
        # API requires from/to in yyyy-mm-dd, max one month per request
        from_date = cursor_date
        to_date = cursor_date
    
        total_written = 0
        next_token = state.get("next_page_token")
    
        while True:
            page = fetch_page(token, from_date, to_date, next_token)
            items = page.get("operation_logs", []) or []
            if items:
                write_chunk(items, dt.datetime.utcnow())
                total_written += len(items)
            next_token = page.get("next_page_token")
            if not next_token:
                break
    
        # Advance to next day if we've finished this date
        today = dt.date.today().isoformat()
        if cursor_date < today:
            nxt = (dt.datetime.fromisoformat(cursor_date) + dt.timedelta(days=1)).date().isoformat()
            state["cursor_date"] = nxt
            state["next_page_token"] = None
        else:
            # stay on today; continue later with next_page_token=None
            state["next_page_token"] = None
    
        put_state(state)
        return {"ok": True, "written": total_written, "date": from_date}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  2. Vai a Configurazione > Variabili di ambiente > Modifica > Aggiungi nuova variabile di ambiente.

  3. Inserisci le seguenti variabili di ambiente, sostituendole con i tuoi valori:

    Chiave Valore di esempio
    S3_BUCKET zoom-operation-logs
    S3_PREFIX zoom/operationlogs/
    STATE_KEY zoom/operationlogs/state.json
    ZOOM_ACCOUNT_ID <your-zoom-account-id>
    ZOOM_CLIENT_ID <your-zoom-client-id>
    ZOOM_CLIENT_SECRET <your-zoom-client-secret>
    PAGE_SIZE 300
    TIMEOUT 30
  4. Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).

  5. Seleziona la scheda Configurazione.

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

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

Creare una pianificazione EventBridge

  1. Vai a Amazon EventBridge > Scheduler.
  2. Fai clic su Crea pianificazione.
  3. Fornisci i seguenti dettagli di configurazione:
    • Programma ricorrente: Tariffa (15 min).
    • Target: la tua funzione Lambda zoom_operationlogs_to_s3.
    • Nome: zoom-operationlogs-schedule-15min
  4. Fai clic su Crea pianificazione.

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

  1. Nella console AWS, vai a IAM > Utenti > Aggiungi utenti.
  2. Fai clic su Add users (Aggiungi utenti).
  3. Fornisci i seguenti dettagli di configurazione:
    • Utente: secops-reader.
    • Tipo di accesso: Chiave di accesso - Accesso programmatico.
  4. Fai clic su Crea utente.
  5. Collega la criterio per la lettura minima (personalizzata): Utenti > secops-reader > Autorizzazioni > Aggiungi autorizzazioni > Collega le norme direttamente > Crea norma.
  6. Nell'editor JSON, inserisci la seguente policy:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::zoom-operation-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::zoom-operation-logs"
        }
      ]
    }
    
  7. Imposta il nome su secops-reader-policy.

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

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

  10. Scarica il file CSV (questi valori vengono inseriti nel feed).

Configura un feed in Google SecOps per importare i log delle operazioni di Zoom

  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, Zoom Operation Logs).
  4. Seleziona Amazon S3 V2 come Tipo di origine.
  5. Seleziona Log delle operazioni di zoom come Tipo di log.
  6. Fai clic su Avanti.
  7. Specifica i valori per i seguenti parametri di input:
    • URI S3: s3://zoom-operation-logs/zoom/operationlogs/
    • Opzioni di eliminazione dell'origine: seleziona l'opzione di eliminazione in base alle tue preferenze.
    • Età massima del file: includi i file modificati nell'ultimo numero di giorni. Il valore predefinito è 180 giorni.
    • 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
azione metadata.product_event_type Il campo del log non elaborato "action" è mappato a questo campo UDM.
category_type additional.fields.key Il campo del log non elaborato "category_type" è mappato a questo campo UDM.
category_type additional.fields.value.string_value Il campo del log non elaborato "category_type" è mappato a questo campo UDM.
Reparto target.user.department Il campo dei log non elaborati "Department" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Descrizione target.user.role_description Il campo del log non elaborato "Description" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Nome visualizzato target.user.user_display_name Il campo del log non elaborato "Display Name" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Indirizzo email target.user.email_addresses Il campo del log non elaborato "Indirizzo email" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Nome target.user.first_name Il campo del log non elaborato "First Name" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Qualifica target.user.title Il campo del log non elaborato "Job Title" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Cognome target.user.last_name Il campo del log non elaborato "Last Name" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Località target.location.name Il campo del log non elaborato "Location" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
operation_detail metadata.description Il campo di log non elaborato "operation_detail" è mappato a questo campo UDM.
operatore principal.user.email_addresses Il campo del log non elaborato "operator" viene mappato a questo campo UDM se corrisponde a un'espressione regolare email.
operatore principal.user.userid Il campo del log non elaborato "operator" viene mappato a questo campo UDM se non corrisponde a un'espressione regolare email.
Room Name target.user.attribute.labels.value Il campo del log non elaborato "Room Name" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Nome ruolo target.user.attribute.roles.name Il campo dei log non elaborati "Nome ruolo" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
tempo metadata.event_timestamp.seconds Il campo log non elaborato "time" viene analizzato e mappato a questo campo UDM.
Tipo target.user.attribute.labels.value Il campo dei log non elaborati "Type" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Ruolo utente target.user.attribute.roles.name Il campo del log non elaborato "Ruolo utente" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
Tipo di utente target.user.attribute.labels.value Il campo del log non elaborato "Tipo utente" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
metadata.log_type A questo campo UDM è assegnato il valore "ZOOM_OPERATION_LOGS".
metadata.vendor_name A questo campo UDM è assegnato il valore "ZOOM".
metadata.product_name A questo campo UDM è assegnato il valore "ZOOM_OPERATION_LOGS".
metadata.event_type Il valore viene determinato in base alla seguente logica:
1. Se il campo "event_type" non è vuoto, viene utilizzato il suo valore.
2. Se i campi "operator", "email" o "email2" non sono vuoti, il valore viene impostato su "USER_UNCATEGORIZED".
3. In caso contrario, il valore viene impostato su "GENERIC_EVENT".
json_data about.user.attribute.labels.value Il campo dei log non elaborati "json_data" (estratto dal campo "operation_detail") viene analizzato come JSON. I campi "assistant" e "options" di ogni elemento dell'array JSON analizzato vengono mappati al campo "value" dell'array "labels" nell'UDM.
json_data about.user.userid Il campo dei log non elaborati "json_data" (estratto dal campo "operation_detail") viene analizzato come JSON. Il campo "userId" di ogni elemento dell'array JSON analizzato (tranne il primo) viene mappato al campo "userid" dell'oggetto "about.user" in UDM.
json_data target.user.attribute.labels.value Il campo dei log non elaborati "json_data" (estratto dal campo "operation_detail") viene analizzato come JSON. I campi "assistant" e "options" del primo elemento dell'array JSON analizzato vengono mappati al campo "value" dell'array "labels" in UDM.
json_data target.user.userid Il campo dei log non elaborati "json_data" (estratto dal campo "operation_detail") viene analizzato come JSON. Il campo "userId" del primo elemento dell'array JSON analizzato viene mappato al campo "userid" dell'oggetto "target.user" nell'UDM.
email target.user.email_addresses Il campo del log non elaborato "email" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
email2 target.user.email_addresses Il campo del log non elaborato "email2" (estratto dal campo "operation_detail") è mappato a questo campo UDM.
ruolo target.user.attribute.roles.name Il campo del log non elaborato "role" (estratto dal campo "operation_detail") è mappato a questo campo UDM.

Hai bisogno di ulteriore assistenza? Ricevi risposte dai membri della community e dai professionisti di Google SecOps.