Raccogliere i log di controllo di Slack

Supportato in:

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)

  1. Accedi alla Console di amministrazione di Slack.
  2. Vai su https://api.slack.com/apps e fai clic su Crea nuova app > Da zero.
  3. Inserisci un nome app univoco e seleziona il tuo spazio di lavoro Slack.
  4. Fai clic su Crea app.
  5. Vai a OAuth e autorizzazioni nella barra laterale sinistra.
  6. Vai alla sezione Ambiti e aggiungi il seguente ambito del token utente: auditlogs:read
  7. Fai clic su Installa in Workspace > Consenti.
  8. Una volta installata, vai a App a livello di organizzazione.
  9. Fai clic su Installa nell'organizzazione.
  10. Autorizza l'app con un account proprietario/amministratore dell'organizzazione.
  11. Copia e salva in modo sicuro il token OAuth utente che inizia con xoxp- (questo è il tuo SLACK_AUDIT_TOKEN).
  12. 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

  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, slack-audit-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": "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.
  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 SlackAuditToS3Role 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 slack_audit_to_s3
Tempo di esecuzione Python 3.13
Architettura x86_64
Ruolo di esecuzione SlackAuditToS3Role
  1. 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())
    
  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 slack-audit-logs
    S3_PREFIX slack/audit/
    STATE_KEY slack/audit/state.json
    SLACK_AUDIT_TOKEN xoxp-*** (token utente a livello di organizzazione con auditlogs: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
  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 > Crea pianificazione.
  2. Fornisci i seguenti dettagli di configurazione:
    • Programma ricorrente: Tariffa (1 hour).
    • Target: la tua funzione Lambda slack_audit_to_s3.
    • Nome: slack-audit-1h
  3. 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:::slack-audit-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::slack-audit-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 gli audit log di Slack

  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, Slack Audit Logs).
  4. Seleziona Amazon S3 V2 come Tipo di origine.
  5. Seleziona Audit di Slack come Tipo di log.
  6. Fai clic su Avanti.
  7. 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.
  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
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.