Raccogliere i log di controllo di Slack
Questo documento spiega come importare i log di controllo di Slack in Google Security Operations utilizzando Amazon S3. Il parser normalizza innanzitutto i valori booleani e cancella i campi predefiniti. Quindi, analizza il campo "message" come JSON, gestendo i messaggi non JSON eliminandoli. A seconda della presenza di campi specifici (date_create
e user_id
), il parser applica una logica diversa per mappare i campi dei log non elaborati all'UDM, incluse le informazioni su metadati, entità, rete, target e informazioni, e crea un risultato di sicurezza.
Prima di iniziare
Assicurati di soddisfare i seguenti prerequisiti:
- Istanza Google SecOps
- Accesso privilegiato al tenant Slack Enterprise Grid e alla Console di amministrazione
- Accesso con privilegi ad AWS (S3, IAM, Lambda, EventBridge)
Raccogli i prerequisiti di Slack (ID app, token OAuth, ID organizzazione)
- Accedi alla Console di amministrazione di Slack.
- Vai su https://api.slack.com/apps e fai clic su Crea nuova app > Da zero.
- Inserisci un nome app univoco e seleziona il tuo spazio di lavoro Slack.
- Fai clic su Crea app.
- Vai a OAuth e autorizzazioni nella barra laterale sinistra.
- Vai alla sezione Ambiti e aggiungi il seguente ambito del token utente: auditlogs:read
- Fai clic su Installa in Workspace > Consenti.
- Una volta installata, vai a App a livello di organizzazione.
- Fai clic su Installa nell'organizzazione.
- Autorizza l'app con un account proprietario/amministratore dell'organizzazione.
- Copia e salva in modo sicuro il token OAuth utente che inizia con
xoxp-
(questo è il tuo SLACK_AUDIT_TOKEN). - Prendi nota dell'ID organizzazione, che si trova nella Console di amministrazione di Slack in Impostazioni e permessi > Impostazioni dell'organizzazione.
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,
slack-audit-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": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::slack-audit-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::slack-audit-logs/slack/audit/state.json" } ] }
- Sostituisci
slack-audit-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
SlackAuditToS3Role
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 | slack_audit_to_s3 |
Tempo di esecuzione | Python 3.13 |
Architettura | x86_64 |
Ruolo di esecuzione | SlackAuditToS3Role |
Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e inserisci quanto segue (
slack_audit_to_s3.py
):#!/usr/bin/env python3 # Lambda: Pull Slack Audit Logs (Enterprise Grid) to S3 (no transform) import os, json, time, urllib.parse from urllib.request import Request, urlopen from urllib.error import HTTPError, URLError import boto3 BASE_URL = "https://api.slack.com/audit/v1/logs" TOKEN = os.environ["SLACK_AUDIT_TOKEN"] # org-level user token with auditlogs:read BUCKET = os.environ["S3_BUCKET"] PREFIX = os.environ.get("S3_PREFIX", "slack/audit/") STATE_KEY = os.environ.get("STATE_KEY", "slack/audit/state.json") LIMIT = int(os.environ.get("LIMIT", "200")) # Slack recommends <= 200 MAX_PAGES = int(os.environ.get("MAX_PAGES", "20")) LOOKBACK_SEC = int(os.environ.get("LOOKBACK_SECONDS", "3600")) # First-run window HTTP_TIMEOUT = int(os.environ.get("HTTP_TIMEOUT", "60")) HTTP_RETRIES = int(os.environ.get("HTTP_RETRIES", "3")) RETRY_AFTER_DEFAULT = int(os.environ.get("RETRY_AFTER_DEFAULT", "2")) # Optional server-side filters (comma-separated "action" values), empty means no filter ACTIONS = os.environ.get("ACTIONS", "").strip() s3 = boto3.client("s3") def _get_state() -> dict: try: obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY) st = json.loads(obj["Body"].read() or b"{}") return {"cursor": st.get("cursor")} except Exception: return {"cursor": None} def _put_state(state: dict) -> None: body = json.dumps(state, separators=(",", ":")).encode("utf-8") s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json") def _http_get(params: dict) -> dict: qs = urllib.parse.urlencode(params, doseq=True) url = f"{BASE_URL}?{qs}" if qs else BASE_URL req = Request(url, method="GET") req.add_header("Authorization", f"Bearer {TOKEN}") req.add_header("Accept", "application/json") attempt = 0 while True: try: with urlopen(req, timeout=HTTP_TIMEOUT) as r: return json.loads(r.read().decode("utf-8")) except HTTPError as e: # Respect Retry-After on 429/5xx if e.code in (429, 500, 502, 503, 504) and attempt < HTTP_RETRIES: retry_after = 0 try: retry_after = int(e.headers.get("Retry-After", RETRY_AFTER_DEFAULT)) except Exception: retry_after = RETRY_AFTER_DEFAULT time.sleep(max(1, retry_after)) attempt += 1 continue # Re-raise other HTTP errors raise except URLError: if attempt < HTTP_RETRIES: time.sleep(RETRY_AFTER_DEFAULT) attempt += 1 continue raise def _write_page(payload: dict, page_idx: int) -> str: ts = time.strftime("%Y/%m/%d/%H%M%S", time.gmtime()) key = f"{PREFIX}/{ts}-slack-audit-p{page_idx:05d}.json" body = json.dumps(payload, separators=(",", ":")).encode("utf-8") s3.put_object(Bucket=BUCKET, Key=key, Body=body, ContentType="application/json") return key def lambda_handler(event=None, context=None): state = _get_state() cursor = state.get("cursor") params = {"limit": LIMIT} if ACTIONS: params["action"] = [a.strip() for a in ACTIONS.split(",") if a.strip()] if cursor: params["cursor"] = cursor else: # First run (or reset): fetch a recent window by time params["oldest"] = int(time.time()) - LOOKBACK_SEC pages = 0 total = 0 last_cursor = None while pages < MAX_PAGES: data = _http_get(params) _write_page(data, pages) entries = data.get("entries") or [] total += len(entries) # Cursor for next page meta = data.get("response_metadata") or {} next_cursor = meta.get("next_cursor") or data.get("next_cursor") if next_cursor: params = {"limit": LIMIT, "cursor": next_cursor} if ACTIONS: params["action"] = [a.strip() for a in ACTIONS.split(",") if a.strip()] last_cursor = next_cursor pages += 1 continue break if last_cursor: _put_state({"cursor": last_cursor}) return {"ok": True, "pages": pages + (1 if total or last_cursor else 0), "entries": total, "cursor": last_cursor} 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
slack-audit-logs
S3_PREFIX
slack/audit/
STATE_KEY
slack/audit/state.json
SLACK_AUDIT_TOKEN
xoxp-***
(token utente a livello di organizzazione conauditlogs:read
)LIMIT
200
MAX_PAGES
20
LOOKBACK_SECONDS
3600
HTTP_TIMEOUT
60
HTTP_RETRIES
3
RETRY_AFTER_DEFAULT
2
ACTIONS
(facoltativo, CSV) user_login,app_installed
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
). - Target: la tua funzione Lambda
slack_audit_to_s3
. - Nome:
slack-audit-1h
- 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:::slack-audit-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::slack-audit-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 gli audit log di Slack
- Vai a Impostazioni SIEM > Feed.
- Fai clic su + Aggiungi nuovo feed.
- Nel campo Nome feed, inserisci un nome per il feed (ad esempio,
Slack Audit Logs
). - Seleziona Amazon S3 V2 come Tipo di origine.
- Seleziona Audit di Slack come Tipo di log.
- Fai clic su Avanti.
- Specifica i valori per i seguenti parametri di input:
- URI S3:
s3://slack-audit-logs/slack/audit/
- 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 |
---|---|---|
action |
metadata.product_event_type |
Mappato direttamente dal campo action nel log non elaborato. |
actor.type |
principal.labels.value |
Mappato direttamente dal campo actor.type , con l'aggiunta della chiave actor.type . |
actor.user.email |
principal.user.email_addresses |
Mappato direttamente dal campo actor.user.email . |
actor.user.id |
principal.user.product_object_id |
Mappato direttamente dal campo actor.user.id . |
actor.user.id |
principal.user.userid |
Mappato direttamente dal campo actor.user.id . |
actor.user.name |
principal.user.user_display_name |
Mappato direttamente dal campo actor.user.name . |
actor.user.team |
principal.user.group_identifiers |
Mappato direttamente dal campo actor.user.team . |
context.ip_address |
principal.ip |
Mappato direttamente dal campo context.ip_address . |
context.location.domain |
about.resource.attribute.labels.value |
Mappato direttamente dal campo context.location.domain , con l'aggiunta della chiave context.location.domain . |
context.location.id |
about.resource.id |
Mappato direttamente dal campo context.location.id . |
context.location.name |
about.resource.name |
Mappato direttamente dal campo context.location.name . |
context.location.name |
about.resource.attribute.labels.value |
Mappato direttamente dal campo context.location.name , con l'aggiunta della chiave context.location.name . |
context.location.type |
about.resource.resource_subtype |
Mappato direttamente dal campo context.location.type . |
context.session_id |
network.session_id |
Mappato direttamente dal campo context.session_id . |
context.ua |
network.http.user_agent |
Mappato direttamente dal campo context.ua . |
context.ua |
network.http.parsed_user_agent |
Informazioni sull'user agent analizzate derivate dal campo context.ua utilizzando il filtro parseduseragent . |
country |
principal.location.country_or_region |
Mappato direttamente dal campo country . |
date_create |
metadata.event_timestamp.seconds |
Il timestamp Unix del campo date_create viene convertito in un oggetto timestamp. |
details.inviter.email |
target.user.email_addresses |
Mappato direttamente dal campo details.inviter.email . |
details.inviter.id |
target.user.product_object_id |
Mappato direttamente dal campo details.inviter.id . |
details.inviter.name |
target.user.user_display_name |
Mappato direttamente dal campo details.inviter.name . |
details.inviter.team |
target.user.group_identifiers |
Mappato direttamente dal campo details.inviter.team . |
details.reason |
security_result.description |
Mappato direttamente dal campo details.reason o, se si tratta di un array, concatenato con virgole. |
details.type |
about.resource.attribute.labels.value |
Mappato direttamente dal campo details.type , con l'aggiunta della chiave details.type . |
details.type |
security_result.summary |
Mappato direttamente dal campo details.type . |
entity.app.id |
target.resource.id |
Mappato direttamente dal campo entity.app.id . |
entity.app.name |
target.resource.name |
Mappato direttamente dal campo entity.app.name . |
entity.channel.id |
target.resource.id |
Mappato direttamente dal campo entity.channel.id . |
entity.channel.name |
target.resource.name |
Mappato direttamente dal campo entity.channel.name . |
entity.channel.privacy |
target.resource.attribute.labels.value |
Mappato direttamente dal campo entity.channel.privacy , con l'aggiunta della chiave entity.channel.privacy . |
entity.file.filetype |
target.resource.attribute.labels.value |
Mappato direttamente dal campo entity.file.filetype , con l'aggiunta della chiave entity.file.filetype . |
entity.file.id |
target.resource.id |
Mappato direttamente dal campo entity.file.id . |
entity.file.name |
target.resource.name |
Mappato direttamente dal campo entity.file.name . |
entity.file.title |
target.resource.attribute.labels.value |
Mappato direttamente dal campo entity.file.title , con l'aggiunta della chiave entity.file.title . |
entity.huddle.date_end |
about.resource.attribute.labels.value |
Mappato direttamente dal campo entity.huddle.date_end , con l'aggiunta della chiave entity.huddle.date_end . |
entity.huddle.date_start |
about.resource.attribute.labels.value |
Mappato direttamente dal campo entity.huddle.date_start , con l'aggiunta della chiave entity.huddle.date_start . |
entity.huddle.id |
about.resource.attribute.labels.value |
Mappato direttamente dal campo entity.huddle.id , con l'aggiunta della chiave entity.huddle.id . |
entity.huddle.participants.0 |
about.resource.attribute.labels.value |
Mappato direttamente dal campo entity.huddle.participants.0 , con l'aggiunta della chiave entity.huddle.participants.0 . |
entity.huddle.participants.1 |
about.resource.attribute.labels.value |
Mappato direttamente dal campo entity.huddle.participants.1 , con l'aggiunta della chiave entity.huddle.participants.1 . |
entity.type |
target.resource.resource_subtype |
Mappato direttamente dal campo entity.type . |
entity.user.email |
target.user.email_addresses |
Mappato direttamente dal campo entity.user.email . |
entity.user.id |
target.user.product_object_id |
Mappato direttamente dal campo entity.user.id . |
entity.user.name |
target.user.user_display_name |
Mappato direttamente dal campo entity.user.name . |
entity.user.team |
target.user.group_identifiers |
Mappato direttamente dal campo entity.user.team . |
entity.workflow.id |
target.resource.id |
Mappato direttamente dal campo entity.workflow.id . |
entity.workflow.name |
target.resource.name |
Mappato direttamente dal campo entity.workflow.name . |
id |
metadata.product_log_id |
Mappato direttamente dal campo id . |
ip |
principal.ip |
Mappato direttamente dal campo ip . Determinato dalla logica in base al campo action . Il valore predefinito è USER_COMMUNICATION , ma cambia in altri valori come USER_CREATION , USER_LOGIN , USER_LOGOUT , USER_RESOURCE_ACCESS , USER_RESOURCE_UPDATE_PERMISSIONS o USER_CHANGE_PERMISSIONS in base al valore di action . Codificato in modo permanente su "SLACK_AUDIT". Imposta "Enterprise Grid" se esiste date_create , altrimenti imposta "Log di controllo" se esiste user_id . Codificato in modo permanente su "Slack". Codificato come "REMOTE". Imposta "SSO" se action contiene "user_login" o "user_logout". In caso contrario, imposta "MACHINE". Non mappato negli esempi forniti. Il valore predefinito è "ALLOW", ma è impostato su "BLOCK" se action è "user_login_failed". Imposta "Slack" se esiste date_create , altrimenti imposta "SLACK" se esiste user_id . |
user_agent |
network.http.user_agent |
Mappato direttamente dal campo user_agent . |
user_id |
principal.user.product_object_id |
Mappato direttamente dal campo user_id . |
username |
principal.user.product_object_id |
Mappato direttamente dal campo username . |
Hai bisogno di ulteriore assistenza? Ricevi risposte dai membri della community e dai professionisti di Google SecOps.