Collecter les journaux Zendesk CRM

Compatible avec :

Ce document explique comment ingérer des journaux Zendesk Customer Relationship Management (CRM) dans Google Security Operations à l'aide d'Amazon S3.

Avant de commencer

Assurez-vous de remplir les conditions suivantes :

  • Une instance Google SecOps.
  • Accès privilégié à Zendesk.
  • Accès privilégié à AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).

Obtenir les prérequis Zendesk

  1. Confirmer le forfait et le rôle
    1. Vous devez être un administrateur Zendesk pour créer des jetons d'API ou des clients OAuth. L'API Audit Logs n'est disponible que dans le forfait Enterprise. (Si votre compte n'est pas un compte Enterprise, ignorez audit_logs dans RESOURCES.)
  2. Activer l'accès aux jetons d'API (une seule fois)
    1. Dans la console d'administration, accédez à Applications et intégrations > API > Configuration de l'API.
    2. Activez l'option Autoriser l'accès au jeton de l'API.
  3. Générer un jeton d'API (pour l'authentification de base)
    1. Accédez à Applications et intégrations> API> Jetons d'API.
    2. Cliquez sur Ajouter un jeton d'API > (facultatif) ajoutez une description > Enregistrer.
    3. Copiez et enregistrez le jeton d'API maintenant (vous ne pourrez plus le voir).
    4. Enregistrez l'adresse e-mail de l'administrateur qui s'authentifiera avec ce jeton.
      • Format d'authentification de base utilisé par Lambda : email_address/token:api_token
  4. (Facultatif) Créez un client OAuth (pour l'authentification par jeton au lieu du jeton d'API).
    1. Accédez à Applications et intégrations> API> Clients OAuth > Ajouter un client OAuth.
    2. Renseignez les champs Nom, Identifiant unique (automatique), URL de redirection (peut être un espace réservé si vous n'émettez des jetons qu'avec l'API), puis cliquez sur Enregistrer.
    3. Créez un jeton d'accès pour l'intégration et accordez les scopes minimaux requis par ce guide :
      • tickets:read (pour les billets incrémentaux)
      • auditlogs:read (pour les journaux d'audit ; Enterprise uniquement)
      • En cas de doute, read fonctionne également pour l'accès en lecture seule.
    4. Copiez le jeton d'accès (collez-le dans ZENDESK_BEARER_TOKEN) et enregistrez l'ID/code secret du client de manière sécurisée (pour les futurs flux d'actualisation des jetons).
  5. Enregistrez votre URL de base Zendesk.

    • Utilisez https://<your_subdomain>.zendesk.com (collez-le dans la variable d'environnement ZENDESK_BASE_URL).

    Contenus à copier et à enregistrer pour plus tard

    • URL de base (par exemple, https://acme.zendesk.com)
    • Adresse e-mail de l'utilisateur administrateur (pour l'authentification par jeton API)
    • Jeton d'API (si vous utilisez AUTH_MODE=token)
    • ou jeton d'accès OAuth (si vous utilisez AUTH_MODE=bearer)
    • (Facultatif) ID/code secret du client OAuth pour la gestion du cycle de vie

Configurer un bucket AWS S3 et IAM pour Google SecOps

  1. Créez un bucket Amazon S3 en suivant ce guide de l'utilisateur : Créer un bucket.
  2. Enregistrez le Nom et la Région du bucket pour référence ultérieure (par exemple, zendesk-crm-logs).
  3. Créez un utilisateur en suivant ce guide de l'utilisateur : Créer un utilisateur IAM.
  4. Sélectionnez l'utilisateur créé.
  5. Sélectionnez l'onglet Informations d'identification de sécurité.
  6. Cliquez sur Créer une clé d'accès dans la section Clés d'accès.
  7. Sélectionnez Service tiers comme Cas d'utilisation.
  8. Cliquez sur Suivant.
  9. Facultatif : Ajoutez un tag de description.
  10. Cliquez sur Créer une clé d'accès.
  11. Cliquez sur Télécharger le fichier CSV pour enregistrer la clé d'accès et la clé d'accès secrète pour référence ultérieure.
  12. Cliquez sur OK.
  13. Sélectionnez l'onglet Autorisations.
  14. Cliquez sur Ajouter des autorisations dans la section Règles relatives aux autorisations.
  15. Sélectionnez Ajouter des autorisations.
  16. Sélectionnez Joindre directement des règles.
  17. Recherchez la règle AmazonS3FullAccess.
  18. Sélectionnez la règle.
  19. Cliquez sur Suivant.
  20. Cliquez sur Ajouter des autorisations.

Configurer la stratégie et le rôle IAM pour les importations S3

  1. Dans la console AWS, accédez à IAM > Stratégies.
  2. Cliquez sur Créer une règle > onglet JSON.
  3. Copiez et collez le règlement suivant.
  4. JSON de la règle (remplacez zendesk-crm-logs si vous avez saisi un autre nom de bucket) :

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::zendesk-crm-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::zendesk-crm-logs/zendesk/crm/state.json"
        }
      ]
    }
    
  5. Cliquez sur Suivant > Créer une règle.

  6. Accédez à IAM > Rôles > Créer un rôle > Service AWS > Lambda.

  7. Associez la règle que vous venez de créer.

  8. Nommez le rôle ZendeskCRMToS3Role, puis cliquez sur Créer un rôle.

Créer la fonction Lambda

  1. Dans la console AWS, accédez à Lambda > Fonctions > Créer une fonction.
  2. Cliquez sur Créer à partir de zéro.
  3. Fournissez les informations de configuration suivantes :

    Paramètre Valeur
    Nom zendesk_crm_to_s3
    Durée d'exécution Python 3.13
    Architecture x86_64
    Rôle d'exécution ZendeskCRMToS3Role
  4. Une fois la fonction créée, ouvrez l'onglet Code, supprimez le stub et collez le code suivant (zendesk_crm_to_s3.py).

    #!/usr/bin/env python3
    
    import os, json, time, base64
    from urllib.request import Request, urlopen
    from urllib.error import HTTPError, URLError
    import boto3
    
    S3_BUCKET    = os.environ["S3_BUCKET"]
    S3_PREFIX    = os.environ.get("S3_PREFIX", "zendesk/crm/")
    STATE_KEY    = os.environ.get("STATE_KEY", "zendesk/crm/state.json")
    BASE_URL     = os.environ["ZENDESK_BASE_URL"].rstrip("/")  # e.g. https://your_subdomain.zendesk.com
    AUTH_MODE    = os.environ.get("AUTH_MODE", "token").lower()  # token|bearer
    EMAIL        = os.environ.get("ZENDESK_EMAIL", "")
    API_TOKEN    = os.environ.get("ZENDESK_API_TOKEN", "")
    BEARER       = os.environ.get("ZENDESK_BEARER_TOKEN", "")
    RESOURCES    = [r.strip() for r in os.environ.get("RESOURCES", "audit_logs,incremental_tickets").split(",") if r.strip()]
    MAX_PAGES    = int(os.environ.get("MAX_PAGES", "20"))
    LOOKBACK     = int(os.environ.get("LOOKBACK_SECONDS", "3600"))  # 1h default
    HTTP_TIMEOUT = int(os.environ.get("HTTP_TIMEOUT", "60"))
    HTTP_RETRIES = int(os.environ.get("HTTP_RETRIES", "3"))
    
    s3 = boto3.client("s3")
    
    def _headers() -> dict:
        if AUTH_MODE == "bearer" and BEARER:
            return {"Authorization": f"Bearer {BEARER}", "Accept": "application/json"}
        if AUTH_MODE == "token" and EMAIL and API_TOKEN:
            token = base64.b64encode(f"{EMAIL}/token:{API_TOKEN}".encode()).decode()
            return {"Authorization": f"Basic {token}", "Accept": "application/json"}
        raise RuntimeError("Invalid auth settings: provide token (EMAIL + API_TOKEN) or BEARER")
    
    def _get_state() -> dict:
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            b = obj["Body"].read()
            return json.loads(b) if b else {"audit_logs": {}, "incremental_tickets": {}}
        except Exception:
            return {"audit_logs": {}, "incremental_tickets": {}}
    
    def _put_state(st: dict) -> None:
        s3.put_object(
            Bucket=S3_BUCKET, Key=STATE_KEY,
            Body=json.dumps(st, separators=(",", ":")).encode("utf-8"),
            ContentType="application/json",
        )
    
    def _http_get_json(url: str) -> dict:
        attempt = 0
        while True:
            try:
                req = Request(url, method="GET")
                for k, v in _headers().items():
                    req.add_header(k, v)
                with urlopen(req, timeout=HTTP_TIMEOUT) as r:
                    return json.loads(r.read().decode("utf-8"))
            except HTTPError as e:
                if e.code in (429, 500, 502, 503, 504) and attempt < HTTP_RETRIES:
                    ra = 1 + attempt
                    try:
                        ra = int(e.headers.get("Retry-After", ra))
                    except Exception:
                        pass
                    time.sleep(max(1, ra))
                    attempt += 1
                    continue
                raise
            except URLError:
                if attempt < HTTP_RETRIES:
                    time.sleep(1 + attempt)
                    attempt += 1
                    continue
                raise
    
    def _put_page(payload: dict, resource: str) -> str:
        ts = time.gmtime()
        key = f"{S3_PREFIX}/{time.strftime('%Y/%m/%d/%H%M%S', ts)}-zendesk-{resource}.json"
        s3.put_object(
            Bucket=S3_BUCKET, Key=key,
            Body=json.dumps(payload, separators=(",", ":")).encode("utf-8"),
            ContentType="application/json",
        )
        return key
    
    def fetch_audit_logs(state: dict):
        """GET /api/v2/audit_logs.json with pagination via `next_page` (Zendesk)."""
        next_url = state.get("next_url") or f"{BASE_URL}/api/v2/audit_logs.json?page=1"
        pages = 0
        written = 0
        last_next = None
        while pages < MAX_PAGES and next_url:
            data = _http_get_json(next_url)
            _put_page(data, "audit_logs")
            written += len(data.get("audit_logs", []))
            last_next = data.get("next_page")
            next_url = last_next
            pages += 1
        return {"resource": "audit_logs", "pages": pages, "written": written, "next_url": last_next}
    
    def fetch_incremental_tickets(state: dict):
        """Cursor-based incremental export: /api/v2/incremental/tickets/cursor.json (pagination via `links.next`)."""
        next_link = state.get("next")
        if not next_link:
            start = int(time.time()) - LOOKBACK
            next_link = f"{BASE_URL}/api/v2/incremental/tickets/cursor.json?start_time={start}"
        pages = 0
        written = 0
        last_next = None
        while pages < MAX_PAGES and next_link:
            data = _http_get_json(next_link)
            _put_page(data, "incremental_tickets")
            written += len(data.get("tickets", []))
            links = data.get("links") or {}
            next_link = links.get("next")
            last_next = next_link
            pages += 1
        return {"resource": "incremental_tickets", "pages": pages, "written": written, "next": last_next}
    
    def lambda_handler(event=None, context=None):
        state = _get_state()
        summary = []
    
        if "audit_logs" in RESOURCES:
            res = fetch_audit_logs(state.get("audit_logs", {}))
            state["audit_logs"] = {"next_url": res.get("next_url")}
            summary.append(res)
    
        if "incremental_tickets" in RESOURCES:
            res = fetch_incremental_tickets(state.get("incremental_tickets", {}))
            state["incremental_tickets"] = {"next": res.get("next")}
            summary.append(res)
    
        _put_state(state)
        return {"ok": True, "summary": summary}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
  5. Accédez à Configuration > Variables d'environnement.

  6. Cliquez sur Modifier > Ajouter une variable d'environnement.

  7. Saisissez les variables d'environnement fournies dans le tableau suivant, en remplaçant les exemples de valeurs par les vôtres.

    Variables d'environnement

    Clé Exemple de valeur
    S3_BUCKET zendesk-crm-logs
    S3_PREFIX zendesk/crm/
    STATE_KEY zendesk/crm/state.json
    ZENDESK_BASE_URL https://your_subdomain.zendesk.com
    AUTH_MODE token
    ZENDESK_EMAIL analyst@example.com
    ZENDESK_API_TOKEN <api_token>
    ZENDESK_BEARER_TOKEN <leave empty unless using OAuth bearer>
    RESOURCES audit_logs,incremental_tickets
    MAX_PAGES 20
    LOOKBACK_SECONDS 3600
    HTTP_TIMEOUT 60
  8. Une fois la fonction créée, restez sur sa page (ou ouvrez Lambda > Fonctions > votre-fonction).

  9. Accédez à l'onglet Configuration.

  10. Dans le panneau Configuration générale, cliquez sur Modifier.

  11. Définissez le délai avant expiration sur 5 minutes (300 secondes), puis cliquez sur Enregistrer.

Créer une programmation EventBridge

  1. Accédez à Amazon EventBridge> Scheduler> Create schedule.
  2. Fournissez les informations de configuration suivantes :
    • Planning récurrent : Tarif (1 hour).
    • Cible : votre fonction Lambda zendesk_crm_to_s3.
    • Nom : zendesk_crm_to_s3-1h.
  3. Cliquez sur Créer la programmation.

(Facultatif) Créez un utilisateur et des clés IAM en lecture seule pour Google SecOps

  1. Accédez à la console AWS> IAM> Utilisateurs> Ajouter des utilisateurs.
  2. Cliquez sur Add users (Ajouter des utilisateurs).
  3. Fournissez les informations de configuration suivantes :
    • Utilisateur : saisissez secops-reader.
    • Type d'accès : sélectionnez Clé d'accès – Accès programmatique.
  4. Cliquez sur Créer un utilisateur.
  5. Associez une stratégie de lecture minimale (personnalisée) : Utilisateurs > secops-reader > Autorisations > Ajouter des autorisations > Associer des stratégies directement > Créer une stratégie.
  6. JSON :

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

  8. Cliquez sur Créer une règle> recherchez/sélectionnez > Suivant> Ajouter des autorisations.

  9. Créez une clé d'accès pour secops-reader : Identifiants de sécurité > Clés d'accès.

  10. Cliquez sur Créer une clé d'accès.

  11. Téléchargez le fichier .CSV. (Vous collerez ces valeurs dans le flux.)

Configurer un flux dans Google SecOps pour ingérer les journaux Zendesk CRM

  1. Accédez à Paramètres SIEM> Flux.
  2. Cliquez sur + Ajouter un flux.
  3. Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple, Zendesk CRM logs).
  4. Sélectionnez Amazon S3 V2 comme type de source.
  5. Sélectionnez Zendesk CRM comme Type de journal.
  6. Cliquez sur Suivant.
  7. Spécifiez les valeurs des paramètres d'entrée suivants :
    • URI S3 : s3://zendesk-crm-logs/zendesk/crm/
    • Options de suppression de la source : sélectionnez l'option de suppression de votre choix.
    • Âge maximal des fichiers : incluez les fichiers modifiés au cours des derniers jours. La valeur par défaut est de 180 jours.
    • ID de clé d'accès : clé d'accès utilisateur ayant accès au bucket S3.
    • Clé d'accès secrète : clé secrète de l'utilisateur ayant accès au bucket S3.
    • Espace de noms de l'élément : espace de noms de l'élément.
    • Libellés d'ingestion : libellé appliqué aux événements de ce flux.
  8. Cliquez sur Suivant.
  9. Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.

Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.