Recolha registos do CRM Zendesk

Compatível com:

Este documento explica como introduzir registos do sistema de gestão das relações com clientes (CRM) da Zendesk no Google Security Operations através do Amazon S3.

Antes de começar

Certifique-se de que cumpre os seguintes pré-requisitos:

  • Uma instância do Google SecOps.
  • Acesso privilegiado ao Zendesk.
  • Acesso privilegiado à AWS (S3, Identity and Access Management [IAM], Lambda e EventBridge).

Obtenha os pré-requisitos do Zendesk

  1. Confirme o plano e a função
    1. Tem de ser um administrador do Zendesk para criar tokens de API / clientes OAuth. A API Audit Logs está disponível apenas no plano Enterprise. (Se a sua conta não for empresarial, ignore o passo audit_logs em RESOURCES.)
  2. Ative o acesso ao token da API (único)
    1. No centro de administração, aceda a Apps e integrações > APIs > Configuração da API.
    2. Ative a opção Permitir acesso ao token da API.
  3. Gere um token da API (para autenticação básica)
    1. Aceda a Apps e integrações > APIs > Tokens da API.
    2. Clique em Adicionar token da API > (opcionalmente) adicione uma descrição > Guardar.
    3. Copie e guarde a chave da API agora (não vai poder vê-la novamente).
    4. Guarde o email de administrador que vai ser autenticado com este token.
      • Formato de autenticação básica usado pela função Lambda: email_address/token:api_token
  4. (Opcional) Crie um cliente OAuth (para autenticação Bearer em vez de token da API)
    1. Aceda a Apps e integrações > APIs > Clientes OAuth > Adicionar cliente OAuth.
    2. Preencha os campos Nome, Identificador exclusivo (automático), URLs de redirecionamento (pode ser um marcador de posição se apenas gerar tokens com a API) e Guardar.
    3. Crie um token de acesso para a integração e conceda os âmbitos mínimos exigidos por este guia:
      • tickets:read (para bilhetes incrementais)
      • auditlogs:read (para registos de auditoria; apenas para clientes Enterprise)
      • Se não tiver a certeza, o read também funciona para acesso só de leitura.
    4. Copie o token de acesso (cole-o em ZENDESK_BEARER_TOKEN) e registe o ID/segredo do cliente de forma segura (para fluxos de atualização de tokens futuros).
  5. Registe o URL base do Zendesk

    • Use https://<your_subdomain>.zendesk.com (cole na variável de ambiente ZENDESK_BASE_URL).

    O que copiar e guardar para mais tarde

    • URL de base (por exemplo, https://acme.zendesk.com)
    • Endereço de email do utilizador administrador (para autorização do token da API)
    • Token de API (se usar o AUTH_MODE=token)
    • ou chave de acesso OAuth (se usar AUTH_MODE=bearer)
    • (Opcional): ID/segredo do cliente OAuth para a gestão do ciclo de vida

Configure o contentor do AWS S3 e o IAM para o Google SecOps

  1. Crie um contentor do Amazon S3 seguindo este guia do utilizador: Criar um contentor
  2. Guarde o nome e a região do contentor para referência futura (por exemplo, zendesk-crm-logs).
  3. Crie um utilizador seguindo este guia do utilizador: criar um utilizador do IAM.
  4. Selecione o utilizador criado.
  5. Selecione o separador Credenciais de segurança.
  6. Clique em Criar chave de acesso na secção Chaves de acesso.
  7. Selecione Serviço de terceiros como Exemplo de utilização.
  8. Clicar em Seguinte.
  9. Opcional: adicione uma etiqueta de descrição.
  10. Clique em Criar chave de acesso.
  11. Clique em Transferir ficheiro .CSV para guardar a chave de acesso e a chave de acesso secreta para referência futura.
  12. Clique em Concluído.
  13. Selecione o separador Autorizações.
  14. Clique em Adicionar autorizações na secção Políticas de autorizações.
  15. Selecione Adicionar autorizações.
  16. Selecione Anexar políticas diretamente.
  17. Pesquise a política AmazonS3FullAccess.
  18. Selecione a política.
  19. Clicar em Seguinte.
  20. Clique em Adicionar autorizações.

Configure a política e a função de IAM para carregamentos do S3

  1. Na consola da AWS, aceda a IAM > Políticas.
  2. Clique em Criar política > separador JSON.
  3. Copie e cole a seguinte política.
  4. JSON da política (substitua zendesk-crm-logs se tiver introduzido um nome de contentor diferente):

    {
      "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. Clique em Seguinte > Criar política.

  6. Aceda a IAM > Funções > Criar função > Serviço AWS > Lambda.

  7. Anexe a política criada recentemente.

  8. Dê o nome ZendeskCRMToS3Role à função e clique em Criar função.

Crie a função Lambda

  1. Na consola da AWS, aceda a Lambda > Functions > Create function.
  2. Clique em Criar do zero.
  3. Faculte os seguintes detalhes de configuração:

    Definição Valor
    Nome zendesk_crm_to_s3
    Runtime Python 3.13
    Arquitetura x86_64
    Função de execução ZendeskCRMToS3Role
  4. Depois de criar a função, abra o separador Código, elimine o stub e cole o seguinte código (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. Aceda a Configuração > Variáveis de ambiente.

  6. Clique em Editar > Adicionar nova variável de ambiente.

  7. Introduza as variáveis de ambiente fornecidas na tabela seguinte, substituindo os valores de exemplo pelos seus valores.

    Variáveis de ambiente

    Chave Valor de exemplo
    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. Depois de criar a função, permaneça na respetiva página (ou abra Lambda > Functions > a sua função).

  9. Selecione o separador Configuração.

  10. No painel Configuração geral, clique em Editar.

  11. Altere Tempo limite para 5 minutos (300 segundos) e clique em Guardar.

Crie um horário do EventBridge

  1. Aceda a Amazon EventBridge > Scheduler > Create schedule.
  2. Indique os seguintes detalhes de configuração:
    • Agenda recorrente: Taxa (1 hour).
    • Destino: a sua função Lambda zendesk_crm_to_s3.
    • Nome: zendesk_crm_to_s3-1h.
  3. Clique em Criar programação.

(Opcional) Crie um utilizador e chaves da IAM só de leitura para o Google SecOps

  1. Aceda a AWS Console > IAM > Users > Add users.
  2. Clique em Adicionar utilizadores.
  3. Indique os seguintes detalhes de configuração:
    • Utilizador: introduza secops-reader.
    • Tipo de acesso: selecione Chave de acesso – Acesso programático.
  4. Clique em Criar utilizador.
  5. Anexe a política de leitura mínima (personalizada): Users > secops-reader > Permissions > Add permissions > Attach policies directly > Create policy.
  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. Nome = secops-reader-policy.

  8. Clique em Criar política > procure/selecione > Seguinte > Adicionar autorizações.

  9. Crie uma chave de acesso para secops-reader: Credenciais de segurança > Chaves de acesso.

  10. Clique em Criar chave de acesso.

  11. Transfira o .CSV. (Vai colar estes valores no feed).

Configure um feed no Google SecOps para carregar registos do Zendesk CRM

  1. Aceda a Definições do SIEM > Feeds.
  2. Clique em + Adicionar novo feed.
  3. No campo Nome do feed, introduza um nome para o feed (por exemplo, Zendesk CRM logs).
  4. Selecione Amazon S3 V2 como o Tipo de origem.
  5. Selecione Zendesk CRM como o Tipo de registo.
  6. Clicar em Seguinte.
  7. Especifique valores para os seguintes parâmetros de entrada:
    • URI do S3: s3://zendesk-crm-logs/zendesk/crm/
    • Opções de eliminação de origens: selecione a opção de eliminação de acordo com a sua preferência.
    • Idade máxima do ficheiro: inclua ficheiros modificados no último número de dias. A predefinição é 180 dias.
    • ID da chave de acesso: chave de acesso do utilizador com acesso ao contentor do S3.
    • Chave de acesso secreta: chave secreta do utilizador com acesso ao contentor do S3.
    • Espaço de nomes do recurso: o espaço de nomes do recurso.
    • Etiquetas de carregamento: a etiqueta aplicada aos eventos deste feed.
  8. Clicar em Seguinte.
  9. Reveja a nova configuração do feed no ecrã Finalizar e, de seguida, clique em Enviar.

Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais da Google SecOps.