Raccogliere i log delle operazioni di Zoom
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)
- Accedi a Zoom App Marketplace.
- Vai a Sviluppa > Crea app > OAuth da server a server.
- Crea l'app e aggiungi il seguente ambito:
report:read:operation_logs:admin
(oreport:read:admin
). - 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
- 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,
zoom-operation-logs
). - 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
- Nella console AWS, vai a IAM > Policy > Crea policy > scheda JSON.
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.
- Sostituisci
Fai clic su Avanti > Crea criterio.
Vai a IAM > Ruoli > Crea ruolo > Servizio AWS > Lambda.
Allega il criterio appena creato.
Assegna al ruolo il nome
WriteZoomOperationLogsToS3Role
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 | zoom_operationlogs_to_s3 |
Tempo di esecuzione | Python 3.13 |
Architettura | x86_64 |
Ruolo di esecuzione | WriteZoomOperationLogsToS3Role |
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())
Vai a Configurazione > Variabili di ambiente > Modifica > Aggiungi nuova variabile di ambiente.
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
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.
- Fai clic su Crea pianificazione.
- 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
- Programma ricorrente: Tariffa (
- Fai clic su Crea pianificazione.
(Facoltativo) Crea chiavi e utenti IAM di sola lettura per Google SecOps
- Nella console AWS, vai a IAM > Utenti > Aggiungi utenti.
- Fai clic su Add users (Aggiungi utenti).
- Fornisci i seguenti dettagli di configurazione:
- Utente:
secops-reader
. - Tipo di accesso: Chiave di accesso - Accesso programmatico.
- Utente:
- Fai clic su Crea utente.
- Collega la criterio per la lettura minima (personalizzata): Utenti > secops-reader > Autorizzazioni > Aggiungi autorizzazioni > Collega le norme direttamente > Crea norma.
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" } ] }
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 log delle operazioni di Zoom
- Vai a Impostazioni SIEM > Feed.
- Fai clic su + Aggiungi nuovo feed.
- Nel campo Nome feed, inserisci un nome per il feed (ad esempio,
Zoom Operation Logs
). - Seleziona Amazon S3 V2 come Tipo di origine.
- Seleziona Log delle operazioni di zoom come Tipo di log.
- Fai clic su Avanti.
- 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.
- 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 |
---|---|---|
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. |
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.