Raccogliere i log di Sentry

Supportato in:

Questo documento spiega come importare i log di Sentry in Google Security Operations utilizzando Amazon S3. Sentry produce dati operativi sotto forma di eventi, problemi, dati di monitoraggio delle prestazioni e informazioni di monitoraggio degli errori. Questa integrazione ti consente di inviare questi log a Google SecOps per l'analisi e il monitoraggio, fornendo visibilità su errori delle applicazioni, problemi di prestazioni e interazioni degli utenti all'interno delle applicazioni monitorate da Sentry.

Prima di iniziare

Assicurati di soddisfare i seguenti prerequisiti:

  • Un'istanza Google SecOps.
  • Accesso con privilegi al tenant Sentry (token di autenticazione con ambiti API).
  • Accesso privilegiato ad AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).

Raccogli i prerequisiti di Sentry (ID, chiavi API, ID organizzazione, token)

  1. Accedi a Sentry.
  2. Trova lo slug dell'organizzazione:
    • Vai a Impostazioni > Organizzazione > Impostazioni > ID organizzazione (lo slug viene visualizzato accanto al nome dell'organizzazione).
  3. Crea un token di autenticazione:
    • Vai a Impostazioni > Impostazioni sviluppatore > Token personali.
    • Fai clic su Crea nuovo.
    • Ambiti (minimo): org:read, project:read, event:read.
    • Copia il valore del token (mostrato una sola volta). Viene utilizzato come: Authorization: Bearer <token>.
  4. (Se è self-hosted): annota l'URL di base (ad esempio, https://<your-domain>); altrimenti utilizza https://sentry.io.

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, sentry-logs).
  3. Crea un utente seguendo questa guida utente: 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 riferimento futuro.
  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 i criteri AmazonS3FullAccess.
  18. Seleziona la policy.
  19. Fai clic su Avanti.
  20. Fai clic su Aggiungi autorizzazioni.

Configura il ruolo e il criterio IAM per i caricamenti S3

  1. Nella console AWS, vai a IAM > Policy.
  2. Fai clic su Crea criterio > scheda JSON.
  3. Copia e incolla i seguenti criteri.
  4. JSON delle policy (sostituisci sentry-logs se hai inserito un nome del bucket diverso):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::sentry-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::sentry-logs/sentry/events/state.json"
        }
      ]
    }
    
  5. Fai clic su Avanti > Crea criterio.

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

  7. Allega il criterio appena creato.

  8. Assegna al ruolo il nome WriteSentryToS3Role 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 sentry_to_s3
    Tempo di esecuzione Python 3.13
    Architettura x86_64
    Ruolo di esecuzione WriteSentryToS3Role
  4. Dopo aver creato la funzione, apri la scheda Codice, elimina lo stub e incolla il seguente codice (sentry_to_s3.py).

    #!/usr/bin/env python3
    # Lambda: Pull Sentry project events (raw JSON) to S3 using Link "previous" cursor for duplicate-safe polling
    
    import os, json, time
    from urllib.request import Request, urlopen
    from urllib.parse import urlencode, urlparse, parse_qs
    import boto3
    
    ORG = os.environ["SENTRY_ORG"].strip()
    TOKEN = os.environ["SENTRY_AUTH_TOKEN"].strip()
    S3_BUCKET = os.environ["S3_BUCKET"]
    S3_PREFIX = os.environ.get("S3_PREFIX", "sentry/events/")
    STATE_KEY = os.environ.get("STATE_KEY", "sentry/events/state.json")
    BASE = os.environ.get("SENTRY_API_BASE", "https://sentry.io").rstrip("/")
    MAX_PROJECTS = int(os.environ.get("MAX_PROJECTS", "100"))
    MAX_PAGES_PER_PROJECT = int(os.environ.get("MAX_PAGES_PER_PROJECT", "5"))
    
    s3 = boto3.client("s3")
    HDRS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json", "User-Agent": "chronicle-s3-sentry-lambda/1.0"}
    
    def _get_state() -> dict:
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            raw = obj["Body"].read()
            return json.loads(raw) if raw else {"projects": {}}
        except Exception:
            return {"projects": {}}
    
    def _put_state(state: dict):
        s3.put_object(Bucket=S3_BUCKET, Key=STATE_KEY, Body=json.dumps(state, separators=(",", ":")).encode("utf-8"))
    
    def _req(path: str, params: dict | None = None):
        url = f"{BASE}{path}"
        if params:
            url = f"{url}?{urlencode(params)}"
        req = Request(url, method="GET", headers=HDRS)
        with urlopen(req, timeout=60) as r:
            data = json.loads(r.read().decode("utf-8"))
            link = r.headers.get("Link")
            return data, link
    
    def _parse_link(link_header: str | None):
        """Return (prev_cursor, prev_has_more, next_cursor, next_has_more)."""
        if not link_header:
            return None, False, None, False
        prev_cursor, next_cursor = None, None
        prev_more, next_more = False, False
        parts = [p.strip() for p in link_header.split(",")]
        for p in parts:
            if "<" not in p or ">" not in p:
                continue
            url = p.split("<", 1)[1].split(">", 1)[0]
            rel = "previous" if 'rel="previous"' in p else ("next" if 'rel="next"' in p else None)
            has_more = 'results="true"' in p
            try:
                q = urlparse(url).query
                cur = parse_qs(q).get("cursor", [None])[0]
            except Exception:
                cur = None
            if rel == "previous":
                prev_cursor, prev_more = cur, has_more
            elif rel == "next":
                next_cursor, next_more = cur, has_more
        return prev_cursor, prev_more, next_cursor, next_more
    
    def _write_page(project_slug: str, payload: object, page_idx: int) -> str:
        ts = time.gmtime()
        key = f"{S3_PREFIX.rstrip('/')}/{time.strftime('%Y/%m/%d', ts)}/sentry-{project_slug}-{page_idx:05d}.json"
        s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(payload, separators=(",", ":")).encode("utf-8"))
        return key
    
    def list_projects(max_projects: int):
        projects, cursor = [], None
        while len(projects) < max_projects:
            params = {"cursor": cursor} if cursor else {}
            data, link = _req(f"/api/0/organizations/{ORG}/projects/", params)
            for p in data:
                slug = p.get("slug")
                if slug:
                    projects.append(slug)
                    if len(projects) >= max_projects:
                        break
            # advance pagination
            _, _, next_cursor, next_more = _parse_link(link)
            cursor = next_cursor if next_more else None
            if not next_more:
                break
        return projects
    
    def fetch_project_events(project_slug: str, start_prev_cursor: str | None):
        # If we have a stored "previous" cursor, poll forward (newer) until no more results.
        # If not (first run), fetch the latest page, then optionally follow "next" (older) for initial backfill up to the limit.
        pages = 0
        total = 0
        latest_prev_cursor_to_store = None
    
        def _one(cursor: str | None):
            nonlocal pages, total, latest_prev_cursor_to_store
            params = {"cursor": cursor} if cursor else {}
            data, link = _req(f"/api/0/projects/{ORG}/{project_slug}/events/", params)
            _write_page(project_slug, data, pages)
            total += len(data) if isinstance(data, list) else 0
            prev_c, prev_more, next_c, next_more = _parse_link(link)
            # capture the most recent "previous" cursor observed to store for the next run
            latest_prev_cursor_to_store = prev_c or latest_prev_cursor_to_store
            pages += 1
            return prev_c, prev_more, next_c, next_more
    
        if start_prev_cursor:
            # Poll new pages toward "previous" until no more
            cur = start_prev_cursor
            while pages < MAX_PAGES_PER_PROJECT:
                prev_c, prev_more, _, _ = _one(cur)
                if not prev_more:
                    break
                cur = prev_c
        else:
            # First run: start at newest, then (optionally) backfill a few older pages
            prev_c, _, next_c, next_more = _one(None)
            cur = next_c
            while next_more and pages < MAX_PAGES_PER_PROJECT:
                _, _, next_c, next_more = _one(cur)
                cur = next_c
    
        return {"project": project_slug, "pages": pages, "written": total, "store_prev_cursor": latest_prev_cursor_to_store}
    
    def lambda_handler(event=None, context=None):
        state = _get_state()
        state.setdefault("projects", {})
    
        projects = list_projects(MAX_PROJECTS)
        summary = []
        for slug in projects:
            start_prev = state["projects"].get(slug, {}).get("prev_cursor")
            res = fetch_project_events(slug, start_prev)
            if res.get("store_prev_cursor"):
                state["projects"][slug] = {"prev_cursor": res["store_prev_cursor"]}
            summary.append(res)
    
        _put_state(state)
        return {"ok": True, "projects": len(projects), "summary": summary}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  5. Vai a Configurazione > Variabili di ambiente.

  6. Fai clic su Modifica > Aggiungi nuova variabile di ambiente.

  7. Inserisci le variabili di ambiente fornite nella tabella seguente, sostituendo i valori di esempio con i tuoi valori.

    Variabili di ambiente

    Chiave Valore di esempio Descrizione
    S3_BUCKET sentry-logs Nome del bucket S3 in cui verranno archiviati i dati.
    S3_PREFIX sentry/events/ Prefisso S3 (sottocartella) facoltativo per gli oggetti.
    STATE_KEY sentry/events/state.json Chiave facoltativa del file di stato/checkpoint.
    SENTRY_ORG your-org-slug Slug dell'organizzazione Sentry.
    SENTRY_AUTH_TOKEN sntrys_************************ Token di autenticazione Sentry con org:read, project:read, event:read.
    SENTRY_API_BASE https://sentry.io URL di base dell'API Sentry (self-hosted: https://<your-domain>).
    MAX_PROJECTS 100 Numero massimo di progetti da elaborare.
    MAX_PAGES_PER_PROJECT 5 Numero massimo di pagine per progetto per esecuzione.
  8. Dopo aver creato la funzione, rimani sulla relativa pagina (o apri Lambda > Funzioni > la tua funzione).

  9. Seleziona la scheda Configurazione.

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

  11. Modifica Timeout impostando 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 sentry_to_s3.
    • Nome: sentry-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.
  2. Fai clic su Add users (Aggiungi utenti).
  3. Fornisci i seguenti dettagli di configurazione:
    • Utente: inserisci secops-reader.
    • Tipo di accesso: seleziona 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. JSON:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::sentry-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::sentry-logs"
        }
      ]
    }
    
  7. Name = secops-reader-policy.

  8. Fai clic su Crea criterio > cerca/seleziona > Avanti > Aggiungi autorizzazioni.

  9. Crea la chiave di accesso per secops-reader: Credenziali di sicurezza > Chiavi di accesso.

  10. Fai clic su Crea chiave di accesso.

  11. Scarica il .CSV. Incollerai questi valori nel feed.

Configura un feed in Google SecOps per importare i log di Sentry

  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, Sentry Logs).
  4. Seleziona Amazon S3 V2 come Tipo di origine.
  5. Seleziona Sentry come Tipo di log.
  6. Fai clic su Avanti.
  7. Specifica i valori per i seguenti parametri di input:
    • URI S3: s3://sentry-logs/sentry/events/
    • 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.

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